Um post no GUJ mais uma vez rende uma resposta maior do que se supunha. A thread em si é bastante útil mas existe muito ruído então vou tentar sumarizar:
Vocês pegam códigos que te faz passar horas e horas tentando entender o que está sendo feito? Valores que você nem imagina de onde estão vindo?
Eu sou muito ruim ou isso é normal?
E logo surge alguém sugerindo que deveria haver documentação. Existem casos onde documentação, seja JavaDoc, especificação funcional ou etc. é fundamental mas neste caso é diferente. JavaDoc, especificação textual e etc. devem ser uma fonte importante quando que está interessado nessa informação não vai lero código, é como reutilizar uma biblioteca ou um framework. Ninguém quer abrir o Spring para entender como utilizá-lo, precisamos de documentação para isso.
Um cenário completamente diferente é quando você recebe de presente um código já existente para dar manutenção. Neste caso o código tem que fazer sentido, tem que dizer algo. Tem que ser expressivo, mostrar suas intenções claramente.
NOTA: O trecho abaixo foi escrito direto no editor de texto, sem ajuda de compilador ou IDE. Por favor me corrijam.
Qualquer programador meia-boca sabe sua linguagem. Sabe o domínio dela. Veja o trecho abaixo:
abstract class A{
public abstract int d(){
}
}
class B extends A{
int z= 0;
int x=-1;
public B(int z, int x){
this.z=z;
this.x=x;
}
public int d(){
if(z==15) return x + x* 0.15;
esle return x;
}
}
Você entendeu muito bem, tenho certeza, que A é uma classe abstrata implementada por B. Que B tem um construtor que recebe dois inteiros, os armazena e usa numa computação simples com uma ramificação abaixo. Ótimo.
Imagine que você recebeu um email do seu usuário dizendo: “Precisamos fazer com que clientes do sexo feminino que comprem mais de R$1000,00 ganhem 10% de desconto.”. Tente implementar isso no código acima enquanto eu vou almoçar.
E aí, terminou? Sim, sim, claro que é impossível sem saber d que se trata. Então depois de procurar bastante você encontra no diretório onde fica a documentação do projeto um arquivo que pode te ajudar a entender. Após umas cinquenta páginas de diagramas de alto nível inúteis explicando tudo que acontece no container web você chega a uma descrição de algo como:
A classe A tem a lógica abstrata de uma venda. As vendas são sempre feitas de acordo com critérios específicos por isso existem classes que implementam vendas específicas. No momento (01/10/2003) só existe uma implementação, na classe B, que é a venda para uma pessoa física.
A primeira coisa que você pensa é: se desde 2003 ninguém precisou de outra implementação para que essa maldita classe abstrata? Mas ants de mexer no código precisamos entendê-lo, então vamos em frente.
O méodo e questão recebe três argumentos: a quantdade em reais vendida, o sexo da pessoa (segundo código vindo do mainframe na tabela ETXS32) e se a compra é casada ou não (um booleano).
De acordo com o caso de uso UC171 o sexo do comprador é utilizado para aplicar descontos.
TABELA ETXS32
15 -> masculino
22 -> feminino
Se a compra for casada o processamento é feito delegando para outra classe, mantendo o padrão Strategy, Composite e Adapter do Decorator. Note que o Chain of Responsibility do Bridge é usado com Visitors para passar as instâncias de Flyweight pelos Interpreters[...]
Após a sequência de buzzwords de padrões é bom parar. Acho que a informação necessária já está aí em cima e olha que não se passaram nem 4 horas de procura! Vamos lá: recebemos o valor, o sexo segundo um código numérico sem lóica que vêm de outro sistema. Também é passado um boolean.. cadê o boolean?
Procurando no histórico deste arquivo no CVS (poxa vida, eles ainda usam CVS!) você vê que no meio de 2005 alguém tirou o boolean de lá com um comentário ‘Removido compra casada. Ninguém usa isso e ninguém entende isso. Ninuém vai sentir falta’. Acho que a pessoa estava correta mas ela esqueceu que existem uns 300 documentos que precisam ser revistos porque todos fazem referência a esta tal venda casada, do caso de uso, diagrama de domínio até diagrama de deployment. Cada mudança simples implica em editar 300 documentos… provavelmente mais tempo atualizando a tal documentação que o código… dá pra culpar o desenvolvedor?
Bom, agora você entende o que o código faz ao menos. Ele está aplicando um desconto de 15% para homens, você não conseguiu achar isso no caso de uso mas se ninguém está reclamando em produção é porque deve ser assim mesmo. Amanhã (afinal, você perdeu o dia inteiro hoje na ‘documentação’ do sistema) você faz a mudança.
Novo dia e você está pronto para alterar este código. Hmm… alterar pode ser muito rápido e sujo ou devagar e bem feito. Como disse o Uncle Bob recentemente sujo nunca é rápido então você opta pelo caminho com mais qualidade (e mais ético).
Como desenvolvedores profissionais escrevem testes (e gerar você deve começar por aí. Você sabe muito pouco sobre este sistema e o teste vai te dar alguma garantia que a menos esta pequena parta que está mexendo vai continuar funcionando após suas modificações. Vamos lá:
class TestVenda extends TestCase{
public void testDeveAplicarDescontoSeSexoDoCompradorForMasculino(){
B vendaParaUmHomem = new B(15, 100);
assertEquals("Valor final sofreu 15% de desconto", 85, vndaParaUmHomem.d());
}
public void testDeveNaoAplicarQualquerDescontoSeVendaNaoCaiEmNenhumaPromocao(){
B vendaParaUmHomem = new B(9999, 1);
assertEquals("Valor final intacto", 1, vndaParaUmHomem.d());
}
}
Os testes executam. Agora vamos alterar um pouco esta classe, pensando no pobre coitado que for mexer nela após você. Vamos começar agregando nomes mais expressivos:
abstract class VendaAbstrata{
public abstract int vender(){
}
}
class Venda extends VendaAbstrata{
static final int NAO_INFORMADO = -1;
static final int MASCULINO = 15;
static final int FEMININO = 22;
int sexoDoComprador= NAO_INFORMADO;
int valorDaCompra=-1;
public B(int sexoDoComprador, int valorDaCompra){
this.sexoDoComprador=sexoDoComprador;
this.valorDaCompra=valorDaCompra;
}
public int vender(){
if(sexoDoComprador==MASCULINO) return valorDaCompra - valorDaCompra* 0.15;
esle return valorDaCompra;
}
}
Já está bem melhor, não? Compare com a primeira versão do código Os testes passam? Então é hora de commitar (eu acho esse neologismo horrível mas alguém sugere algo melhor?).
Vamos para o segundo round: pequeno refactoring. Já é possível fazer a alteração neste código mas anda temos tempo para deixá-lo um pouquinho mais legível, mais claro, mais expressivo. Vamos alterar:
abstract class VendaAbstrata{
public abstract int vender(){
}
}
class Venda extends VendaAbstrata{
static final int NAO_INFORMADO = -1;
static final int MASCULINO = 15;
static final int FEMININO = 22;
int sexoDoComprador= NAO_INFORMADO;
int valorDaCompra=-1;
public B(int sexoDoComprador, int valorDaCompra){
this.sexoDoComprador=sexoDoComprador;
this.valorDaCompra=valorDaCompra;
}
public int vender(){
int valorFinalDaCompra = aplicarDescontosSobreValorDaCompra();
return valorFinalDaCompra;
public int aplicarDescontosSobreValorDaCompra(){
int valorComDesconto= valorDaCompra;
if(sexoDoComprador==MASCULINO)
valorComDesconto = descontarPorcentagem(15, valorDaCompra);
return valorComDesconto;
}
public int descontarPorcentagem(int porcentagem, int valorOriginal){
return valorOriginal * (porcentagem / 100.0);
}
}
Agora que tal esta versão do código + teste unitário contra a versão antiga + trezentos documentos e especificações? A implementação da regra nova ficou fácil? Acho que sim, tanto que deixo como exercício ao leitor, bem como algumas dezenas de refactorings que vão deixar o código acima decente.
A resposta curta para a thread do GUJ é: geralmente o problema não é seu mas de quem escreveu o código.
Any fool can write code that a computer can understand. Good programmers write code that humans can understand.
-Martin Fowler, “Refactoring: Improving the Design of Existing Code “