Imaginando o cenário de uma locadora, vamos analisar o seguinte trecho de código:
Cliente cliente = new Cliente(); cliente.setNome("Geraldo Ferraz"); Filme filme = new Filme(); filme.setNome("Star Wars"); Locadora locadora = new Locadora(); locadora.getFilmes().add(filme); locadora.getClientes().add(cliente); Locacao locacao = new Locacao(); locacao.setCliente(cliente); locacao.setFilme(filme); locacao.setData(Calendar.getInstance()); locadora.getLocacoes().add(locacao); locadora.getLocacoes().remove(locacao);
O que esse código faz?
Se você concluiu rapidamente que esse código está cadastrando o cliente "Geraldo" e imediatamente fazendo a locação do filme "Star Wars" é porque provavelmente você está familiarizado com essa implementação. Pra quem não está e mesmo assim chegou na mesma conclusão, onde sentiu mais dificuldade?
Teria sido mais fácil se o código estivesse assim:
Cliente geraldo = new Cliente("Geraldo Ferraz"); Filme starWars = new Filme("Star Wars"); Locadora locadora = new Locadora(); locadora.associar(geraldo); locadora.adicionarAoAcervo(starWars); locadora.locarFilme(starWars).para(geraldo); geraldo.devolverPara(locadora).oFilme(starWars);
Não importa o nível de experiência que você tenha com programação, o segundo código é mais legível até por quem não é desenvolvedor. Isso porque o segundo código é muito mais expressivo e semântico.
Antes de continuarmos, vamos definir o que é expressividade e semântica.
Semântica:
É o estudo do significado. A semântica linguística estuda o significado usado por seres humanos para se expressarem através da linguagem.
Expressividade:
Claro, significativo, que dá a entender, enérgico.
Então se aplicarmos o conceito de semântica e expressividade nos nossos códigos, teremos códigos que são compreensíveis, claros e sem qualquer tipo de dupla interpretação.
Para conseguirmos um código expressivo e semântico precisamos ser críticos com o que escrevemos e temos sempre que fazer a seguinte pergunta: "será que outros entenderão o que escrevi?".
Ainda dentro do mesmo cenário (locadora), vamos analisar o seguinte código:
public void executar(Locacao loc) { long dif = 0; if (loc.getDataEntrega().getTimeInMillis() < Calendar.getInstance().getTimeInMillis()) { long hj = Calendar.getInstance().getTimeInMillis(); long dtLoc = loc.getDataEntrega().getTimeInMillis(); dif = (hj-dtLoc)/1000/60/60/24; } loc.setValorPago(loc.getFilme().getCategoria().getPreco() * (dif+1)); filmes.add(loc.getFilme()); }
O código acima não possui expressividade ou semântica alguma. Podemos notar isso claramente quando demoramos alguns minutos para saber que esse código está fazendo a devolução de um filme para a locadora.
Para tornar esse código mais expressivo e semântico, precisamos ser críticos com pequenos detalhes.
"executar" é algo muito genérico para o nome de um método, precisamos deixá-lo com um significado mais forte, algo que deixe claro qual é a verdadeira intenção do método. Vamos experimentar "efetuarDevolucao".
Muitos desenvolvedores estão acostumado a abreviar os nomes das variáveis. Nunca entendi essa prática, pensava que fosse por hábito ou alguma convenção, mas logo percebi que era por pura preguiça. Com todo o poder que as IDEs têm hoje em dia (auto-complete), não justifica abreviar os nomes das variáveis. Algumas pessoas podem até defender a Notação Húngara, mas eu acredito que se o nome for expressivo o suficiente chegaremos à mesma conclusão que a Notação Húngara propõe.
Vamos mudar "loc" para "locacao", "dif" para "diferencaDeDias", "dtLoc" para "dataLocacao" e "hj" para "hoje".
Vejamos como ficou nosso código depois dessas pequenas modificações:
public void efetuarDevolucao(Locacao locacao) { long diferencaDeDias = 0; if (locacao.getDataEntrega().getTimeInMillis() < Calendar.getInstance().getTimeInMillis()) { long hoje = Calendar.getInstance().getTimeInMillis(); long dataLocacao = locacao.getDataEntrega().getTimeInMillis(); diferencaDeDias = (hoje-dataLocacao)/1000/60/60/24; } locacao.setValorPago(locacao.getFilme().getCategoria().getPreco() * (diferencaDeDias+1)); filmes.add(locacao.getFilme()); }
Bem melhor, mas ainda está longe do ideal. Vamos analisar o primeiro "if", que pega a data da entrega e verifica se a mesma é menor que a data de hoje. O que, em muitas palavras, está perguntando se a devolução está atrasada. Essa pergunta está relacionada diretamente ao estado da locação, então devemos fazer essa pergunta diretamente pra a locação. Vejamos o resultado:
public void efetuarDevolucao(Locacao locacao) { long diferencaDeDias = 0; if (locacao.estaAtrasada()) { long hoje = Calendar.getInstance().getTimeInMillis(); long dataLocacao = locacao.getDataEntrega().getTimeInMillis(); diferencaDeDias = (hoje-dataLocacao)/1000/60/60/24; } locacao.setValorPago(locacao.getFilme().getCategoria().getPreco() * (diferencaDeDias+1)); filmes.add(locacao.getFilme()); }
Repare que até aqui não houve mudança na estrutura, apenas modificações simples nos nomes dos métodos e variáveis e mesmo assim já conseguimos ter uma leitura muito melhor do código.
Robert C. Martin, autor do livro Agile Software Development, Principles, Patterns, and Practices e Clean Code (um dos melhores livros que já li), fala sobre um princípio muito importante: Single responsability principle. O princípio explica que um módulo deve ter uma única responsabilidade, e no livro ele descreve a "responsabilidade" como "um motivo para mudar", e cita o exemplo de um gerador de relatório. Esse gerador tem dois motivos para mudar, primeiro, o conteúdo exibido, segundo, o layout. Esses dois motivos bem definidos são considerados motivos diferentes, portanto responsabilidades diferentes.
Com base nesse conceito, podemos perceber que o método "efetuarDevolucao" está com mais responsabilidades do que uma simples devolução. Para resolver isso temos que entender qual é o fluxo da devolução:
- Efetuar o pagamento
- Devolver o filme para o acervo.
Então devemos isolar a regra do pagamento na própria classe Locacao, resultando no seguinte código:
public void efetuarDevolucao(Locacao locacao) { locacao.efetuarPagamento(); devolverFilmeParaOAcervo(locacao.getFilme()); } private void devolverFilmeParaOAcervo(Filme filme) { filmes.add(filme); }
Resolvemos a primeira parte, agora vamos ver como ficou a classe locação.
public void efetuarPagamento(){ long diferencaDeDias = 0; if (dataEntrega.getTimeInMillis() < Calendar.getInstance().getTimeInMillis()) { long hoje = Calendar.getInstance().getTimeInMillis(); long dataLocacao = dataEntrega.getTimeInMillis(); diferencaDeDias = (hoje-dataLocacao)/1000/60/60/24; } valorPago = filme.getCategoria().getPreco() * (diferencaDeDias+1); }
Bem, parece que apenas empurramos o problema pra dentro da classe Locacao. Ainda não está claro como o método efetuarPagamento funciona. Para melhorar esse código devemos seguir os mesmos passos anteriores. Veja que as variáveis já possuem nomes significativos, então vamos definir quais são os passos para efetuar o pagamento e identificarmos se o método está com mais responsabilidade do que deveria.
- Verificar se já está pago.
- Verificar se está atrasado.
- Se estiver atrasado, cobrar multa.
- Se não estiver atrasado, fazer cobrança normal.
public class Locacao { private DateUtil util = new DateUtil(); public void efetuarPagamento() { if(!estaPago()){ if (estaAtrasada()) { efetuarPagametnoAtrasado(); }else{ efetuarPagamentoPontual(); } } } private boolean estaPago(){ return valorPago > 0; } private boolean estaAtrasada() { return util.ehDepoisDeHoje(dataEntrega); } private void efetuarPagametnoAtrasado() { valorPago = filme.getCategoria().getPreco() * quantidadeDeDiasAtrasados(); } private void efetuarPagamentoPontual() { valorPago = filme.getCategoria().getPreco(); } private long quantidadeDeDiasAtrasados() { return util.diasEntre(dataEntrega, DateUtil.HOJE); } }Agora conseguimos entender facilmente o que o "efetuarPagamento" está fazendo. Para uma leitura rápida, não temos a necessidade de olhar o conteúdo de cada método, faremos isso apenas se realmente houver a necessidade de modificarmos algum deles especificamente.
O blog VidaGeek.net recentemente fez um post falando sobre expressividade e nele o autor tenta mostrar a importância da expressividade em contextos do mundo real, como o de um escritor.
O autor fala também que um escritor tem à sua disposição vários recursos dentro de sua ferramenta que é a linguagem. Cabe a cada escritor escolher qual recurso é mais recomendado no momento ou para o público alvo.
Discutir semântica não é uma tarefa fácil, talvez por ser algo subjetivo, o que é mais significativo pra mim talvez não seja para outra pessoa. Em termos técnicos, acredito que a melhor opção é tentar ser o mais simples e breve possível e lembrar sempre que um código legível não facilita a manutenção apenas para os outros, mas pra si mesmo também.
Referências:
Semantica
Separation of concern
Single Responsability
Cohesion
Mlk ta mandando bem.
ResponderExcluirParabéns
Legal Geraldo, e isso ai, código legível facilita a vida de todo mundo além tornar a programação uma coisa mais "humana". Tem muita gente que "quer ver funcionando" e ta valendo, sou totalmente contra. Codigo legivel gera inclusive uma API de mais facil utilizacao e ajuda a nao ter "codigo duplicado que faz a mesma coisa" hahaha Abraco!!! To no aguardo do proximo!
ResponderExcluirQuem já programou com você sabe o valor que vc dá para os nomes de métodos e variáveis.
ResponderExcluirNo começo eu estranhava esse "costume" que você tinha em corrigir os nomes, mas depois comecei a perceber a diferença que isso faz.
Gostei muito do post. Parabéns.
Ficou muito bem escrito! parabéns Geraldo.
ResponderExcluirMeus sinceros parabéns pelo post, ficou excelente. Está Claro e objetivo!
ResponderExcluirVou me esforçar para adaptar esse conceito no meu dia-a-dia.