Criar um novo sistema é um processo de várias etapas: elaboração do conceito e design, design da arquitetura, implementação, teste e liberação. O design e a implementação da arquitetura são os estágios com os quais os desenvolvedores se preocupam principalmente.
A maioria dos desenvolvedores gosta de se engajar na arquitetura, pensar em como o sistema ou parte dele será organizado do zero. Se alguém que pensou na arquitetura do sistema e a implementou, não há problemas de motivação: o programador ficará satisfeito com a realização de suas idéias. Mas se um pensou em arquitetura e outro se engajou na implementação, então o último pode ter uma indignação natural: eles pensaram em tudo para mim, mas posso fazer o que está escrito?

Como evitar tais situações, por que a implementação não pode ser menos interessante que a elaboração da arquitetura e, às vezes, mais, será discutida neste artigo.
1. Introdução
Uma arquitetura bem pensada pode ser usada como base para tarefas de detalhamento : a implementação de cada componente suficientemente separado se torna uma subtarefa separada.
Por exemplo, se houver um pipeline de processamento de consultas projetado no estilo de pipes e filtros , as subtarefas serão a implementação de etapas individuais de processamento (cada etapa possui sua própria subtarefa) e outra subtarefa para conectar todas as etapas.
Embora uma arquitetura bem planejada e a divisão em subtarefas tenham uma idéia geral de como criar um sistema e permitir que você avalie os custos de mão-de-obra, eles não são suficientes para implementar o plano. A descrição da subtarefa dirá o que o componente deve fazer, pode conter requisitos para velocidade e consumo de memória, mas não fornecerá instruções abrangentes sobre como fazê-lo.
O fato é que existem muitas opções para criar um componente que atenda aos requisitos especificados. Depende muito de como será implementado: flexibilidade de código, extensibilidade, facilidade de suporte etc. Chegamos perto do conceito de design de código .
Código Design Concept
Às vezes, o design de código é chamado de arquitetura ou organização de código, às vezes até apenas arquitetura. Eu mantenho o termo design de código porque ele o contrasta com a arquitetura do sistema e traça uma linha clara entre eles. Para ser mais específico, considere um exemplo.
Digamos que estamos desenvolvendo o back-end de um site que está ganhando popularidade. O número de servidores já excedeu várias dezenas, o público está crescendo e decidimos que queremos coletar análises sobre o comportamento do usuário no site: popularidade das páginas de visita, frequência de uso dos recursos, dependendo do perfil do usuário etc.
Surgem várias questões arquiteturais e tecnológicas: onde armazenar métricas, como transferi-las pela rede, o que fazer se o armazenamento de métricas não estiver disponível, como o serviço de back-end registrará métricas etc. A arquitetura só precisa responder a essas perguntas, determinar os componentes da solução e definir requisitos para elas.
Suponha que desenvolvamos uma arquitetura: usaremos o InfluxDB como armazenamento, transferiremos métricas pela rede usando UDP para telegraf e, para contornar a inacessibilidade do armazenamento, armazenaremos as métricas no Kafka replicadas em vários servidores. Todas as métricas seguirão o caminho do serviço de back-end -> telegraf -> Kafka -> InfluxDB. Para escrever as métricas, o back-end decidiu escrever um módulo que implementa a função de transferência de métricas no telegraf usando UDP.
O módulo para registrar métricas é um componente separado do sistema, sua escrita é uma subtarefa separada que pode ser confiada ao desenvolvedor. Essa subtarefa tem muitas soluções e perguntas que precisam ser respondidas: as métricas serão enviadas de forma síncrona ou assíncrona; como o acesso simultâneo de vários segmentos de back-end será sincronizado, quais serão as principais classes / funções.
Essas perguntas estão além da descrição da arquitetura da solução, mas as respostas para elas têm consequências de longo alcance. Por exemplo, se durante a operação de uma solução ficar claro que a pilha de tecnologia não é ótima e você precisar substituir o telegraf por uma solução alternativa, a divisão incorreta do módulo em classes não permitirá isso sem reescrever o módulo inteiro. As respostas para essas perguntas são o domínio do design de código .
O desenvolvimento do design de código é um estágio de design separado , que fica entre o desenvolvimento da arquitetura do sistema e a codificação. Desenhar uma linha entre arquitetura e design de código permite projetar um sistema sem considerar todos os detalhes e avaliar os custos de mão-de-obra em um tempo limitado. Por outro lado, destacar o desenvolvimento do design de código como um estágio de implementação separado permite aumentar a qualidade da implementação do sistema, reduzir o custo de outras melhorias e aumentar a facilidade de suporte.
A necessidade de refletir sobre o design do código no estágio de implementação antes da codificação torna a implementação interessante : as tarefas de projetar um design de código podem não ser menos interessantes do que projetar um sistema inteiro no nível da arquitetura. Essa idéia foi expressa por Brooks no mítico homem-mês .
Obviamente, traçar uma linha entre arquitetura e design de código pode não ser tão fácil, vamos dar uma olhada mais de perto nesta questão.
A fronteira entre arquitetura e design de código
Ideologicamente, arquitetura e design de código estão em diferentes níveis de design: a arquitetura é pensada no estágio inicial, quando há pouca certeza, e pensar sobre o design de código adiciona detalhes. Assim, eles são executados em diferentes momentos: a arquitetura está mais próxima do início e o design do código durante a implementação das subtarefas.
O estabelecimento de um limite entre esses dois estágios do design depende de vários fatores, eis os principais:
- O grau em que o componente afeta o sistema. Às vezes, o dispositivo de todo o sistema pode depender significativamente do dispositivo de seu componente individual. Nesse caso, você precisa projetar o componente no estágio de desenvolvimento arquitetural, e não no estágio de implementação.
- A presença de uma interface clara para o componente. É possível isolar o design de um componente como uma subtarefa apenas se estiver claramente definido o que esse componente deve fazer e como ele irá interagir com o restante do sistema.
- Estimativas realistas do esforço para concluir a subtarefa. A tarefa pode ser muito grande para poder avaliar os custos de mão-de-obra com precisão suficiente. Nesse caso, é melhor projetar a tarefa com mais detalhes e dividi-la em suas próprias subtarefas para fornecer uma avaliação mais adequada dos custos de mão-de-obra.
Existem vários casos especiais em que você pode traçar uma boa linha entre o projeto de arquitetura e o design de código.
O componente tem uma API estrita.
Por exemplo, na minha prática, havia uma tarefa: implementar em cima de uma API de soquete UNIX para capturar / liberar recursos do sistema operacional usados por um daemon existente. Essa tarefa surgiu dentro da estrutura da arquitetura escolhida para o novo recurso épico . Dentro da estrutura da arquitetura, era bastante alto o nível de descrição da API, e o design detalhado foi feito posteriormente, durante a implementação.
Módulo / classe com uma determinada interface
A maneira mais fácil de delegar o design de uma parte de um sistema monolítico é destacar um módulo ou classe, descrever sua interface e tarefas. Um módulo alocado como uma subtarefa separada não deve ser muito grande. Por exemplo, a biblioteca cliente para acesso ao banco de dados shard é, sem dúvida, um módulo separado, mas será difícil avaliar a tarefa de implementar essa biblioteca pelos custos de mão-de-obra sem um design mais detalhado. Por outro lado, a tarefa de implementar uma classe muito pequena será trivial. Por exemplo, se a subtarefa "para implementar uma função que verifica a existência de uma determinada pasta por um determinado caminho" surgir, a arquitetura será claramente pensada em muitos detalhes.
Componente pequeno com requisitos fixos
Se o componente for pequeno o suficiente e o problema resolvido for estritamente definido, os custos de mão-de-obra para a implementação poderão ser estimados com precisão suficiente, e a implementação do próprio componente deixará espaço para o design. Exemplo: um processo em execução em uma coroa e excluindo recursivamente arquivos e diretórios antigos em um determinado caminho.
Antipatterns
Existem cenários em que a distribuição entre pensar sobre a arquitetura e a implementação não está correta, alguns deles são discutidos abaixo.
Tudo foi projetado nos mínimos detalhes.
Diagramas UML detalhados foram construídos, a assinatura de cada método de cada classe foi especificada, os algoritmos para a implementação de métodos individuais foram descritos ... De acordo com uma descrição tão detalhada, você pode implementar o sistema o mais rápido, realmente, porque tudo é planejado com tantos detalhes que não há espaço para a criatividade, aceite-o e faça-o escrito. Se o objetivo é que o desenvolvedor codifique o que eles dizem para ele o mais rápido possível, sim, você pode.
No entanto, se você se aprofundar um pouco mais, ficará claro um número de deficiências na organização do trabalho dessa maneira. Primeiro, para projetar tudo com esses detalhes, você precisará gastar muito tempo no próprio design. No que o desenvolvedor geralmente pensa antes da implementação, o arquiteto pensa nesse esquema: todo o design se aproxima do início do projeto, o que pode aumentar sua duração. Afinal, se você não dividir o trabalho de design em partes, não poderá paralelizar. Em segundo lugar, a falta de trabalho de design durante a implementação reduzirá bastante a motivação dos desenvolvedores: fazer exatamente o que eles dizem pode ser útil para iniciantes, mas desenvolvedores experientes ficarão entediados. Em terceiro lugar, essa abordagem geralmente pode reduzir a qualidade da produção: o sistema, que não é dividido em componentes suficientemente independentes, será mais difícil de manter e expandir.
A arquitetura é sempre projetada por um desenvolvedor, o resto fumar de lado só percebe
Primeiro de tudo, vários casos devem ser observados quando isso pode ser útil. Em primeiro lugar, é uma equipe em que há muitos iniciantes e apenas um programador experiente. Nesse caso, os iniciantes não têm experiência suficiente para projetar a arquitetura para executar o trabalho com qualidade suficiente, enquanto, ao mesmo tempo, a implementação de uma arquitetura bem pensada os ajudará a aumentar seu nível. Em segundo lugar, esses são grandes projetos nos quais várias equipes estão envolvidas. Em seguida, o design da arquitetura do projeto é dividido em dois níveis: o arquiteto pensa nele como um todo e em cada equipe - a arquitetura dos componentes dentro de sua área de responsabilidade.
Mas considere uma equipe composta por especialistas suficientemente experientes. Se as tarefas de arquitetura sempre serão atribuídas a apenas um, digamos, o desenvolvedor mais experiente, outros desenvolvedores não poderão revelar completamente seus recursos. A arquitetura do sistema será unilateral, porque todo mundo tem um conjunto de técnicas que ele aplica. Se diferentes desenvolvedores pensassem na arquitetura de diferentes componentes / subsistemas, isso facilitaria a troca de experiências e o desenvolvimento dos membros da equipe. Às vezes, mesmo os membros da equipe não muito experientes devem receber tarefas de arquitetura: isso aumentará seu nível e aumentará o envolvimento no projeto.
Conclusão
A presença do estágio de design na implementação é o principal fator que torna as tarefas de implementação interessantes. É claro que existem outras: o uso de novas tecnologias, tarefas de pesquisa, mas, em regra, são muito menos comuns. Se as tarefas de implementação não exigirem design e consistirem em codificação simples, isso afetará bastante a motivação dos desenvolvedores e não permitirá o uso de suas habilidades.
A criação de um design de código no estágio de implementação permite fazer rapidamente estimativas adequadas dos custos de mão-de-obra, paralelizar com mais eficiência o trabalho e, geralmente, melhorar a qualidade do sistema.
A necessidade de projetar um design de código durante a implementação é exatamente o que torna a implementação interessante nos olhos dos desenvolvedores.
Não vale a pena cometer erros, excluindo o trabalho de design da implementação de subtarefas, assim como você nem sempre deve confiar tarefas de arquitetura apenas ao desenvolvedor mais experiente.