"Programming is best regarded as the process of creating
works of literature,
which are meant to be read."
—Donald E. Knuth,
Literate Programming
". . . put a comment on each function saying what the function does,
what sorts of arguments it gets,
and what the possible values of arguments
mean and are used for [ . . . ]
Also explain the significance of the return value, if there is one."
—Seção
Commenting Your Work
em
GNU Coding Standards
"Needless to say
[that]
all by itself,
a program is no more than half a conjecture.
The other half of the conjecture
is the functional specification the program is supposed to satisfy.
The programmer's task
is to present such complete conjectures as proven theorems."
—Edsger W. Dijkstra,
In Pursuit of Simplicity,
manuscrito EWD1036
"Computer science is no more about computers
than astronomy is about telescopes."
—Edsger W. Dijkstra
"Programs must be written for people to read,
and incidentally for machines to execute."
— H. Abelson, G. Sussman,
The Structure and Interpretation of Computer Programs
"Debugging is twice as hard as writing
the code in the first place.
Therefore, if you write the code as
cleverly as possible, you are,
by definition, not smart enough to
debug it."
— Brian W. Kernighan
Há quem pense que documentar um programa é o mesmo que escrever muitos comentários de mistura com o código. Totalmente errado! Uma boa documentação não suja o código com comentários. Uma boa documentação limita-se a
explicar o que cada função do programa faz.
Em geral, uma boa documentação não perde tempo tentando explicar como uma função faz o que faz, porque o leitor interessado nesse assunto é capaz de ler o código.
A distinção entre o que e como é a mesma que existe entre a interface (arquivo .h) e a implementação (arquivo .c) de uma biblioteca C. Eis uma analogia que pode tornar mais clara a diferença. Uma empresa de entregas promete apanhar o seu pacote em São Paulo e entregá-lo no Rio de Janeiro. Isso é o que a empresa faz. Como o serviço será feito — se o transporte será terrestre, aéreo ou marítimo, se haverá ou não uma escala em Belo Horizonte, etc. — é assunto interno da empresa.
Em suma, a documentação de uma função ou algoritmo é um mini-manual que dá instruções precisas e completas sobre o uso correto da função. A primeira parte desse mini-manual deve dizer o que a função recebe e o que devolve; em C, isso é conhecido como protótipo da função.
Uma documentação correta é até uma questão de honestidade intelectual, pois coloca nas mãos do leitor a real possibilidade de encontrar os erros que o autor tenha porventura cometido ao escrever o código.
Veja também o verbete Software documentation na Wikipedia.
Em uma das páginas destas notas escrevemos uma função que calcula o valor de um elemento máximo de um vetor. Vamos repetir aqui o código daquela função juntamente com uma documentação perfeita:
/* A função abaixo recebe um número n >= 1 e um vetor v
* e devolve o valor de um elemento máximo de v[0..n-1].
******************************************************/
int
max (int n, int v[])
{
int j, x = v[0];
for (j = 1; j < n; j += 1)
if (x < v[j]) x = v[j];
return x;
}
Veja como a documentação é simples e exata. A documentação só diz o que a função faz; ela não perde tempo tentando explicar como a função faz o que faz (por exemplo, se a função é recursiva ou iterativa, se percorre o vetor da esquerda para a direita ou vice-versa). Observe também que não há comentários inúteis (como "o índice j vai percorrer o vetor" ou "x armazena o maior valor encontrado até agora") sujando o corpo da função.
Eis alguns exemplos de má documentação. Dizer apenas que a função
devolve o valor de um elemento máximo de um vetor
é indecentemente vago. Dizer que a função
devolve o valor de um elemento máximo do vetor v
é um pouquinho melhor, mais ainda muito vago: o leitor fica sem saber qual o papel do parâmetro n. Dizer que a função
devolve o valor de um elemento máximo de um vetor v que tem n elementos
é melhor, mas ainda está vago: não se sabe se o vetor é v[0..n-1] ou v[1..n]. Dizer que a função
devolve o valor de um elemento máximo de v[0..n-1]
já está quase bom, mas sonega a informação de que a função só funciona corretamente se n ≥ 1.
Em uma das páginas destas notas escrevemos uma função que decide se um número x é igual a algum elemento de um vetor v. Vamos repetir aqui o código daquela função juntamente com uma documentação perfeita:
/* Recebe um número x, um vetor v e um índice n >= 0.
* Devolve 1 se x está em v[0..n-1] e devolve 0
* em caso contrário.
***************************************************/
int
verifica (int x, int n, int v[])
{
int j = 0;
while (j < n && v[j] != x)
j += 1;
if (j < n) return 1;
else return 0;
}
Veja como a documentação diz, de maneira exata e completa, o que a função faz. Veja como a documentação não perde tempo tentando explicar como a função faz o serviço.
Para contrastar, eis alguns exemplos de má documentação. Dizer
decide se x está em v[0..n-1]
é um tanto vago; dizer
decide se x está no vetor v
é muito vago; dizer
decide se um número está em um vetor
é absurdamente vago.
Eis uma função acompanhada de documentação perfeita:
/* Recebe x, v e n >= 0 e devolve j * tal que 0 <= j < n e v[j] == x. * Se tal j não existe então devolve n. *************************************/ int onde (int x, int v[], int n) { int j = 0; while (j < n && v[j] != x) j += 1; return j; }
int compmax (int a[], int n) { int i, k, max = 1; i = 0; for (k = 1; k <= n; k++) if (k == n || a[k] != a[i]) { if (max < k-i) max = k-i; i = k; } return max; }
A documentação abaixo está correta?
/* a função recebe um vetor a[0..n-1] e devolve * o comprimento de um segmento horizontal máximo do vetor *********************************************************/
A seguinte documentação está correta?
/* a função recebe um vetor crescente a[0..n-1] com n >= 1 * e devolve o comprimento de um segmento horizontal máximo * do vetor *********************************************************/
Existem várias ferramentas sofisticadas que permitem uma excelente integração de código com documentação. Veja minha página sobre literate programming e CWEB. Eis alguns exemplos de programas escritos em CWEB: mdp, isort. [Software para ler arquivos .pdf]
O corpo de muitas funções consiste em um processo iterativo (um for ou um while). Nesses casos, depois de dizer o que a função faz, você pode enriquecer a documentação dizendo quais as invariantes do processo iterativo. Uma invariante é uma relação entre os valores das variáveis que
vale no início de cada iteração e não se altera de uma iteração para outra.
Essas relações invariantes explicam o funcionamento do processo iterativo e permitem provar, por indução, que ele tem o efeito desejado.
Eis um exemplo. A função max recebe um número n ≥ 1 e um vetor v e devolve o valor de um elemento máximo de v[0..n-1]:
int
max (int n, int v[])
{
int j, x = v[0];
for (j = 1; j < n; j++)
/* neste ponto, x é um elemento máximo de v[0..j-1] */
if (x < v[j]) x = v[j];
return x;
}
Outro exemplo: a função compmax recebe um vetor a[0..n-1] e devolve o comprimento de um segmento horizontal (veja exercício acima) máximo do vetor:
int compmax (int a[], int n) { int i, k, max = 1; i = 0; for (k = 1; k <= n; k += 1) /* neste ponto, max é o comprimento de * um segmento horizontal máximo de a[0..i-1] * e a[i..k-1] é um segmento horizontal *******************************************/ if (k == n || a[k] != a[i]) { if (max < k-i) max = k-i; i = k; } return max; }
Vários exemplos de prova de correção de algoritmos baseada em invariantes aparecem, por exemplo, nas páginas dedicadas à busca binária, à ordenação, ao mergesort, ao heapsort, ao quicksort.
(O conceito de invariante é análogo ao que existe na Física. Albert Einstein era particulamente habilidoso em descobrir invariantes — como a velocidade da luz, por exemplo.)