Código legado. O Horror!!

Cthulhu

Cthulhu

Existem, na comunidade, várias definições para o que seria código legado: todo código que você escreveu é legado; o código que outra pessoa escreveu é legado; o código ruim, de difícil manutenção é legado. Não importando a definição, podemos identificar uma característica primordial, o código legado é aquele difícil de mudar. E isso, geralmente é um terror para os desenvolvedores.

Quem escreveu o código, não necessariamente é um desenvolvedor ruim. Precisamos entender o contexto. Mas gosto da idéia de que a qualidade do código é um espelho do local onde ele foi desenvolvido. Se o código é ruim, a empresa pode apresentar sintomas graves de uma péssima gestão, principalmente em se tratando de comunicação inter-departamentos, já que normalmente as demandas não vem de TI. Nesse cenário, será bem difícil trabalhar em melhorias do código legado, enquanto não houver melhorias nos processos internos da empresa e um alinhamento de negócios com TI. Mas, ainda assim, agente tenta.

Já tive a oportunidade de trabalhar em muitos projetos legados. Sei que você também tem muita história pra contar a respeito de suas aventuras nas matas densas do legado. Você irá achar que, a partir daqui, eu estarei exagerando e querendo causar polêmica, mas não, o que vou contar é a mais pura verdade. Já trabalhei com código que dava vontade de vomitar só de olhar pra ele. hahaha. Não é pra tanto, mas já chegou perto disso.

Trabalhei em projetos legados bem complexos e com uma base de código bem ‘doente’, normalmente monstros monolíticos. Mas apesar disso, os softwares eram, e são até hoje, bastante elogiados. Assim como para seres humanos o que importa é o caráter e não a aparência, para software o que importa é o valor que ele entrega ao cliente. Mas isso é só um lado da moeda. Aqueles sistemas tão elogiados de que falei faziam bem o seu trabalho, mas apresentavam uma base de código extremamente difícil de gerenciar, o que dificultava a criação de novas funcionalidades e melhorias.

Outro fator que considero importante em um cenário como esse é a satisfação com o seu trabalho. É bem ruim ter que trabalhar todo dia fazendo remendos em uma estrutura ‘capenga’. É desmotivante, é cansativo e isso pode aumentar a rotatividade em sua empresa. E a alta rotatividade de desenvolvedores é um fator que contribui a gerar mais código ruim. É extremamente frustrante quando, devido a algum fator, ou vários fatores, não é possível melhorar a base de código.

Você pode achar que isso tudo é muito mimimi. Mas que fique claro que o maior prejudicado nessa história é a empresa que mantém o código. Se não for possível fazer melhorias, a empresa terá que conviver com os custos de se manter um legado ruim. A escolha é sua, ficar no mimimi ou partir pro ataque.

Enfrentando o monstro

Uma outra definição para sistemas legados, e essa foi a que mais gostei, nos diz que sistema legado é todo sistema que não está coberto por uma suíte de testes, ou seja, você estará construindo código legado toda vez que você escreve código sem testes. Interessante. Então fica a dica: sempre que for refatorar, escreva um teste, se bem que refatorar sem testar não faz sentido. Mas quando se tem uma base de código muito grande e complexa, isso não é tão fácil. Além disso, você irá encontrar no legado muitos recursos que nem são utilizados. Puro lixo.

O código legado tem a característica de ser bastante acoplado. Então uma forma de começar a refatoração é isolando as dependências e trabalhar em cima delas. Inicialmente pode ser inviável criar testes de unidade, mas é possível criar testes de aceitação e isso pode ser um bom começo.

Outra forma seria o que Martin Fowler chama de estrangular a aplicação. Você vai criando um novo sistema nas bordas do antigo, muitas vezes de forma transparente ao usuário. Não é uma tarefa tão simples, principalmente se precisar rever a modelagem do banco de dados. É um processo bastante demorado e pode durar anos, mas é eficaz. É importante estar alinhado com a gerência e que ela saiba o que você está fazendo, mas algumas vezes é mais fácil pedir perdão do que permissão, essa é a verdade.

Um outro cenário é reescrever tudo. Isso pode ser possível, mas tem que ser analisado com cautela. Dependendo do contexto pode ser a melhor solução, ou pode ser catastrófico. Tive a experiência de trabalhar num sistema legado que estava até bem escrito. O problema é que ele havia sido desenvolvido com um framework caseiro que possuia geradores de código e o dono não estava mais na empresa. Não havia muito o que fazer. Resolvemos reescrever tudo com um novo framework que estávamos testando na época (um framework bastante conhecido e usado pela comunidade). Um fator que ajudou bastante é que a modelagem de dados do sistema legado estava impecável, então reaproveitamos o banco.

Uma dica final é sempre seguir padrões. Estar atento ao que a comunidade recomenda e participar de eventos é importante. Você pode estar sendo o vilão escrevendo o legado de amanhã. Poupe sua mãe de xingamentos. Lembre-se que o código não é seu, mesmo que você o tenha escrito.

referências:

Michael Feathers, Working Effectively with Legacy Code, Prentice Hall, 2005.

Martin Fowler, “StranglerApplication”, http://www.martinfowler.com/bliki/StranglerApplication.html.

Mary e Tom Poppendieck, Implementando o Desenvolvimento Lean de Software

Anúncios

Criando abstrações com repositórios

Ainda hoje vejo muita gente utilizando o padrão DAO em novos projetos, mesmo usando novas tecnologias de persistência como Hibernate e JPA. Não vou entrar em discussões sobre o padrão DAO estar morto ou não. O fato é que em projetos legados, principalmente os que usam JDBC, o DAO está bem vivo. Mas e em projetos que usam ORM? É necessário usar DAOs e os famosos DAOs Genéricos?

Usar DAOs, hoje, pode ser algo extremamente redundante. Pois as ferramentas de ORM já implementam o padrão DAO. Por que você implementaria novamente? Mas, claro, mesmo em projetos novos, pode ser que apareça o cenário para se utilizar o DAO. É uma decisão de design que não deve se basear em senso comum. Projetar software não é uma tarefa trivial, porém, muitos ainda se baseiam em receitas de bolo e não param para trabalhar no design da aplicação. Mas nós aprendemos a criar DAOs na camada de persistência. Como fazer de outra maneira?

Lembre-se que o DAO existe para abstrair a camada de persistência e criar uma interface para o acesso aos dados. Hoje isso já está pronto e quem o faz é a ferramenta de ORM. Em determinados cenários não vejo problema em injetar o EntityManager do JPA direto no Controller. Algumas vezes não existe motivo para se criar uma camada desnecessária. No entanto, é mais usual que tenhamos que fazer consultas usando HQL ou JPA QL, ou que seja necessário o controle manual de  transações. Esse tipo de coisa jamais deve ficar na camada de aplicação. Existe uma forma mais elegante de abstrair a camada de persistência. Criando repositórios.

Repository é um conceito do Domain Drive Design. A camada de domínio de uma aplicação é onde se encontram as regras de negócio, logo, o que ocorre nessa camada está na ‘boca do povo’, ou seja, está nos brainstorms, reuniões de levantamento de requisitos, e nas conversas entre desenvolvedores e pessoal de negócios. A camada de domínio precisa ser escrita no que chamamos de linguagem ubíquoa, a linguagem comum entre devs e negócios.

O que normalmente encontramos dentro de controllers é algo assim:


BookDao dao = new BookDAO();
Book book = dao.findById(id);

Primeiro, se estivermos usando ORM, o DAO é desnecessário. Segundo, é feio de ler. Não tem nada a ver com uma liguagem de domínio. É muito comum as pessoas somente trocarem o nome DAO por Repository e modificar a interface método.

/**

*/
public class BookRepository {

  public final EntityManager entityManager;

  public BookRepository(EntityManager entityManager){
    this.entityManager = entityManager;
  }

  public void add(Book book){
    this.entityManager.merge(book);
  }

  public Book findBook(String id){
    return this.entityManager.find(Book.class, id);
  }

}

A chamada fica assim:

  BookRepository bookRepository = new BookRepository();
  Book book = bookRepository.findBook(id);

Bem melhor, mas ainda temos alguns problemas. Muitos desenvolvedores costumam usar o termo repository no nome das classes, assim como fazem com o dao. Devemos usar nomes significativos para nossas classes, métodos e variáveis, principalmente em se tratando de classes de domínio. Que tal se mudarmos o nome da nossa classe para Library?

  Library library = new Library();
  Book book = library.findBook(id);

Uma coleção de livros, geralmente é uma biblioteca, não é verdade? Mas ainda há um problema. O alto acoplamento com a implementação do repositório. Em se tratando de uma classe de repositório, ela poderá ser usada em vários locais, tanto em controllers, quanto em classes de serviços e até mesmo por classes de modelo. Mesmo usando injeção de dependência, ainda ficamos acoplados com a camada de persistência. Então podemos criar uma interface.


public interface Library{

  void add(Book book);

  List<Book> allBooks();

  Book findBook(String id);

}

E fazemos com que a classe repository implemente a interface Library.


public class BookRepository implements Library {

  public final EntityManager entityManager;

  public BookRepository(EntityManager entityManager){
    this.entityManager = entityManager;
  }

  public void add(Book book){
    this.entityManager.merge(book);
  }

  public List<Book> allBooks(){
    return this.entityManager.createQuery("select b from Book b", Book.class).getResultList();
  }

  public Book findBook(String id){
    return this.entityManager.find(Book.class, id);
  }

}

Agora através de injeção de dependência, as classes podem receber a interface Library como dependência, e não mais depender de sua implementação. Podemos criar uma camada de persistência e inserir as classes *Repository nela enquanto que as interfaces ficam na camada de domínio. Em Domain Drive Design, as classes que implementam a persistência localizam-se na camada de infraestrutura enquanto que as interfaces dos repositórios podem ficar no mesmo pacote dos modelos, na camada de domínio.

Baixe aqui, um exemplo de aplicação modelada com DDD.

Seu projeto não tem testes? Pior pra ele

Olá pessoal! Esse é o meu segundo post aqui no blog do Tá Safo! e desta vez vou falar sobre testes e TDD que é um tema ainda pouco discutido pela região.

Bem, eu vejo em muitos lugares, inclusive em gerências de projetos por ai que uma tarefa é dividida nos seguintes estágios: A Fazer, Fazendo, Testando, Homologação e Pronto.

A primeira é quando a funcionalidade ainda está a espera de ser implementada, a segunda é quando alguma criatura começa a produzir a tarefa (que no nosso caso, é o código). Então, após ser implementado ele será testado para depois ser aceito pelo cliente e pronto, fim de papo.

Sempre que assisto a alguma palestra ou converso com alguém sobre o que este teste realmente quer dizer, ouço que é alguém dedicado a usar o software para saber se a funcionalidade feita está de acordo com o esperado e/ou funcionando.

Cruzes! Toda vez que escuto isso me dá um frio na espinha, sério. Primeiramente, existe uma pessoa dedicada para isso. Segundo, o teste é feito manualmente. Terceiro, o teste é feito DEPOIS que o código é escrito.

Óbvio, hehe, o terceiro ponto pareceu muito esquisito. Não tem como testar a função antes dela ter sido implementada. Quebremos, então, este paradigma.

Continuar lendo