Reescrever ou não reescrever, eis a questão

Introdução

Quantas vezes já nos deparamos com um software legado e pensamos: é melhor jogar tudo fora e fazer de novo. É uma triste realidade, mas tem horas que com o nosso próprio código, temos esse sentimento. Quando falamos daquele sistema legado de uns 10 anos de idade, é pior ainda.

Nunca sabemos quando vamos mexer num lugar e quebrar outro imprevisível, ou ainda quantos pontos de entrada com regras de negócio diferentes temos para a mesma entidade, ou ainda impactos de performance. Tem ainda aquele bug que o cliente já se acostumou tanto com ele que se corrigimos, ele reclama.

Por causa desse sentimento, acabamos chegando à conclusão que se começarmos de novo, vamos ter sucesso.

Será que é verdade?

O que torna um software impossível de se manter?

Existem vários fatores, mas uma verdade é inquestionável: Todos os softwares começam bem, e dali algum tempo viram legados que ninguém quer mexer. Por que?

  • Falta de padrões: Raramente começamos um software com padrões arquiteturais bem definidos, sobre como evoluir o software, como nomear variáveis, métodos. Pior ainda, raramente conseguimos disseminar isso e tornar isso parte da cultura das equipes, que inevitavelmente tem várias baixas durante os projetos.
  • Falta de definição de requisitos: O tempo investido no levantamento de requisitos geralmente é incondizente com o projeto. É necessário estressar muito o levantamento de requisitos, documentar muito bem todos os critérios necessários para conseguir um produto final de qualidade.
  • Falta de processos: A gestão do ciclo de vida do software é muito importante. Processo de liberação de versão, gestão de configuração e versões, testes, homologação, deploy, integração contínua, etc.

O outro lado da moeda.

É muito fácil falar em falta de padrões, de requisitos e de processos. Essas reclamações são uma constante quando conversamos com profissionais da área de desenvolvimento de software.

Mas será que se tivéssemos acertado tudo, ainda estaríamos no caminho do software perfeito? Eu particularmente acredito que não, e as razões são:

  • Os padrões mudam: O padrão arquitetural ideal de hoje, provavelmente não será o padrão arquitetural ideal de amanhã. As tecnologias mudam, os padrões mudam.
  • Os requisitos mudam: Raramente o usuário conhece os requisitos. Geralmente os requisitos vêm de vários usuários, áreas, empresas, organizações. Por mais que se estresse o levantamento, é impossível prever 100% das situações. O processo minimiza o retrabalho, mas não elimina. Novamente, tem o fator evolução. Os requisitos que hoje atendem ao negócio de hoje, amanhã podem não atender.
  • Os processos mudam: Antigamente era tolerável colocar uma ferramenta de sincronização para atualizar o schema de um banco de dados em produção. Hoje em dia as equipes de desenvolvimento estão proibidas de colocar a mão em servidores de produção. Antigamente aceitava-se uma compilação proveniente da máquina do desenvolvedor, hoje em dia não. Em resumo, os processos evoluem.

Cada vez que as tecnologias evoluírem vamos reescrever os softwares, mesmo que as linguagens sejam exatamente as mesmas?

De volta ao mérito da questão: Reescrever ou não?

Pergunta difícil, né? Eu vou tentar respondê-la com uma proposição lógica. Se hoje o seu legado está tentando sair de um nível de maturidade caótico e não consegue, quando o novo software se tornar um legado ele vai conseguir?

Pra mim a resposta a maioria das vezes é não.

Se a organização não tem maturidade para conseguir fazer o produto evoluir de forma consistente, estável, sem autodestruir a base do produto, se ela não consegue manter uma documentação andando junto do produto atual, porque conseguiria fazer tudo isso num software novo?

A verdade é que a maioria dos problemas está nos processos. Nas pequenas decisões de implementação “ótima”, “média” ou “quick ‘n’ dirty”, o “quick ‘n’ dirty” acaba sempre ganhando, por causa do patrocínio do cliente, que sempre quer a coisa rápida e funcional para ele. As dificuldades de gestão do produto e de manutenção de código não são problema dele (no fundo são, mas ele não percebe diretamente). Mas sempre essas pequenas decisões acabam carregando o custo intangível do produto se tornar caótico a longo prazo. Esse custo só se torna tangível, quando surge a máxima “é melhor jogar tudo fora e fazer de novo”. Aí é possível sentir o real custo de tornar um software caótico.

Pra mim o único momento em que “reescrever” é uma opção é quando se trata de uma troca de tecnologia completa. As vezes mesmo uma troca parcial de tecnologia (um componente, por exemplo) consegue-se resolver através de processos. No momento em que nada se aproveita, ou seja, a linguagem é diferente, a framework é diferente, aí não tem jeito. É necessário reescrever. Mesmo assim, se a base do produto está boa, é possível fazer um porte diretamente a partir do código fonte, sem mudar conceitualmente o produto e aproveitando todo o trabalho de levantamento de requisitos.

O paradoxo do “eu não sei o que tem lá dentro”

Muitas vezes essa é a grande justificativa para se reescrever um software inteiro. “Eu não sei o que tem lá dentro”, “eu não sei como esse software se comporta”. Sim, estou falando de níveis de caos elevados.

Parte-se do princípio que reescrevendo o software todo, passa-se a conhecer o que está lá dentro. Mas para conhecer o software novo, você vai desprezar toda a experiência, todo o conhecimento já levantado e funcionando no software atual?

Numa das minhas experiências profissionais, tive a oportunidade de trabalhar num projeto de “reescrever” um produto. Segregaram-se as equipes em “novo e atual” e começou-se um levantamento de requisitos do zero. Uma equipe mantinha o produto atual e a outra começava o novo. Existe todo um contexto que impede que a afirmação possa ser científica do tipo “sempre vai dar errado”. Mas nessa experiência, o produto novo nunca atingiu a mesma maturidade do atual, mesmo porque se o atual para de evoluir, raramente a empresa consegue atrair novos clientes, conseqüentemente dinheiro para manter todos os projetos.

Coloca-se o novo produto na frente do cliente e obtém-se o feedback: “E aquele negocinho que tinha no outro?”.

Em resumo, se a motivação para você fazer um novo produto é não conhecer o que já possui dentro de casa, a chance de acertar é bem pequena.

Se reescrever não é a saída, qual é a saída?

É muito difícil formar opinião sobre esse assunto. Acho que atualmente a minha idéia vai mais de encontro com reforçar os processos para fazer o produto atual amadurecer de forma consistente. Ou em outras palavras, criar meios para consertar o avião com ele voando.

Acredito que esse objetivo possa ser conquistado, com os seguintes passos:

  • Estabelecer uma boa política de gestão de configuração e versões. Isso vai permitir que se isole desenvolvimento de alta complexidade, que mexe estruturalmente no produto, de manutenções triviais. Gera meios para o produto se modificar consistentemente.
  • Estabelecer uma documentação de produto. Conseguir um processo que mantenha uma documentação atualizada do que se tem no produto.
  • Gestão de projetos. Cada evolução estrutural do produto é um projeto e deve ser gerido como tal. É muito difícil tornar os benefícios tangíveis, por isso é muito difícil justificar esse tipo de projeto. Os projetos que estão associados com entrada de clientes e fluxo financeiro geralmente ganham na prioridade.
  • Criar processos de testes consistentes. Testes automatizados ajudam muito na evolução do produto, pois sabe-se quando uma nova feature não passa num critério de aceitação definido em versões anteriores.

Em resumo, a estratégia que me parece mais factível é criar um andaime de processos e boas práticas ao lado do produto e à medida que ele começar a tender a ser melhor e mais estável, é um indicador que a organização ganhou a maturidade suficiente para tal. Mesmo que esse produto venha a ser descontinuado, com o aprendizado institucional destes processos é possível construir um novo e ter sucesso. A parte ruim é que tudo isso é intangível.

Dentro do código, existem milhares de pequenas situações, incluídas via bug fixes ou pequenos desenvolvimentos que são extremamente complexos de serem mapeados, mas as vezes representam features chave para os usuários finais. Raramente mapeamos corretamente esses requisitos em novos produtos.

Em resumo, a resposta está nos processos e no bom gerenciamento do ciclo de desenvolvimento.

O “pulo do gato” pra mim está em conseguir dar visibilidade dos ganhos obtidos com projetos de estruturação e que permitem a entrada de novas features sem grandes dificuldades. Exemplo: É sempre muito complicado colocar novas funcionalidades nos pedidos, pois existem lógicas separadas para entrada do pedido por integração via arquivo, por web service ou cadastrado pelo usuário. Não existe um ponto central.

Em resumo, viabilizar um projeto para unificar as três estruturas é muito difícil, pois não permite ganhos financeiros imediatos. Porém, permite que a funcionalidade ganhe estabilidade e projetos futuros sejam muito menos “destrutivos” e suscetíveis a erros. É muito difícil as organizações apostarem nesse tipo e projeto, e pra mim é onde acontecem os grandes erros. Insiste-se no modelo que dá ganhos imediatos e o software deteriora-se por completo no decorrer do tempo, causando perda de clientes por constante insatisfação com a qualidade do software. Até o momento que se chega à máxima: “não vale a pena consertar, tem que fazer de novo”. Nesse ponto faz-se um “go-to” para o início do artigo e volta-se ao círculo vicioso, com a diferença de que todo esse custo “intangível” de deteriorar o produto, começa a se tornar tangível.

Exercitando o modelo

Vamos tornar essa idéia mais prática (quem já acostumou com meus artigos e me conhece pessoalmente sabe que eu sou prático!). Partimos do princípio que temos um sistema de 10 anos de idade, escrito com sucessivas repetições do anti-pattern “magic-push-button”, ou seja, com botões que contém toda a lógica de negócio, acesso a dados, tudo dentro dele. Aí percebemos que além do problema sério de manutenção, precisamos que esse software torne-se multi banco de dados.

O primeiro passo é pensar em como resolver o problema no cenário ideal, construindo um software novo. Parte-se de um pattern em 3 camadas, com apresentação, lógica de negócio e acesso a dados, com a camada de acesso a dados abstraindo o banco de dados.

O próximo passo é fazer uma prova de conceito, com o pattern acessando os dois bancos, estressando todas as formas possíveis que podem gerar problemas: Stored procedures passam a ser indesejadas, pois precisa-se desenvolvê-las para cada um dos bancos de dados. Tipos de dados precisam ser padronizados e podem gerar sérios problemas no modelo de dados, queries que retornam múltiplos datasets tem incompatibilidades, etc.

Uma vez levantados os pontos que precisam ser padronizados, chega-se ao padrão ideal, ou o ponto onde queremos chegar. A partir daqui, partimos para os projetos.

Primeiro projeto para a próxima versão: Converter as stored procedures para a camada de acesso a dados da aplicação. Óbvio que não dá para fazer isso sem testar inputs e outputs. Cada procedure precisa ser mapeada, com entradas e saídas e devidamente testadas, para saber se o “porte” do código tem o mesmo comportamento e não vai gerar problemas na versão atual.

Aí já surgem as vertentes que defendem o pensamento: Mas se eu tiver que fazer testes em todas as procedures, fica inviável.

E porque a idéia de reescrever o software todo parece mais viável que isso? Para que o novo software não se torne um novo legado daqui a cinco anos, não deveríamos ter um processo para isso?

E quando no futuro, esse software precisar de evolução, não passaremos pelo mesmo problema?

A pergunta é mais simples. Se não existe maturidade para evoluir o produto atual, existe maturidade para escrever um novo sem repetir os mesmos erros?

Uma vez que se estabeleça o meio de realizar os testes, tem-se dois ganhos para próxima versão:

  • Procedures portadas para a aplicação, abrindo o caminho para multi-banco.
  • Processo de teste automatizado estabelecido e vivo na próxima versão.

Na próxima iteração, deve-se trocar os tipos de dados para compatibilizar os bancos de dados. Agora não precisamos mais nos preocupar com os casos de teste. Já temos o processo e precisamos somente repetí-lo para a próxima versão.

Conclusão

Os legados impossíveis de se manter surgem da dificuldade com processos de engenharia de software. São processos caros, complexos e que dependem de pessoas com especialidades muito diferentes.

O grande problema é que raramente temos oportunidade de começar um produto novo. Quase tudo é legado. E não poderia ser diferente.

Quando estava lendo um livro sobre a API do Windows, talvez o maior “legado” (não de forma pejorativa) da história, percebi que os headers da API do Windows são praticamente os mesmos desde o Windows 3.1. Hoje estamos no Windows 7. Temos praticamente o mesmo software evoluindo a 20 anos de forma consistente, cada vez mais estável e com mais features, tudo em cima da mesma “casca” da API. Será que o Windows foi inteiro “reescrito”? De uma vez só?

O mesmo observamos em várias ferramentas, compiladores. Vemos um copyright “199x-2010”, indicando que é o mesmo produto, sofrendo sucessivas evoluções. Duvido que eles joguem todo o código fora a cada versão. Reescrevem partes problemáticas, aperfeiçoam outras, mas o conjunto é o mesmo.

Após alguma experiência que já tive com desenvolvimento de software, sempre penso comigo mesmo: Que ações estou tomando para não desenvolver um novo legado (agora de forma pejorativa)?

Reescrever o software gera aquela sensação de confiança no que se está fazendo. Porque você participou de todo o processo e conhece todo o código que está ali dentro. Porém, à medida que a equipe vai aumentando, os requisitos vão se complicando, naturalmente chega-se ao mesmo estágio de evolução da versão anterior. Porque a sensação de controle começa a diminuir. Simplesmente porque o método não mudou.

Aplicando sempre o mesmo método, você acredita que pode obter resultados diferentes?

Advertisement

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s