Durante minha carreira, trabalhei com vários projetos legados, cada um deles sofrendo várias falhas.
É claro que, muitas vezes, o principal problema era a baixa qualidade do software (falta de testes de unidade, recusa em usar os princípios do código limpo ...), mas também havia dificuldades, cuja origem eram as decisões arquiteturais tomadas no início do projeto ou mesmo durante o início do sistema corporativo. Na minha opinião, essa classe de problemas é a causa da maior dor para muitos projetos.
Em essência, melhorar o código é bastante direto, especialmente agora que o movimento de artesanato em software está promovendo boas práticas de equipe. No entanto, alterar as partes principais dos sistemas, as restrições impostas no início de seu ciclo de vida, é uma tarefa extremamente difícil.
Vou falar sobre vários tipos de soluções arquiteturais que encontrei, que podem se tornar um fardo real para as equipes envolvidas no suporte a esses sistemas.
Toda a sua empresa compartilha seu banco de dados
Este é talvez um dos problemas mais comuns que já vi. Quando vários aplicativos precisam usar dados compartilhados, por que não lhes damos acesso compartilhado a um banco de dados compartilhado? Afinal, duplicação é considerada má no desenvolvimento de software, certo? Bem, isso nem sempre é verdade, e especialmente quando se trata do banco de dados. Venkat Subramaniam colocou desta maneira: "O banco de dados é como uma escova de dentes: nunca deixe ninguém usá-lo." O que há de tão ruim em compartilhar um banco de dados? De fato, bastante ...
A primeira coisa que vem à mente é o acoplamento do modelo de dados. Imagine que dois aplicativos, A e B, processem dados em carros. O Apêndice A é usado pela equipe responsável pelo trabalho de reparo e, portanto, eles precisam armazenar muitas informações técnicas: sobre mecânica, avarias, histórico de intervenções no carro ... O Apêndice B é usado para definir tarefas para a equipe técnica, de modo que ele precise apenas de informações básicas sobre o carro, suficientes para sua identificação. Nesse caso, o uso da mesma estrutura de dados para ambos os programas não faz sentido: eles usam dados diferentes; portanto, cada um deve usar sua própria estrutura de dados. Isso é mais fácil porque o carro é fácil de identificar, portanto não há necessidade de uma referência geral.
O segundo problema também se deve a essa malha do modelo de dados. Imagine que o aplicativo B queira renomear o identificador do carro - dê um nome mais lógico em termos de área de assunto. Nesse caso, o aplicativo A também precisará ser atualizado - porque o nome da coluna mudou ... Como resultado, para não incomodar a equipe do aplicativo A, os desenvolvedores B começarão a duplicar informações em outra coluna, pois não podem alterar uma coluna existente ... É claro que os desenvolvedores A dirão que eles planejam alterar o nome da coluna no futuro para não armazenar as mesmas informações em duas colunas, mas todos sabemos: provavelmente isso nunca será feito ...
Tudo se torna ainda mais repugnante quando os aplicativos não apenas lêem dados de uma fonte, mas também os alteram! Quem é o proprietário dos dados neste caso? Em quem confiar? Como garantir a integridade dos dados? Isso é difícil mesmo quando os mesmos dados são alterados por diferentes partes do mesmo aplicativo, mas quando vários aplicativos diferentes o fazem, o problema se torna muito mais sério ...
O último dos casos que eu vi: dois aplicativos que compartilham a mesma estrutura de dados para armazenar informações sobre dois objetos de negócios, relativamente semelhantes, mas ao mesmo tempo diferentes o suficiente para complicar seriamente o entendimento de qual aplicativo os dados se referem. Ambos os aplicativos usaram a mesma tabela para modelar execuções no mercado financeiro, mas com diferentes níveis de agregação. Nada indicava que a tabela continha dados de dois tipos; portanto, tivemos que examinar outra tabela (pertencente ao segundo aplicativo) para determinar as linhas geradas por cada um dos programas ... Cada novo desenvolvedor que precisava trabalhar com essa tabela veio inevitavelmente no mesmo rake de todos os seus antecessores e usou dados incorretos (vulneráveis), com todos os riscos decorrentes para a empresa.
Seu sistema está vinculado ao software usado na empresa
Nem toda empresa pode desenvolver software que atenda a todas as necessidades de seus negócios. De fato, em muitos casos, isso seria uma invenção da bicicleta, pois as necessidades de muitas empresas são as mesmas e é fácil encontrar no mercado softwares que já possuam a funcionalidade necessária.
Em geral, geralmente é mais barato comprar um produto do que criá-lo. Mas, é claro, o produto que você acabou de comprar não funciona imediatamente com o software que você já usa, portanto, é necessário criar uma ponte entre os dois aplicativos (na maioria dos casos, proprietários). Você certamente desenvolverá suas próprias ferramentas para partes específicas do seu negócio e, desde que este software caro que você já tenha comprado tenha um modelo adequado, basta usar o banco de dados e adicionar seus dados às tabelas desse banco de dados ...
Vários anos se passam, uma dúzia de desenvolvedores ou equipes executam essas ações repetidamente, e então você está em um beco sem saída: você simplesmente não pode usar outro software se o desenvolvedor do software que você comprou o fechar, ou parar de dar suporte ao produto, ou se algum novo software for mais adequado às suas necessidades. Em alguns casos, você pode até ter dependências técnicas de software externo. Se o autor da solução de software que você está usando deseja que você use a versão da linguagem / estrutura / outra coisa que ele precisa, isso significa que a arquitetura do seu sistema não lhe pertence. Se você deseja vender uma nova versão para fornecer uma função que você definitivamente precisa, mas esta versão está sujeita a uma alteração nos requisitos técnicos, você será obrigado a atualizar sua pilha de tecnologias para atender às recomendações de outras pessoas. Eu sei como é, e você não gostaria que essa migração forçada fosse algo frequente ...
Trabalhei em um projeto em que os desenvolvedores do produto que usamos não queriam adicionar novos recursos a todos os nossos clientes, porque era muito difícil manter mudanças competitivas e várias versões atuais (cada cliente tinha sua própria versão com as funções necessárias apenas para ele). Então eles decidiram nos vender o SDK para podermos implementar nossa própria funcionalidade. Naturalmente, eles não forneceram documentação suficiente sobre como fazer isso e, além disso, fomos forçados a usar suas entidades comerciais, que tivemos que descompilar para entender sua estrutura - porque não tínhamos código fonte nem documentação ... Implementação a funcionalidade mais simples levou vários dias, e era quase impossível testá-la, pois tudo era muito complicado e até exigia conhecimento de uma linguagem de script que ninguém na equipe com uma pilha tecnológica já complexa conhecia ...
Malha forte entre vários aplicativos
Lembre-se do início dos anos 2000: como era agradável usar o Enterprise Java Beans (EJB) para lidar com chamadas remotas entre aplicativos em seu sistema de informação. Na época, isso poderia parecer uma boa ideia. Compartilhar seu código com outras equipes para evitar duplicação também parece bom. Sim, todas as equipes foram forçadas a distribuir seus aplicativos ao mesmo tempo para garantir que as dependências binárias não quebrassem, mas era divertido à noite - comer pizza com os colegas enquanto esperavam que o aplicativo fosse entregue por 2 horas, certo?
Bem, na verdade não foi tão divertido. E a impossibilidade de refatorar uma classe separada em seu próprio código - simplesmente porque outra pessoa na empresa gostou do seu código e decidiu usá-lo em seu aplicativo não testado - ainda é um prazer.
Depois de perceber como essas decisões iniciais são confusas, o esforço necessário para separar seu aplicativo do resto do mundo é enorme. Você vê, no sentido literal, levará anos para dividir seu projeto em vários componentes, para que outros aplicativos não possam mais usar o núcleo da sua área de assunto, seu cliente ou o mecanismo de cache; remover todas as chamadas para classes externas que levam a um forte vínculo com outros projetos; para substituir todas as chamadas EJB por APIs REST ... No entanto, para todos esses esforços, cada funcionário associado ao projeto será mais do que recompensado: o desenvolvimento e os testes serão simplificados, o processo de entrega será acelerado, pois agora não há mais necessidade de sincronizar seu trabalho com outra pessoa ; conceitos em seu próprio código ficarão melhor separados; o gerenciamento de dependências será simplificado, os problemas com dependências transitivas desaparecerão quando você importar um monte de dependências de outros aplicativos para o seu caminho de classe ... Todas essas alterações caras salvarão a vida da equipe e podem ser muito mais fáceis de implementar se você as tiver criado no início do projeto!
Você constrói seu projeto com base no projeto de outra pessoa
É provável que você não encontre esse problema, mas ele ainda pode ocorrer e, se isso acontecer, esse é o pior cenário, porque combina vários dos problemas discutidos anteriormente. Na verdade, me deparei com um semelhante em um dos primeiros projetos da minha carreira profissional.
Quando ingressei no projeto, disseram-me que o sistema corporativo estava completamente reescrito e que o trabalho no projeto começou apenas recentemente, dois meses atrás. Imagine minha surpresa quando vi um aplicativo da Web complexo com um módulo de administração completo, já implementou funcionalidades comerciais complexas e uma estrutura desenvolvida para escrever outros módulos. Logo descobri que quase tudo isso não foi escrito por minha equipe: para não começar do zero, foi decidido reutilizar o framework desenvolvido por outra empresa do nosso grupo. O problema era que essa estrutura não estava isolada do projeto para o qual foi desenvolvida. Portanto, nossa equipe simplesmente recebeu um arquivo com todo o código-fonte do projeto de outra empresa, incluindo a lógica de negócios, que nada tinha a ver com o próprio negócio. Para piorar, também herdamos deles o esquema do banco de dados e os próprios dados ...
Não foi fácil para um novato na equipe entender qual código se relaciona à estrutura, que se refere ao nosso projeto e se refere aos negócios de outra empresa. A equipe queria esclarecer essa bagunça, mas várias tentativas de fazer isso terminaram em graves erros de regressão devido a dependências entre as partes do código (o idioma não liga para chamá-lo de módulos porque havia apenas um módulo!) E, é claro, não há testes automatizados lá foi. Além disso, tivemos que abandonar a idéia de usar um servidor de aplicativos diferente, pois ele possuía código específico usado por outra empresa em todo o sistema, e isso tornava essa migração muito onerosa para nossa pequena equipe.
Em algum momento, queríamos adicionar alguns recursos interessantes à estrutura, mas nos disseram que isso já havia sido feito em outra empresa. Por isso, fomos convidados a mesclar nossa versão atual com a versão atual do código de outra empresa ... A equipe conseguiu evitar esse pesadelo simplesmente redirecionando (escolher) alguns dos commits associados à nova função, mas ainda era muito complicado e complicado em comparação com isso o que precisávamos ...
Conseguimos terminar este projeto, mas sua qualidade foi uma verdadeira dor. Pelo menos 40% do código e o conteúdo do banco de dados não foram utilizados como lastro e a remoção desse código morto não era uma prioridade. Espero que a equipe tenha finalmente tido a oportunidade de separar seu próprio código - não sei, deixei a equipe antes que isso acontecesse!
Toda a sua lógica de negócios é armazenada dentro de um sistema de gerenciamento de regras
Colocar parte da lógica de negócios em um sistema de gerenciamento de regras é uma prática comum. Isso, por exemplo, é útil se algumas das regras de negócios precisarem ser atualizadas com freqüência, e o processo de entrega do seu aplicativo monolítico exigir uma longa fase de teste antes que você possa verificar o candidato à liberação, e isso impossibilitará a configuração de algumas de suas "variáveis voláteis". "Regras. Embora prefira uma abordagem em que todas as regras da área de assunto estejam no código-fonte, posso entender que, em algumas situações, um sistema de gerenciamento de regras pode ser útil.
No entanto, me deparei com um aplicativo em que quase TODAS as lógicas de negócios estavam no sistema de gerenciamento de regras e, às vezes, as regras eram geradas com base em um arquivo do Excel! Além disso, as regras não deveriam ser alteradas com muita frequência, pois o projeto era, em geral, um simples pacote ETL . O projeto Java, no qual tudo isso foi baseado, consistiu apenas em detalhes técnicos sobre a estrutura de processamento em lote e pura leitura / gravação dos sistemas de origem e destino, sem nenhuma conexão com a área de assunto.
Como resultado, todas as regras foram escritas em um idioma especial que ninguém realmente conhecia bem na equipe, difícil de escrever (nossos IDEs não a apoiavam) e praticamente impossível de depurar e testar. Quando uma nova regra ou uma alteração em uma existente era solicitada, a maioria dos desenvolvedores das equipes simplesmente copiava a regra existente, o que levava a arquivos completamente idênticos, com exceção de uma alteração específica (geralmente a única alteração era o campo ao qual a regra se aplicava).
Se isso já parece um problema, então aqui está outra coisa: não havia absolutamente nenhuma pista sobre seu objetivo em nenhuma regra. As regras foram nomeadas como Regra1, Regra2 e assim por diante, com um total de mais de 100! E, basicamente, cada regra consistia em verificações e atribuição de valores codificados, sem termos de domínio. Mesmo o nome do projeto não esclareceu o objetivo de todo o ETL como um todo.
Conclusão
Como o tio Bob explicou em seu livro “Arquitetura limpa”, ao pensar sobre a arquitetura de seu projeto, algumas decisões devem ser adiadas até que realmente precisamos fazer uma escolha, sem a qual não podemos continuar agregando valor ao nosso produto (como escolher um banco de dados por exemplo). Outras decisões devem ser tomadas muito cedo - não espere até que as coisas piorem. Felizmente, esse tipo de decisão crítica é fácil de identificar porque é o que pode ser chamado de "arquitetura com alma": quando você pensa sobre essas idéias, não vê nada de bom nelas - apenas um problema que, mais cedo ou mais tarde, voltará e irá assombrar você. Infelizmente, ao trabalhar com projetos legados, esse tipo de carga geralmente está profundamente envolvido no código, o que torna muito caro eliminá-lo.
Mas não devemos ter medo. Sim, limpar a bagunça que se acumulou ao longo dos anos e até décadas não é uma tarefa fácil, mas como desenvolvedores profissionais de software, simplesmente não podemos permitir que esses problemas continuem apodrecendo e matando a motivação dos desenvolvedores, a confiança dos clientes e nossa capacidade de beneficiá-los, incorporados ao nosso produto.
É claro que todo tipo de carga arquitetônica que descrevi pode ser eliminado de várias maneiras - não há uma bala de prata para resolver qualquer um desses problemas. No entanto, tenho certeza de que qualquer equipe pode inventar algo para finalmente se livrar desse fardo. Então, vamos enfrentar nossos problemas juntos e começar a colocar as coisas em ordem!