O cliente pergunta: "Se adicionar esse recurso em um novo aplicativo levaria uma semana, por que aqui são necessárias três semanas?"
Essa é uma ótima pergunta.
E, muitas vezes, a resposta não é: "porque os programadores trabalham mais devagar".
O problema pode estar muito mais fundo - na arquitetura do sistema, em suas dependências, na forma de armazenar dados, na falta de testes, em decisões históricas e em mudanças sucessivas acrescentadas ao longo dos anos.
É justamente por isso que o custo do desenvolvimento de software não é ثابت.
A mesma funcionalidade pode custar um valor totalmente diferente em dois sistemas distintos.
O código não é avaliado apenas pelo número de funcionalidades
À primeira vista, a tarefa pode parecer banal.
"Vamos adicionar a possibilidade de exportar dados para o Excel."
Ou: "Vamos adicionar uma nova função de usuário."
Ou: "Vamos integrar o sistema ao nosso CRM."
O problema é que uma funcionalidade nunca existe em completo isolamento do restante do sistema.
Uma nova funcionalidade pode exigir mudanças em:
- banco de dados,
- API,
- backend,
- frontend,
- sistema de permissões,
- login,
- relatórios,
- integrações,
- testes,
- mecanismos de cache,
- documentação,
- processo de implantação.
Quanto mais conectado for o sistema, mais elementos precisam ser analisados antes da mudança.
O maior custo pode surgir antes de escrever a primeira linha de código
Em um sistema maduro, o programador não deve simplesmente começar a escrever.
Primeiro é preciso responder:
- Onde essa funcionalidade deve ser adicionada?
- Com quais módulos ela vai se comunicar?
- Quais dados ela utiliza?
- Os mecanismos de permissão existentes a cobrem?
- A mudança afetará outros processos?
- Quais testes precisam ser atualizados?
- A arquitetura atual sequer permite fazer isso corretamente?
Tudo isso faz parte do custo de construção da funcionalidade.
Por isso, em um sistema antigo, uma parte significativa do trabalho pode não ser a programação em si, mas reconhecer as dependências e limitações da solução existente.
Technical debt funciona como juros
Uma boa forma de pensar em technical debt é justamente o custo das mudanças posteriores.
Se uma determinada solução foi feita rapidamente no passado, isso pode ter sido totalmente justificado.
O problema surge quando uma solução temporária se torna um elemento permanente do sistema.
Surge uma nova funcionalidade.
Depois outra.
Aparece uma exceção.
Depois mais uma exceção.
A isso se soma uma integração, um contorno do problema, um processo manual e uma regra extra.
Depois de alguns anos, ninguém mais se lembra por que o sistema funciona exatamente dessa forma.
Mas cada nova mudança precisa levar em conta todas essas decisões históricas.
Martin Fowler descreve technical debt como o esforço adicional assumido ao modificar um sistema em razão de problemas com sua qualidade interna.
Assim, pode-se dizer: technical debt não precisa impedir o desenvolvimento imediatamente. Primeiro, ele faz com que cada mudança seguinte fique mais cara.
Sinal um: "aproveitando, também precisamos corrigir mais cinco coisas"
Esse é um dos sintomas mais característicos.
O cliente encomenda uma funcionalidade.
Durante a análise, descobre-se que, para implementá-la, é preciso:
- corrigir a estrutura da tabela,
- mudar a forma de autorização,
- atualizar a biblioteca,
- corrigir a API antiga,
- reescrever um trecho do frontend.
De repente, uma pequena funcionalidade deixa de ser pequena. Não porque o requisito seja complicado. Mas porque o sistema já não tem limites arquitetônicos adequados.
Sinal dois: uma mudança exige testar todo o sistema
Se uma pequena modificação exige um regresso manual completo, a organização está pagando pela falta de automação.
Com o crescimento do sistema, o número de combinações possíveis aumenta.
Sem o conjunto adequado de testes, fica cada vez mais difícil ter certeza de que o novo recurso não danificou o antigo.
Isso, por sua vez, gera cautela.
Os deploys são menos frequentes.
As mudanças são maiores.
O risco aumenta.
E implantações maiores são mais difíceis de diagnosticar em caso de problemas.
Cria-se um círculo vicioso.
Sinal três: "melhor não mexer nesse módulo"
Essa frase deveria acender uma luz de alerta.
Se um módulo específico se tornou uma área que a equipe evita porque seu comportamento é imprevisível, o sistema tem um problema sério de manutenção.
Pior ainda se apenas uma pessoa conhece seu funcionamento. Nesse caso, a empresa não tem apenas technical debt. Tem também knowledge risk.
A saída de um funcionário pode significar a perda do conhecimento necessário para evoluir o sistema com segurança.
Sinal quatro: toda funcionalidade exige exceções
Um sistema bem projetado deve ter regras previsíveis.
Se cada nova funcionalidade exige a criação de uma exceção especial, uma condição adicional ou um caminho individual, a arquitetura provavelmente está começando a limitar o crescimento.
Isso muitas vezes leva a um código que já não pode ser facilmente previsto.
E a falta de previsibilidade significa maior custo de análise, testes e manutenção.
É preciso reescrever tudo?
Não.
E aqui chegamos a uma distinção muito importante.
Technical debt não significa automaticamente a necessidade de um rewrite.
As soluções possíveis incluem:
Refatoração
Ou seja, melhorar a estrutura do código existente sem alterar seu comportamento de negócio.
Esse é um bom caminho quando o sistema ainda tem uma arquitetura razoável, mas trechos específicos são difíceis de manter.
Modernização de componentes selecionados
Não é preciso substituir todo o aplicativo.
Pode-se começar pelo módulo, integração ou camada mais problemática.
Migração gradual
Os novos elementos podem funcionar ao lado do sistema antigo, e as áreas seguintes são transferidas progressivamente.
Essa abordagem permite reduzir o risco de uma migração única. Na literatura sobre modernização de legacy, costuma-se usar justamente o desmembramento gradual da funcionalidade e a substituição de partes sucessivas do sistema.
Rewrite
Construir um novo sistema faz sentido quando a arquitetura atual é tão limitante que seguir modernizando não traz um retorno justificável.
Mas o rewrite deve ser uma decisão resultante de análise, e não uma reação à frustração da equipe.
Quando ainda não vale a pena investir na modernização?
A dívida técnica, por si só, não é motivo para parar o desenvolvimento. Todo sistema tem um certo nível de dívida técnica. Às vezes, quitá-la não faz sentido econômico.
Se a aplicação:
- funciona de forma estável,
- é segura,
- tem um número reduzido de mudanças,
- suporta um processo que não vai se desenvolver significativamente,
- não gera problemas operacionais,
pode ser racional deixá-la como está.
Não se trata de fazer com que todo sistema seja tecnologicamente perfeito.
Trata-se de fazer com que o nível de dívida seja uma decisão consciente.
Quando o custo da dívida se torna um problema de negócio?
Quando começa a afetar os resultados da empresa.
Por exemplo:
Uma nova funcionalidade deveria entrar no mercado em um mês, mas precisa de três.
A integração com um novo parceiro se arrasta, porque a API do sistema antigo não permite lidar facilmente com novos dados.
Uma pessoa-chave da equipe precisa participar de cada trabalho, porque só ela conhece o módulo antigo.
Cada implantação maior exige horas de testes de regressão.
O concorrente lança novas funcionalidades mais rapidamente, porque sua plataforma permite experimentar mais depressa.
Nesse momento, a technical debt deixa de ser um problema do departamento de TI.
Ela se torna um problema de negócio.
Como medir se a situação está piorando?
Não é preciso criar um sistema complicado de KPIs.
Vale a pena observar alguns indicadores simples:
Lead time - quanto tempo passa desde o início do trabalho em uma mudança até sua implantação.
Frequência de implantações - com que frequência a equipe consegue entregar mudanças com segurança.
Taxa de falha em mudanças - com que frequência as implantações causam problemas.
Tempo de recuperação - quão rápido é possível voltar ao funcionamento estável após uma falha.
Tempo de implementação da funcionalidade - se tarefas semelhantes exigem cada vez mais esforço.
Além disso, vale analisar o número de operações manuais, a cobertura de testes, a atualização das dependências e o tempo necessário para integrar um novo programador ao projeto.
Esses dados permitem ver se o problema é realmente técnico ou se pode decorrer do processo, dos requisitos ou da forma de organização do trabalho.
A pior solução é "mais uma correção rápida"
Se a equipe sabe que a arquitetura precisa de mudanças, mas adia o tema toda vez, o sistema pode entrar em espiral.
"Vamos fazer um workaround por enquanto."
"A refatoração faremos depois."
"Por enquanto basta."
"Na próxima release."
O problema é que a próxima release traz novos requisitos.
E cada workaround adicional aumenta o custo da próxima mudança.
Por isso, a decisão de quitar a technical debt deve fazer parte da estratégia de desenvolvimento do produto, e não ser uma reação casual a uma crise.
Uma boa aplicação não é aquela que nunca envelhece
Todo sistema vai mudar.
As tecnologias vão mudar.
Os clientes terão novas necessidades.
Novas integrações vão surgir.
A forma de trabalho da empresa vai mudar.
Por isso, o objetivo não deve ser criar uma aplicação que nunca precise ser modernizada. O objetivo deve ser criar uma arquitetura na qual a modernização seja possível sem parar o negócio. Isso é uma enorme diferença.
Porque o melhor sistema não é aquele que parece mais moderno no dia do lançamento. É aquele que, mesmo depois de alguns anos, permite à empresa reagir rapidamente às mudanças.
E, se cada nova funcionalidade custa cada vez mais, isso nem sempre significa que a funcionalidade é difícil.
Talvez o próprio sistema já tenha se tornado difícil.
