Por que você deve se preocupar com a organização e a testabilidade do seu código?

Num cenário onde a escalabilidade tem se tornado cada vez mais importante e necessária, esses aspectos se tornam primordiais para a evolução do seu produto no ritmo que o mercado precisa.

Organização

Sempre quando você começa a ler um bom livro é possível notar que ele foi escrito de uma forma organizada, revisado algumas vezes e, provavelmente, possui algumas edições — que tendem a trazer conteúdos mais atualizados e com melhorias. Isso torna sua compreensão mais fácil e a leitura mais agradável. E é possível fazer o mesmo com seu código fonte.

De maneira geral, existem diversas práticas que podem te ajudar a manter seu código organizado, legível e, o mais importante, de fácil compreensão. Mas algumas delas são um bom ponto de partida, independente do tempo de experiência que você tenha em programação.

Nomes significativos

Essa é uma das práticas mais óbvias. Porém, é também uma das mais difíceis de lidar. Escolher bons nomes nunca é fácil. Já trabalhei — e trabalho — com excelentes programadores. E todos eles enfrentam o mesmo problema que eu:

Qual nome vou dar para este método/classe/variável de forma que, ao lê-lo apenas uma vez, eu saiba qual a sua real função?

Mais importante do que você conhecer a real função daquele método/classe/variável é fazer com que outros programadores saibam. Sempre tenha em mente que você nunca será o único a trabalhar em um código fonte.

Já trabalhei em códigos cujo um dos métodos se chamava abracadabra. A verdade é que tive que gastar uns bons minutos até descobrir o que este método realmente fazia. 😕

Classes e métodos enxutos

Quanto menor o escopo de um método, maior a chance dele fazer apenas uma coisa. Se o seu método desempenha apenas uma função será mais fácil entendê-lo e testá-lo. O mesmo deve acontecer com suas classes. Porém, o contexto normalmente costuma ser um pouco maior (ou mais complexo), ainda que haja apenas uma responsabilidade na sua aplicação:

Exemplo de um método extenso e com múltiplas responsabilidades

O método acima faz mais de uma coisa e, portanto, possui diversas responsabilidades. Neste caso, escrever um teste unitário seria uma tarefa muito mais complicada do que o necessário.

Exemplo de um método um pouco mais enxuto, legível e testável

O objetivo do exemplo apresentado é apenas ilustrar como um método pode ter seu escopo reduzido se fizer apenas uma coisa — e como testá-lo pode ser muito mais fácil dessa forma.

Não use trocadilhos e/ou codificações

Também já trabalhei em códigos que eram uma verdadeira macarronada: bem desorganizado. A pior coisa, nesses casos, é você gastar minutos (senão horas) navegando em um código confuso onde a maioria dos nomes de métodos e variáveis são trocadilhos (ou até mesmo piadas internas entre programadores que trabalhavam naquele código em épocas passadas). Isso está diretamente ligado à prática de nomes sugestivos. Mas vale ressaltar que codificações também são péssimas, como mostra o exemplo a seguir:

GeolocationSubmitterService glss = new GeolocationSubmitterService();

Com certeza, ao longo do código, você se irá se deparar com glss e se perguntar: mas que diabos é isso?

Tenha uma arquitetura definida (por mais básica que seja)

Saber o que cada parte do seu sistema deve fazer (Presenters, Views, UseCases, etc.) irá te salvar boas horas pensando onde encontrar determinado trecho de código — ou até mesmo onde encaixar determinada modificação. Você não precisa ser um arquiteto e nem um desenvolvedor mega experiente para pensar em arquitetura. Existem vários artigos que irão te mostrar algumas bem conhecidas e eu te garanto: não são nenhum bicho de sete cabeças. Além disso, você também pode (e deve) adaptá-las às suas necessidades — mas deixe isso bem definido para não ter um grande spaghetti à bolognesa lá na frente.


Existem inúmeras outras boas práticas que você pode seguir, mas mencionar a maioria delas deixaria esse artigo ainda mais extenso. Por isso, e para finalizar, gostaria de indicar alguns livros que eu sempre gosto de reler de vez em quando:

  • Clean Code, Uncle Bob;
  • The Clean Coder, Uncle Bob;
  • Working Effectivelly With Legacy Code, Michael C. Feathers;
  • Effective Java, Joshua Bloch.

Estamos quase no fim. Mas não podemos fechar esse trecho sem falar que…

S.O.L.I.D. is your friend

Em breve teremos um artigo apenas para falar sobre estes princípios. Mas vale lembrar que, independente da arquitetura que você utilize em sua aplicação, estes 5 princípios (apesar de serem um pouco mais complexos e demandarem uma leitura um pouco mais demorada) certamente irão te salvar tempo de (re)codificação no futuro:

  1. SRP (Single Responsibility Principle)
  2. OCP (Open/Closed Principle)
  3. LSP (Liskov Substitution Principle)
  4. ISP (Interface Segregation Principle)
  5. DIP (Dependency Inversion Principle)

Testabilidade

Eu costumo mensurar a qualidade de um código baseado na facilidade que eu tenho de testá-lo. Após algum tempo utilizando TDD, você percebe facilmente alguns pontos que podem dificultar a testabilidade de um código — e tais pontos, na maioria das vezes, estão relacionados ao SRP e ao DIP.

A organização do código de testes é tão importante, senão mais, quanto o código de produção e a explicação para isso é bem interessante: testes ajudam a manter seu código limpo, organizado e previsível — e, quando digo previsível, me refiro à possibilidade de alterá-lo e garantir que o comportamento do seu código continue como o esperado.

Quando seus testes estão desorganizados ou mal escritos, a tendência é você dar cada vez menos manutenção, uma vez que isso demandará mais tempo. Por consequência, seus códigos de produção não passarão mais pelos testes com sucesso, fazendo com que você pare de executá-los. Pode não parecer, mas isso acaba se tornando uma bola de neve, principalmente quando seu projeto ainda não possui uma arquitetura bem definida — cabe um artigo apenas para falar sobre como testes podem te ajudar a definir uma arquitetura para seu projeto.

E como eu poderia escrever testes limpos e organizados, que sejam fáceis de serem mantidos?

F.I.R.S.T things first

Da mesma forma que o S.O.L.I.D te ajudará a ter um código minimamente organizado, o F.I.R.S.T também contribuirá muito para os seus testes.

  • Fast: seus testes devem rodar de maneira rápida, de forma que você possa executá-lo a cada vez que compila seu projeto sem ter que esperar minutos enquanto seus testes unitários estão rodando.
  • Independent: seus testes não devem depender uns dos outros. Dessa forma, você não terá que rodá-los numa determinada ordem para que passem com sucesso. Devem ser eventos totalmente independentes.
  • Repeatable: seus testes devem ser repetíveis, de forma que, independente do número de vezes que você rodá-lo, os resultados sempre serão os mesmos — e o tempo de execução também. Em outras palavras: seus testes não podem depender de conexão com APIs, Databases etc. (a não ser que sejam testes de integração) ou qualquer outra parte do ecossistema que possa tornar seu teste volátil.
  • Self-validating: seus testes devem ter uma saída booleana, indicando sucesso ou falha. Não deve ser necessária a análise de um arquivo de log — ou até mesmo a comparação de dois arquivos — para saber se o teste fora executado com êxito. Afinal, isso pode levar a falhas subjetivas e exigir um bom tempo até você saber o resultado do teste.
  • Timely: seus testes unitários devem ser escritos antes do código de produção — alguns autores falam sobre Test Before vs Test After, mas a leitura se estenderia um pouco. Por ora, vou falar dos upsides de se escrever antes. Optar por testes unitários imediatamente antes do código de produção te força a escrever um código fácil, não acoplado e com uma complexidade baixa. Resumindo: algo que você só notaria ao tentar escrever os testes após o código de produção — e, provavelmente, ainda teria um trabalho extra de refatoração.

Então, por que me preocupar com tudo isso?

Independente do seu nível de experiência, sempre será mais fácil alterar seu próprio código do que algum que tenha sido escrito por terceiros. É claro que, com o passar do tempo, torna-se mais fácil alterar códigos de outras pessoas, mas, ainda assim, você sempre se sentirá mais confortável em alterar o seu próprio. Isso é fato!

Um projeto que possui classes com responsabilidades únicas e bem definidas, baixo acoplamento entre elas, padronização na nomenclatura de classes, métodos e arquivos e com nomes significativos demandará menos tempo para se identificar onde determinada alteração deve ser feita - ou determinado bug pode estar acontecendo, ou até mesmo uma nova classe deve ser introduzida. Além disso, métodos cobertos por testes tornam muito mais segura e rápida qualquer alteração, uma vez que os testes garantirão que elas não causarão efeitos colaterais indesejados ao longo da aplicação nem que o comportamento esperado está de fato ocorrendo.

Pode parecer difícil (ou até mesmo impossível) ter um código fácil de trabalhar, sem muitas dores. Mas, quando seu time todo está alinhado e isso torna-se a cultura de desenvolvimento, o produto acaba atingindo esse patamar de forma natural. E, acredite: para chegar em tal nível não é necessário muito. Com pequenos passos (e algumas preocupações em mente) é totalmente possível e viável: basta mudar seu mindset. Trabalhar em um código limpo, estruturado e testado irá aumentar sua produtividade, a qualidade e a velocidade de suas entregas, tornando possível escalar sua equipe e produto de forma controlada e previsível.