segunda-feira, outubro 15, 2007

Desenvolvendo Softwares Agradáveis de Manter

Já mencionei antes os sinais de um programa ruim e o site WTF que contém exemplos de como não fazer um software. Neste post vou tentar algo mais construtivo: sugestões de como escrever softwares que sejam mais agradáveis de manter.

A Raiz do Problema

Para dar manutenção em um programa é preciso entendê-lo. Portanto um programa será mais fácil de dar manutenção na medida em que ele for mais facilmente compreensível. Esta é a principal motivação das evoluções nas metodologias e ferramentas de desenvolvimento. Existe uma limitação do tamanho do contexto que conseguimos manipular, quando o programa ultrapassa este limite nós paramos de entender como o programa funciona e começamos a introduzir bugs.

No início era a linguagem de máquina e o Assembly. Uma operação simples exigia múltiplos comandos e rapidamente o limite era atingido. As linguagens de alto nível permitem colocar mais lógica em menos linhas de código, propiciando (mas não garantindo) uma maior legibilidade. Na chamada programação "espagete", o código era composto de trechos longos com a execução pulando de um ponto para outro por gotos. A programação estruturada procurou colocar ordem na casa, pregando a divisão em subrotinas e o uso de construções de controle padronizadas que sempre são entradas por cima e saídas por baixo (if-the-else, while, switch, etc).

As estruturas de dados tinham problemas semelhantes. Váriaveis globais eram acessadas por pontos dispersos do fonte. A programação estruturada introduziu os módulos e as variáveis locais. A programação orientada a objetos agrupou rotinas e estruturas de dados em classes, favorecendo a criação de interfaces claras e acessos controlados.

Consistência

Fica mais fácil aprender e lembrar as coisas que são iguais ou bem parecidas. Durante o desenvolvimento isto pode parecer cansativo e existe sempre a tentação de ser "criativo". Para ser um bom desenvolvedor é preciso saber onde ser criativo e onde ser consistente.

Dando Nomes

A consistência deve começar nos nomes de variáveis, rotinas, classes e módulos. Por pior que seja uma convenção para os nomes, ele será melhor que nenhuma.

É claro que os nomes devem indicar de forma precisa o que é armazenado na variável, efetuado pela rotina, etc:
  • Evite nomes muito genéricos. Uma variável nItens é bem mais clara que simplesmente n (ou pior ainda, x ou aux).
  • No caso de variáveis lógicas, o nome deve indicar o que representa o valor verdadeiro. Do ruim para o melhor: flag, flagPorta, flagPortaAberta.
  • Revise os nomes sempre que fizer uma alteração no código. Principalmente em rotinas é comum encontrar rotinas cujo nome foi dado devido a uma função que ela deixou de fazer.
Comentários

Os comentários podem ajudar em muito na compreensão de programas. Por outro lado eles podem também atrapalhar. Bons comentários são os que destacam as coisas não óbvias ou resumem o que um trecho mais complicado faz. Comentários ruins são os mentirosos (lembranças de cut-and-paste apressados ou não atualizados após mudanças) e os inúteis (que só ocupam espaço e reduzem a atenção).

Os comentários devem se preocupar mais nos porquês do que nos o que. Consideremos o exemplo clássico:

n = n + 1;

Alguns comentários úteis são

// temos mais um item na tabela
// a rotina OrdenaItens espera o número de itens, não o índice do último

Um comentário inútil é

// soma um a n

Um comentário ruim é

// diminui de um a quantidade de itens

Em alguns casos deve-se comentar também o que não é feito. Por exemplo, podemos ter os seguintes comentários ao invés de n = n + 1;

// n não é incrementado, pois vamos remover o item adiante
// n será incrementado pela rotina InsereItem

A padronização de comentários para variáveis e rotinas é também útil, principalmente para ajudar a lembrar o que deve ser documentado nos comentários.

Simplificando o Código

Durante a fase de depuração é comum mais código ir sendo acrescentado para cobrir casos que não tinham sido previstos inicialmente. Muitas vezes a codificação é encerrada quando o programa funciona. É importante revisar o código e ver se não é possível simplificá-lo (e testá-lo bem após a simplificação para garantir que continua funcionando).

Um exemplo típico de simplificação é detectar trechos repetidos várias vezes e colocá-lo em uma subrotina. Além de reduzir o tamanho do código (o que simplifica o entendimento), acertos e aperfeiçoamentos futuros serão feitos em um único ponto.

No caso de trechos parecidos é freqüentemente possível criar uma rotina "genérica" onde as diferenças de comportamento são feitas condicionalmente em função dos valores dos parâmetros ou de um parâmetro específico para isto. Entretanto, é preciso tomar o cuidado de não exagerar nesta generalidade, criando uma rotina extremamente confusa, cheia de testes. Neste caso rotinas específicas, mesmo que parecidas, pode ser mais claro e fácil de manter.

Um ponto que raramente é revisado são as variáveis lógicas. Se você perceber que está usando com muita freqüência a negação da variável, talvez você tenha escolhido a condição errada. Por exemplo, suponha que você tenha definido flagPortaAberta mas quase sempre você está usando !flagPortaAberta. Neste caso é melhor você passar a usar uma variável flagPortaFechada e se livrar das negações.

Resumindo, com alguns cuidados simples você vai melhorar em muito a qualidade de vida de quem for dar manutenção no código que você está escrevendo hoje. E lembre-se que este alguém pode ser você mesmo.

2 comentários:

Anônimo disse...

Gostei do post Daniel, mas gostaria de fazer um adendo.
Mutias vezes acho util a utilização da negação de uma variável lógica. Por exemplo, se no contexto do meu problema, as 3 situações são diferenciadas: "porta fechada", "porta totalmente aberta" e "porta entre-aberta".
Neste caso, definir uma variável como portaFechada, pode torna-se mais viável pois !portaFechada significa "Porta Totalmente Aberta" ou "Porta Entre-aberta".
Abraços...

wmoecke disse...
Este comentário foi removido pelo autor.