É natural do ser humano aprender com os seus próprios erros. Quem aqui nunca vivenciou uma situação inusitada na qual mesmo tendo a impressão de que algo está errado, acabamos ignorando e seguindo em frente. E assim continua até que, num belo dia, a bomba explode e você pensa consigo mesmo: “eu sabia!”.
Essas situações, embora aparentam ser ruins em um primeiro momento, são excelentes oportunidades para nos ensinar algumas coisas que provavelmente não aprenderíamos de outra forma. Aliás, talvez seja até mais fácil aprender com nossos próprios erros do que ler sobre isso em algum livro ou artigo. O problema é que nem sempre é fácil saber tirar algo de bom de situações problemáticas.
Por isso, tentarei fazer um gancho entre esse assunto e a área de desenvolvimento de softwares, mostrando algumas das lições em que aprendi errando. Também seria interessante se os leitores que se identificarem com o assunto também compartilhassem algumas lições que aprenderam em períodos turbulentos. Afinal, errar faz bem!
Lição número 1: confie no código e não no documento
Deixe-me contar uma experiência interessante que tive na faculdade. Certa vez, um de nossos professores chegou na aula, colocou no projetor um Diagrama de Classes e um Modelo Entidade-Relacionamento e disse para a sala:
Esse é o modelo de um sistema de locadora. Nesses diagramas vocês tem tudo o que é preciso pra fazer isso funcionar. Não quero nem saber se vocês não gostam do cara que está sentado do seu lado, se acha que ele tem chulé ou mau hálito; eu quero que vocês aí se organizem e me entreguem isso funcionando até o final da aula.
Enfim, todo mundo se levantou e começou a andar de um lado para o outro e, alguns momentos depois, tínhamos nos organizado para dar início a programação. Pois bem… depois de algum tempo programando o que estava nos diagramas, o professor se volta para a sala e diz, em tom de sarcasmo:
Vem cá, vocês não encontraram nada estranho, não?
Praticamente todo mundo da sala havia percebido que alguns relacionamentos estavam faltando, outros sobrando, outros com a multiplicidade incorreta, entre outras coisas. Ninguém só não havia dito nada antes por pensar que o diagrama era indefectível. Se isso aconteceu em um “projeto de mentira” com duas dúzias de pessoas, quem imagina o que pode acontecer em projetos com dezenas e dezenas de pessoas envolvidas?
O fato é que os erros foram colocados lá propositalmente e isso acabou me fazendo perceber que nem sempre um documento representa fielmente um sistema. O que sempre representa fielmente um sistema é o seu código-fonte!
Durante um tempo, trabalhei numa empresa onde praticamente tudo o que precisava ser desenvolvido era entregue aos programadores na forma de textos (para fins de documentação, diziam). Para você perceber a gravidade da situação, se eu implementasse exatamente o que estava escrito nas folhas que recebia, a chance do sistema sequer compilar era altíssima. Além do mais, as especificações normalmente vinham incompletas, o que fazia com que nós tivéssemos de nos preocupar não em simplesmente programar, mas também de tentar driblar possíveis falhas na especificação.
Eu confesso que, naquele tempo, a minha vontade era dar um tiro na minha própria cabeça. Mas hoje, olhando para trás, percebo que isso fez com que eu adquirisse algumas habilidades que não seriam desenvolvidas se as especificações chegassem perfeitas em minhas mãos.
Lição número 2: escreva testes automatizados
Novamente remetendo aos tempos da faculdade, eu não poderia ter escolhido um tema melhor para o meu TCC do que o desenvolvimento de um framework. Me lembro que fui alertado dezenas de vezes pelos professores de que tratava-se de algo inusitado e que eu não teria direito de reclamar caso algo saísse errado. Bem ameaçador, não acha?
Apesar de assumir o desafio sozinho - pois a faculdade não dispunha de um orientador com experiência em design de frameworks - tudo correu bem nas primeiras semanas de desenvolvimento, afinal o código estava bonito e funcionava bem. O que mais eu poderia querer? Entretanto, um sério (e óbvio) problema foi se instalando sem que eu percebesse: a ausência de testes automatizados.
No começo, quando o framework possuía apenas algumas dezenas de classes - era fácil localizar e corrigir os problemas que apareciam. Mas, quanto mais o código crescia, menos controle eu tinha sobre ele. Fazer experimentações no código já não era mais seguro.
Nessa época eu já sabia como escrever testes e tudo mais. O meu grande equívoco foi pensar que, se poupasse o tempo utilizado na escrita dos testes, eu teria mais tempo para entregar aquilo que prometi no começo do ano. Obviamente, eu não poderia estar mais errado.
Escrever testes automatizados (e até mesmo levá-los a outro nível, como acontece com o TDD) é essencial para que as aplicações evoluam de forma consistente. Sem a segurança que tais testes proporcionam, é impossível garantir que o que foi feito hoje continue funcionando amanhã. Não testar o código é comparável a um jogo de tabuleiros, onde, num momento de azar, você acaba caindo numa casa que diz “volte à primeira casa”.
A grande lição que eu tirei disso foi aprender que os testes não servem unicamente para comprovar que tudo está funcionando (pois isso eu já sabia). Eu aprendi que deixar de escrever testes alegando falta de tempo é burrice. Falando nisso, ainda encontramos pessoas que dizem que testes são ruins porque “é mais código para dar manutenção”, ou “escrever testes consome tempo que não temos” ou ainda “mas quem vai testar os testes?”. Não irei responder a essas perguntas, mas posso dizer que são poucas as coisas que fazem tão bem a um software quanto a criação de testes automatizados.
Lição número 3: desenvolva software para o cliente, não para a infraestrutura
Algumas pessoas, ao se iniciar um novo projeto, já tenta desde o início juntar e configurar o framework de injeção de dependências, o framework web, o framework ORM, e por aí vai. A minha pergunta é: pra quê? Confesso que eu mesmo já fiz isso, mas devo dizer que trabalhar dessa forma não faz muito sentido na maioria dos casos.
Voltando aos tempos de faculdade, uma outra coisa que eu gostava era que nós montávamos os sistemas sem pensar em bancos de dados, latência da rede, essas coisas. Nosso banco de dados era nada mais que algumas instâncias de LinkedHashSet! Então, considerando que algumas boas práticas foram usadas e que o sistema esteja funcionando em memória, trocar o mecanismo de persistência (ou lidar com qualquer outra questão de infraestrutura) era fácil.
Por exemplo, em um sistema de locadora, o caso de uso Alugar Filme não fará nada de diferente caso estejamos persistindo os objetos em um banco de dados em vez da memória. Da mesma forma, o caso de uso Reservar Filme (que envia uma mensagem para o usuário informando quando o filme está disponível) não mudará se trocarmos a implementação atual - que executa…
System.out.println("Filme disponível");
…para uma outra que utiliza a API JavaMail para enviar uma mensagem via SMTP. O que importa é que os objetos estão sendo armazenados em algum lugar e que o usuário é notificado de algum jeito quando o filme que ele reservou está disponível. Acredite, isso é o bastante durante boa parte do desenvolvimento de uma aplicação.
Lição número 4: seja preguiçoso
Uma coisa interessante que aprendi foi que nem todo programador preguiçoso é um bom programador, mas todo bom programador é preguiçoso!
Os bons desenvolvedores possuem um faro aguçado que os permite identificar situações nas quais soluções melhores podem ser adotadas de modo a diminuir esforços, tornando o código (e a aplicação de um modo geral) mais conciso e organizado. Alguns chamam tais situações de cheiros (ou smells), termo este que “pegou” após a publicação do livro Refactoring: Improving the Design of Existing Code, de Martin Fowler.
Para identificar tais situações, vamos começar do básico: o que um desenvolvedor de software mais costuma odiar em seu trabalho? Trabalho repetitivo. Se você precisa fazer uma determinada tarefa e tal tarefa exige a repetição de procedimentos que você suspeita serem supérfluos, então faça um favor para si mesmo: deixe sua preguiça trabalhar por você!
Não sei se você é assim, mas eu costumo aprender as coisas mais rapidamente através de exemplos… então, vamos a eles.
Imagine que você tenha sido selecionado para trabalhar no deployment de uma aplicação. Se você já tentou implantar uma aplicação “no braço”, certamente sabe que, se você for fazer o trabalho manualmente, terá de fazer sempre as mesmas coisas, indefinidamente. O deployment de uma aplicação simples poderia ser exemplificada da seguinte forma:
- Organizar as classes compiladas em uma estrutura de diretórios específica;
- Copiar bibliotecas que devem estar disponíveis a nível de servidor (como drivers JDBC) no diretório de bibliotecas do Container;
- Disponibilizar as bibliotecas usadas pela aplicação em um diretório específico;
- Mover os descritores e arquivos de configuração aos locais apropriados;
- Gerar um WAR ou EAR;
- Implantar o WAR ou EAR no Container;
- (Re)iniciar o Container (caso este não suporte hot deployment).
Veja que fazer o deployment de uma aplicação não é assim tão complicado, mas perceba que você terá de fazer essas mesmas tarefas sempre que você precisar rodar a aplicação. Trata-se de algo completamente mecânico e chato.
Neste exemplo, você teria duas opções: continuar fazendo isso manualmente até o dia da sua morte, ou criar um script para automatizar essa tarefa para você. O que você prefere?
Cuidado com os extremos!
Uma coisa importante é não levar essa lição ao extremo (como qualquer outra coisa). Querer achar uma solução mais simples para todos os problemas nem sempre é a melhor saída. Já dizia Albert Einstein:
Faça as coisas o mais simples que você puder, porém não se restrinja às mais simples.
Lição número 5: não tente adivinhar o futuro
Um dos meus professores de faculdade nos contou sobre uma experiência que ele havia feito com outra classe. Ele propôs que os alunos, em grupos, implementassem um sistema qualquer, cuja única restrição era, ao se partir para a fase de implementação, o código deveria refletir exatamente o modelo criado no início do processo, e este, por sua vez, não poderia mais ser alterado! Bem no estilo Waterfall mesmo. Loucura, não? Embora a tarefa não parecesse difícil para os alunos em um primeiro momento, nem é preciso dizer o quanto estavam enganados em relação a isso.
Adivinhem o resultado da experiência? Sistemas que pareciam “faltar uma perna”. Quer dizer, todos os sistemas funcionavam, mas não resolviam os problemas que deveriam. O interessante é que todos os projetos acabaram falhando, apesar de a maioria desses alunos terem escolhido desenvolver algo que conheciam bem (por terem experiências profissionais e tudo mais).
Graças às metodologias ágeis, essa coisa de se tentar prever o futuro está perdendo a força. Acho que todo mundo já entendeu que isso não serve de nada, que mudanças são inevitáveis. O melhor a fazer, então, é saber reagir frente a elas.
Lição 6: aprenda que sempre…
… há algo a aprender.
Gostei da parte sobre os testes automatizados. Ultimamente, tenho brigado para começar a implementar isso na empresa onde trabalho.
Em breve, começaremos o desenvolvimento de um sistema novo, do zero (obrigatoriamente, porque toda a estrutura do negócio vai mudar). Basicamente, é um sistema de cadastro de requisições que têm um conjunto bastante complexo de regras que compõem o valor da requisição. Alguns me dizem que “tem muitas regras, não tem como testar”. Porém, isso é uma *vantagem* dos testes automatizados. Você cria os testes, e eles garantem que o sistema cumpre um conjunto de regras, e que as alterações subsequentes não quebrarão alguma regra. O problema é lutar contra a cultura. Eu mesmo tive que lutar bastante contra a minha mania de achar que um teste automatizado é perda de tempo.
Olá João, concordo plenamente!
Se pegarmos como exemplo uma aplicação que só lê e grava dados num banco MySQL, sem nenhuma lógica de negócio, realmente fica muito fácil de se criar testes automatizados. O problema é que o valor que os testes agregam em sistemas assim é muitas vezes insignificante, o que acaba por desestimular uma empresa e/ou seus desenvolvedores a implementá-los.
Só que, quando se trata de sistemas realmente complexos, com muitas regras e validações, a maioria das pessoas acaba não levando em conta que é justamente nesse tipo de sistema que os testes automatizados costumam agregar mais valor, pois, como você mesmo disse, torna-se possível garantir (ou ao menos minimizar muito) os riscos de que cálculos ou regras importantes sejam quebradas acidentalmente.
Obrigado pelo comentário!