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 |
Negócios e tecnologias, eventualmente, podem evoluir em ritmos diferentes e por razões diferentes, e o software deve suportar ambos os tipos de mudança, mas, logo, não, necessariamente, na mesma velocidade. As mudanças de negócio impactam diretamente a razão da existência de qualquer aplicação. Dessa forma, quando se ela não não evolui, ela não atende mais o motivo pelo qual foi criada. Já as mudanças de tecnologia impactam na possibilidade de permitir o software continuar evoluindo para continuar atendendo o negócio.
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.0Você 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.0Você concorda?x
Dívida técnica
Dívidas técnicas surgem quando desenvolvedores de software, de forma deliberada ou não, abrem mão de boas práticas de desenvolvimento para ganhar algum tempo. Entretanto, acabam sofrendo, mais tarde, com os custos e tempos de manutenção mais altos.
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.
As atividades de um arquiteto, em um contexto evolucionário, são o da busca constante da resolução de incertezas. Aliás, o grande inimigo é a incerteza! Isso não significa, entretanto que o arquiteto não deva ter, a todo momento, a “escolha-com-os-dados-de-agora”. Ou seja, documentos arquiteturais e discussões precisam revelar a “posição atual” da arquitetura, indicando, porém, onde estão os pontos de maior incerteza.
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.
Análise da complexidade ciclomática, embora não seja precisa, costuma funcionar como um bom proxy para identificar pontos do código que irão consumir mais tempo 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?