Software muda! Muitas vezes para atender novas necessidades do negócio, algumas vezes para acomodar evoluções tecnológicas. Boas práticas arquiteturais ajudam a “aceitar” melhor as mudanças, minimizando custo e riscos.
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. Ford, Parsons e Kua |
Com o tempo, a aplicação de mudanças gera, naturalmente, complexidades predatórias. Ou seja, resquícios de iniciativas ou decisões do passado que ficam no código ou em decisões que não foram adequadamente revertidas. As práticas da arquitetura devem mitigar essas complexidades de maneira contínua e disciplinada.
Complexity is anything related to the structure of a software system that makes it hard to understand and modify the system. John Ousterhout |
O ritmo das mudanças, tecnológicas e de negócios, está acelerado e está ficando cada vez mais acelerado. Aliás, essa é a característica que melhor justifica as atividades de arquitetura.
If I had to name one primary factor that influences architecture, I’d put rate of change at the top of my list. Greg Hohpe |
Sempre inclua Evolvability entre os atributos de qualidade de seus projetos. |
Definição: Evolvability
Atributo de qualidade relacionado a capacidade de um sistema “evoluir”, tanto para atender demandas de negócio como para “acomodar” novidades tecnológicas, sem comprometer objetivos do negócio, desrespeitar restrições ou romper os demais atributos, minimizando custo ou risco.
Valor de software sob duas perspectivas
Há duas formas dominantes de perceber e valorizar software: 1) pelas funcionalidades que ele oferece (comportamento); e 2) por sua arquitetura (estrutura). Essa distinção é tão marcante que acaba sendo “replicada” em quase todas atividades de engenharia, inclusive na geração de documentação arquitetural – por vezes, na explicitação na estrutura de módulos, em outras, na relação de componentes e conectores.
As funcionalidades de um software são expressas em dados e comportamentos. Trata-se da parte com utilidade mais percebida de um sistema. São as funcionalidades que suportam atividades dos usuários, de forma economicamente favorável (geralmente economizando tempo e dinheiro) e, por causa disso, muitas vezes, são entendidas como única manifestação de valor.
A arquitetura de um software trata de como ele está organizado, em termos de componentes e conexões, para suportar as funcionalidades. A arquitetura garante que o software continuará fazendo a coisa certa, do jeito certo, ao longo do tempo.
Um software terá mais valor quando conseguir suportar adequações de suas funcionalidades, ao longo do tempo, no ritmo necessário (por sinal, cada vez mais acelerado). Para isso, é de fundamental importância que possua uma arquitetura flexível que consiga acomodar essas adequações.
Arquiteturas mal concebidas ou implementadas aumentam silenciosamente o custo de propriedade de um sotware. |
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 As “Leis de Lehman” direcionam o comportamento e o projeto das iniciativas para engenharia de software. Nesse episódio, disponível em áudio e vídeo, explicamos essas leis. |
Complexity will still increase over time, in spite of our best efforts, but simpler designs allow us to build larger and more powerful systems before complexity becomes overwhelming. John Ousterhout |
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.
Dissemine as leis de Lehman em sua organização. |
Mudanças tecnológicas acontecem rapidamente e têm impactos imprevisíveis
A emergência de práticas como a utilização de contêineres, 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.
Determine qual a obsolescência tecnológica máxima admitida. |
Adicionar evolvability como atributo de qualidade arquitetônica implica proteger os demais atributos conforme o sistema evolui. Ford, Parsons e Kua |
Explicitando princípios, requisitos e restrições
Boa arquitetura de software garante o atendimento dos objetivos do negócio, respeitando restrições e atributos de qualidade, minimizando risco ou custo. Para que isso aconteça, esses aspectos precisam ser conhecidos e modelados.
Requisitos e restrições em projetos de software, emergem de princípios e diretrizes que, por sua vez, são derivados de objetivos associados a aspectos de interesse de algum stakeholder. Geralmente, aspectos de interesse são “amparados” por conhecimentos prévios. É responsabilidade da arquitetura externar todos esses aspectos.
Para um exemplo tangível, podemos assumir “conversão em e-commerce” como um “tema de interesse” de um CMO. Poderia ser associado o “conhecimento prévio” de que “cada segundo de renderização de página impacta em 7% de conversões”, daí poderia ser desdobrado o objetivo de que “renderizar páginas em menos de 300ms”, que implicaria em uma série de diretrizes arquiteturais associadas – como restrições e atributos de qualidade.
Evolvability emerge de “práticas evolucionárias”
Evolvability, como atributo de qualidade arquitetural, emerge de práticas de engenharia e arquitetura, adequadas a este fim, executadas ao longo do tempo.
Building Evolutionary Architectures As práticas de arquitetura evolucionárias foram consolidadas, primeiro, nessa excelente obra. Trata-se da referência mais qualificada para esse tema. |
Práticas arquiteturais evolucionárias se caracterizam por:
- suportar mudanças incrementais, parte por parte, preservando a eficiência (tempo) na aplicação de mudanças futuras;
- serem guiadas por fitness funcions, comprovando continuamente adequação a parâmetros de qualidade bem-definidos;
- acomodar concerns em múltiplas dimensões.
Building Evolutionary Architectures - Craft Conference 2019 Neil Ford compartilha nessa excelente palestra conceitos e insights fundamentais para o entendimento das práticas de arquitetura evolucionária. Uma verdadeira aula, de um dos criadores do conceito. |
Evolvability demanda suporte a mudanças incrementais
Um sistema se torna legado quando nossa capacidade de pagar dívidas técnicas é menor que a necessidade de contrair novas. Fernando Paiva |
Estruture um “catálogo” das dívidas técnicas. Gerencie-as! |
Evolvability demanda “governança arquitetural”
Como garantir que, frente as demandas do dia a dia, times de desenvolvimento de software se comprometam a preservar, sobretudo, atributos de qualidade? Restrições e objetivos de negócio, em geral, são mais fáceis de observar. Entretanto, atributos de qualidade, como disponibilidade e segurança são, certamente, bem menos evidentes. O “caminho”, entende-se, passa pelo estabelecimento de práticas de governança.
Governança
Conjunto de práticas e condutas pelas quais se busca certificar que “resultados planejados” serão realizados, respeitando condições e regras estabelecidas, sem prejuízo para nenhuma das partes.
Na prática, guidelines e guardrails são estabelecidos pela definição de padrões e decisões arquiteturais, amplamente comunicadas, e pela adoção de processos e ferramentas de verificação.
A evolvability é, em última instância, a principal conexão entre a arquitetura de software e a escrita de código e impedimento para que antigos erros sejam repetidos.
“Testando” a arquitetura
Há diversas ferramentas disponíveis para ajudar arquitetos a conduzir governança arquitetural de seus sistemas. Mais recentemente, começaram a emergir frameworks de testes automatizados visando ajudar nesse propósito.
Bibliotecas como ArchUnit (para Java) e ArchUnitNET (para .NET) permitem “representar em código” guardrails importantes, como, por exemplo impedir referências cíclicas.
IArchRule rule = Slices().Matching("Module.(*)").Should() .BeFreeOfCycles();
Evolvability e Architectural Fitness Functions
Quando falamos em governança, devemos encontrar meios objetivos – fórmulas – que se observadas regularmente, demonstram a “aproximação” da solução arquitetural de um “ideal” – por isso, são identificadas como architectural fitness functions.
Definição: Architectural Fitness Functions
Architecture fitness functions são um tipo de função objetivo que é usada para resumir o quão perto uma determinada solução de arquitetura de software está de atingir os objetivos definidos.
Trata-se de um conceito derivado das fitness functions, comuns em algoritmos genéticos.
Architectural fitness functions podem ser holísticas – apuradas com base no software como um todo – ou isoladas, geradas automaticamente (o ideal) ou através de processos manuais, disparadas pelo processo automaticamente ou acionadas explicitamente (como “desempenho” em testes de carga), estáticas (mensuráveis a partir do código) ou dinâmicas (demandando software em execução)
Testes de unidade automatizados, por exemplo, ajudam a estabelecer uma “rede de proteção contra regressão” que, combinados com limites mínimos de cobertura, servem como indícios de evolvability.
Outro bom indicador é a ponderação das métricas de complexidade de um artefato conforme sua frequência de modificação.
Na fórmula acima, sugere-se que a complexidade de um software inteiro pode ser determinada pela complexidade de uma parte (Cp) e a frequência de mudança dessa parte. Interessante observar que a frequência de mudança pode ser medida usando sistemas de controle de versão (que devem ser bem mais do que backup sofisticados)
A Philosophy of Software Design A ideia de modularizar um sistema, isolando partes mais complexas com menor necessidade de modificações é a base desse maravilhoso livro. |
Outro bom exemplo, é análise de manutenabilidade a partir de indicadores de acoplamento e abstração (que veremos em detalhes no próximo capítulo).
Essas funções, bem aplicadas, governam a evolução do software, desde que incorporadas a pontos chaves do processo (como em liberação de versões e integração de commits)
The Checklist Manifesto O argumento central desse livro é o estabelecimento de procedimentos de verificação, rotineiros, para garantia da qualidade. Trata-se de uma forma de evitar que a “rotina” tire o foco de atividades que são fundamentais. |
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.
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.
Arquiteturas evolutivas combatem as “origens” da complexidade
There are two general approaches to fighting complexity. The first approach is to eliminate complexity by making code simpler and more obvious. For example, complexity can be reduced by eliminating special cases or using identifiers in a consistent fashion. The second approach to complexity is to encapsulate it, so that programmers can work on a system without being exposed to all of its complexity at once. John Ousterhout |
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;
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?