Software relevante evolui! Seja para atender novas demandas do negócio, seja para incorporar novidades tecnológicas, adaptações são sempre necessárias. Enquanto isso, é fato que modificar software fica, geralmente, mais difícil com o tempo.
Apesar de nossos melhores esforços, o software se torna mais difícil de alterar com o tempo. Por uma variedade de razões, as partes que compõem os sistemas de software desafiam a modificação fácil, tornando-se mais frágeis e intratáveis com o tempo. Mudanças em projetos de software são geralmente conduzidas pela reavaliação da funcionalidade e/ou escopo. (Ford, Parsons e Kua)
As leis de Lehman
Ao longo de décadas, Meir Lehman e László Bélády formularam, propuseram e aprimoraram diversas “leis” que, alegadamente, governam a evolução de sistemas de software. Essas leis ficaram conhecidas como “Leis de Lehman”.
As “Leis de Lehman” descrevem e ajudam a entender o sensível equilíbrio entre as motivações para a evolução de um software, por adaptações, e as causas para o aumento da complexidade (em consequência, do lead time para atender demandas do negócio) ao longo do tempo.As duas primeiras “leis” formuladas por eles, em 1974, foram a lei da “mudança contínua” e a lei da “complexidade crescente”. A primeira diz que, ao longo do tempo, um sistema de software precisa ser continuamente adaptado, recebendo novas “adaptações”, para se manter relevante e satisfatório. A segunda aponta que, enquanto essas “adaptações” são feitas, o software se torna mais complexo, exceto quando existam esforços explicitamente direcionados para mitigar essa complexidade.
As duas leis, deliciosamente evidentes e conflitantes, levam a alguns desdobramentos preocupantes:
- Projetos de software bem sucedidos estão “condenados” a demandarem trabalho para mitigar a a complexidade.
- Times extremamente eficazes, mas exclusivamente focados em atender as demandas de negócio se tornam, eventualmente, menos produtivos, deteriorando sua capacidade de fazer entregas e, consequentemente, sua eficácia.2Você concorda?x
- O esforço para combater a complexidade é imperativo para manter o software relevante no médio/longo prazo.
- Times de negócio que não se sensibilizam para a necessidade de reduzir a complexidade do software, priorizando somente inclusão de features, se condenam a ter suas demandas atendidas em prazos cada vez maiores.2Você concorda?x
Mudanças tecnológicas acontecem rapidamente e têm impactos imprevisíveis
A emergência de práticas como a utilização de contêiners, aceleradas por tecnologias como Docker, por exemplo, impactam a indústria de forma definitiva e, para os menos “antenados”, parece surgir de uma hora para outra.
Frameworks Javascript parecem crescer em popularidade e se tornarem obsoletas em ciclos, muitas vezes, menores do que os necessários para o desenvolvimentos de alguns grandes sistemas. Em poucos anos, vimos o declínio de jQuery; a ascensão e a “morte” de AngularJS; a “provocação” de Backbone e Mustache; o progresso com Angular, React e Vue; propostas ousadas multiplataforma como Flutter e Electron; etc. É quase impossível dizer qual será a tecnologia dominante para elaboração de frontends de aqui dois anos.
Adicionar evolucionabilidade como uma característica arquitetônica implica proteger as outras características conforme o sistema evolui. Por exemplo, se um arquiteto projetou uma arquitetura para escalabilidade, ele não sabe o que essa característica degradar à medida que o sistema evolui. Assim, a evolucionabilidade é uma meta-característica, um invólucro arquitetônico que protege todas as outras características arquitetônicas. (Ford, Parsons e Kua)
O que é arquitetura evolucionária
O conceito de arquitetura evolucionária foi consolidado no excelente livro Building Evolutionary Architectures, escrito por Neil Ford, Rebecca Parsons e Jason Kua. Lá, eles propõe que arquiteturas evolucionárias são aquelas que 1) suportam mudanças incrementais guiadas; 2) em várias dimensões. Partindo da definição proposta, é possível inferir que toda arquitetura evolucionária tem “evolvability” entre seus atributos de qualidade.
A arquitetura evolucionária preconiza suportar as mudanças, de maneira incremental. De maneira ampla, assume-se que a filosofia evolucionária defende que, sempre que possível, mudanças devem ser aplicadas aos poucos, sem grandes saltos ou descontinuidades. Dessa forma, o “custo da mudança” fica diluído causando “menos dor”.
A evolução incremental, para ser segura, precisa ser guiada! Quanto menos “guiada”, maior o risco. Ao longo do tempo, na medida em que as mudanças ocorrem, é importante que os compromissos da arquitetura de software sejam relembrados e reafirmados. A cada mudança, é necessário identificar se houve aproximação ou afastamento dos objetivos do negócio, se as restrições continuam sendo observadas e se os atributos de qualidade estão sendo observados. Esse “cuidado” precisa ocorrer de forma objetiva e, sempre que possível, automatizada ou, pelo menos, incorporada a rotina. A proposta central passa por definir architectural fitness functions.
Finalmente, a arquitetura evolucionária lida com múltiplas dimensões, tentando preservar o equilíbrio. Melhorias na performance não podem comprometer a disponibilidade ou a segurança, por exemplo. A visão arquitetural é, dessa forma, holística.
A evolvability é, em última instância, a principal conexão entre a arquitetura de software e a escrita de código.
Postergando decisões para o “último momento responsável”
Toda decisão implica em assumir riscos. Quanto antes uma decisão é tomada, maiores são os riscos associados a possibilidade dessa decisão representar um erro causando prejuízos.
No início dos projetos, a principal preocupação dos arquitetos é explicitar a estratégia – padrões coerentes para tomada de decisão – que deve ser observada nas escolhas. Se possível, essa “estratégia” precisa ser objetiva e observável, expressa em architectural fitness functions. Arquitetura evolucionária não é emergente, nem baseada em palpites.
A evolução demanda implementação incremental das mudanças
Um sistema se torna legado quando nossa capacidade de pagar dívidas técnicas é menor que a necessidade de contrair novas. (Fernando Paiva)
A boa arquitetura permite e facilita que atualizações incrementais sejam realizadas. Um software onde o front-end esteja “emaranhado” com a lógica do negócio é, naturalmente, difícil de atualizar tecnologicamente. Por outro lado, quando há “inteligência demais” no front-end, a atualização é mais difícil.
HTTP/1.1 200 OK Content-Type: application/vnd.acme.account+json Content-Length: ... { "account": { "account_number": 12345, "balance": { "currency": "usd", "value": 100.00 }, "links": { "deposit": "/accounts/12345/deposit", "withdraw": "/accounts/12345/withdraw", "transfer": "/accounts/12345/transfer", "close": "/accounts/12345/close" } } }
Decisões de design assim, tiram do front-end a responsabilidade de decidir quando mostrar, ou não, um botão ou link. Um dos grandes inimigos da implantação de mudanças é o acoplamento e, por isso, ele deve ser evitado.
A prática da arquitetura evolutiva demanda métricas de qualidade interna
Código limpo e fácil de entender demanda menos esforço cognitivo e, por consequência, é mais fácil de mudar.
Indicadores simples como complexidade aferente e eferente, quando bem analisados autorizam identificar e priorizar pontos de atuação e desafios arquiteturais para a mudança.
No gráfico acima, podemos ver que há clara concentração de commits em uma parcela muito pequena de arquivos no projeto do servidor do RavenDB, por exemplo. Um arquivo, apenas, recebeu 3% de todos os commits. Mais de 50% dos commits envolveu menos de 10% de toda a base de código. Parece evidente que as dívidas técnicas presentes nesses arquivos acabariam tendo impactos muito mais altos para a produtividade do time. Também parece lógico que o código desses arquivos fosse aquele com maior cobertura por testes automatizados.
A implementação de métricas e o acompanhamento vigilante previne que desvios grandes de qualidade acontençam antes que algo possa ser feito para colocar as “coisas certas nos lugares certos”. Quando uma métrica “sai do controle”, é importante que a normalidade seja restabelecida no tempo certo.
Arquiteturas evolutivas combatem as “origens” da complexidade
A complexidade tem quatro origens genéricas distintas que devem ser combatidas. São elas: dimensionalidade, interdependência, influência do ambiente e irreversibilidade.
Quanto maior o número de variáveis envolvidas, maior será a dimensionalidade. Por isso, por exemplo, qualquer software com mais features se torna mais complexo. A cada novo processo na organização, menor a simplicidade. Toda exceção suportada aumenta o custo.
Operações interdependentes demandam, por exemplo, sincronização. Não raro, a indisponibilidade de um recurso impossibilita o uso de outro, aparentemente não relacionado. É bastante comum que uma performance mais pobre de uma parte de um sistema deixe ele todo “lento”.
Sistemas mais “sensíveis” ao ambiente também são mais complexos. Afinal, demandam planejamento de contingências e workarounds.
Finalmente, a irreversibilidade também demanda cuidados. Ações ou eventos cujo ocorrências resultem em consequências que não podem ser desfeitas implicam em custo maior de planejamento, nem sempre eficiente. Boa parte das estratégias modernas de desenvolvimento conta com o pressuposto de um “rollback” rápido. Este é o fundamento de iniciativas operacionais como DevOps.
Naturalmente, sistemas tendem para o incremento da dimensionalidade e da interdependência. Além disso, em ambientes cada vez mais abrangentes, é difícil controlar o impacto do ambiente, marcando, de forma irreversível impactos e prejuízos. Se o empenho de um time for orientado a apenas manter os níveis atuais de complexidade, essa irá aumentar.
Sistemas que seguem a lei de Postel evoluem mais fácil
A Lei de Postel — ou Lei da Robustez — é um princípio originalmente descrito como um guia para a transferência de dados entre softwares. Contudo, ela é bastante compatível com arquitetura.
A lei preconiza que sistemas sejam 1) conservadores no que enviam e ; 2) liberais no que recebem;
A evolução é segura quando “fitness functions” são determinadas
“Architectural Fitness Functions” é um conceito inspirado nas fitness functions utilizadas com algoritmos genéticos.
Uma função de adequação (fitness function) é um tipo particular de função objetivo que é usada para resumir, como uma única figura de mérito, o quão perto uma determinada solução de design está de atingir os objetivos definidos. As funções de adequação são usadas em programação genética e algoritmos genéticos para orientar as simulações em direção a soluções de arranjo ideais. (Wikipedia)
Uma “architectural fitness function” deve proteger as diversas propriedades de um sistema de software na medida em que modificações, preferencialmente incrementais, são realizadas. De maneira objetiva, uma solução implementada deve ser submetida sob a perspectiva de diversas fitness functions para garantir que todas as adaptações realizadas não impactam no atingimento dos objetivos de negócio, restrições ou atributos de qualidade que suportam. Toda vez que uma fitness function indicar afastamento do objetivo, sabe-se que há um problema a analisar ou adaptação a rever na arquitetura.
Toda grande jornada tem desvios, atalhos e … aprendizado
Os sistemas mais interessantes (e úteis) costumam começar com uma excelente direção, mas, raramente escopo claramente delimitado. Se há uma certeza é de que requisitos mudam, condições técnicas também.
Arquiteturas de software de qualidade são, acima de tudo, aquelas que permitem que mudanças de rumo sejam feitas com menos trauma possível. Quanto mais “fácil de mudar”, maior o espaço entre eventuais descontinuidades.
// TODO
Antes de avançar para o próximo capítulo, recomendo as seguintes reflexoões:
- É possível expressar objetivos, atributos de qualidade e restrições do sistema em que está trabalhando de forma objetiva?
- As métricas de qualidade interna estão orientadas a facilitar a manutenabilidade?
- Quais partes do seu sistema demandam mais testes?
- Qual o nível de acoplamento atual do frontend com lógicas de negócio?
Negócios e tecnologias evoluem em ritmos diferentes e por razões diferentes, e o software deve acompanhar ambas, mas não necessariamente na mesma velocidade, tudo dependa da necessidade, pois nem todo recurso de negócio demanda de uma nova tecnologia, as que existem podem permitir sua implementação de forma eficiente.
As mudanças de negócio impactam diretamente na razão da existência da aplicação, logo se ela não não evolui, ela não atende mais o motivo pelo qual foi criado. Já as mudanças de tecnologia impactam na possibilidade de permitir o software continuar evoluindo para continuar atendendo as razões de existir, há uma dependência do negócio para com a tecnologia, pois esta última é a ferramenta da qual o negócio depende para ser implementado.
A evolução do software deve ocorrer na velocidade da mudança dos negócios, e a velocidade da mudança de tecnologia será ditada pelos requisitos da mudança do negócio.
Os times de negócio que não se sensibilizam com a necessidade de reduzir a complexidade do código, estes não devem ser culpados pelo aumento da complexidade, pois essa responsabilidade é do time de desenvolvimento que deve considerar a manutenção da complexidade como parte do desenvolvimento, não como uma tarefa a ser executada posteriormente ou paralelamente, é parte do desenvolvimento mitigar as complexidades assim é desenvolver recursos.
Técnicas, padrões e boas práticas só são o que são por serem estáveis, que evoluem, mas não de uma forma instável. Já o conjunto de tecnologias evolui rapidamente, alguns são instáveis e outros não, e a escolha de uma tecnologia instável torna-se um desafio para manter e evoluir sistemas, por isso escolhemos tecnologias estáveis e seguimos padrões e boas práticas, para criar assim software estável.
Parecem legados pela ótica da evolução da tecnologia, mas pela ótica do negócio podem estar atuais, e partindo da premissa que a tecnologia é uma ferramenta para implementar o requisito de negócio, as bases de código podem “parecer” legadas, mas não significa que são, depende da ótica.
Esta é uma frase que nos leva a uma profunda reflexão, pois se times focados em atender demandas de negócio eventualmente se tornam menos produtivos, times focados apenas em tecnologia, sem conhecimento e foco no negócio, tendem a criar soluções mais complexas pela falta de conhecimento do negócio. É preciso encontrar o equilíbrio, mas ter um time especialista no negócio é fundamental para conseguir quebrar este complexidade
A inclusão de novas features é o mais comum. Não me lembro de ter atuado em demandas para quebrar complexidade sem adição de novas features. Acredito que alguns exemplos concretos aqui seriam esclarecedores
Acredito que esta instabilidade tecnológica é gerada por um conjunto de fatores, por exemplo a escolha de uma tecnologia não madura, ou por falta de conhecimento do time, ou alta rotatividade. Porque se levarmos em consideração os fundamentos de arquitetura, boas práticas são temas falados há décadas e que ainda assim soam como novidade nos dias de hoje
Concordo que quanto mais informações, maiores as chances de tomar uma decisão correta. O difícil é controlar a ansiedade de TODOS os envolvidos
É surpreendente que até os dias de hoje é um tabu pagar dívidas técnicas que foram assumidas outrora. Muito em parte culpa nossa mesmo, por como você mesmo diz, falta de habilidade em vender dinheiro.
Permita-me discordar de você, apesar de todos os dias surgirem tecnologias e frameworks novos. Isso não quer dizer que precisamos dessas tecnologias para resolver os problemas de negócio da empresa, salvo em raras exceções. Existe um delicado equilíbrio em que nós arquitetos devemos trazer entre utilizar novas tecnologias e utilizar “Tecnologias Entediantes”. Esse jargão é melhor nesse link: https://mcfunley.com/choose-boring-technology
É importante notar duas coisas: Os fundamentos do desenvolvimento de software são basicamente os mesmos desde a sua criação. Quanto mais se foca nos assuntos que ‘não mudam’ mais fica fácil enxergar que as novidades na verdade são variações sobre o mesmo tema. E que o software sempre é limitado ao hardware que o executa (como se o software fosse um gás que expande e ocupa todo o volume disponível). Logo, nós profissionais de software devemos estar atentos a evolução dessa indústria. As linguagens de paradigma funcional são mais utilizadas atualmente pela estabilização do clock dos processadores e pelo barateamento da memória RAM. Atualmente está ocorrendo uma grande transição da arquitetura de processadores, será que todos estão prestando atenção?
É triste ver que isso acontece na maioria das empresas. Pior ainda é quando existe uma divisão entre times de desenvolvimento e times de sustentação. Geralmente repassando a equipes menos preparadas tecnicamente a missão de realizar a manutenção do software que irá operar a empresa, fazendo assim que a degradação do software seja ainda mais rápida.
Pelas nossas discussões, ficou claro para mim que é possível criar uma arquitetura simples, respeitando os objetivos de negócio, as restrições e os atributos de qualidade e mantendo proximidade com o time de negócios. Mas para evitar que seja uma arquitetura simplória, basta que o atributo Evolvability seja tratado como o mais importante? Entendo que sim, mas confesso que tenho a incerteza de que é o caminho correto.