CUPID — para codificação alegre - parte 3
Isso é uma continuação da parte 2 que tive que cortar o texto devido a limitação de caracteres do tabnews
https://www.tabnews.com.br/uriel/cupid-para-codificacao-alegre-parte-2
Outro cenário comum é um “componente de interface do usuário” em que o SRP exige que você separe a renderização e a lógica de negócios do componente. Como desenvolvedor, tê-los morando em lugares diferentes leva a uma tarefa administrativa de encadear campos idênticos. O maior risco é que isso possa ser uma otimização prematura que impeça uma separação mais natural de preocupações surgindo à medida que a base de código cresce e à medida que surgem componentes que “fazem uma coisa bem” e que são mais adequados ao modelo de domínio do espaço do problema. À medida que qualquer base de código cresce, chegará a hora de separá-la em subcomponentes sensíveis, mas as propriedades de Composability e estrutura baseada em domínio serão um indicador melhor de quando e como fazer essas alterações estruturais.
Previsível ¶
O código deve fazer o que parece, de forma consistente e confiável, sem surpresas desagradáveis. Deve ser não apenas possível, mas fácil de confirmar isso. Nesse sentido, a previsibilidade é uma generalização da testabilidade.
O código previsível deve se comportar como esperado e deve ser determinístico e observável.
Comporta-se como esperado ¶
A primeira das quatro regras de design simples de Kent Beck é que o código “passa em todos os testes”. Isso deve ser verdade mesmo quando não há testes! O comportamento pretendido do código previsível deve ser óbvio a partir de sua estrutura e nomeação. Se não houver testes automatizados para fazer isso, deve ser fácil escrever alguns. Michael Feathers chama esses testes de caracterização. Em suas palavras:
“Quando um sistema entra em produção, de certa forma, ele se torna sua própria especificação.” — Michael Feathers
Isso não é necessário, e acho que algumas pessoas pensam no desenvolvimento orientado a testes como uma religião e não como uma ferramenta. Certa vez, trabalhei em um aplicativo de negociação com algorítmico complexo que tinha cerca de 7% de “cobertura de teste”. Esses testes não foram distribuídos uniformemente! Grande parte do código não tinha testes automatizados, e alguns tinham quantidades loucas de testes sofisticados, verificando bugs sutis e casos extremos. Eu estava confiante em fazer alterações na maior parte da base de código, porque cada um dos componentes fazia uma coisa e seu comportamento era direto e previsível, então a mudança geralmente era óbvia.
Determinista ¶
O software deve fazer a mesma coisa todas as vezes. Mesmo o código projetado para ser não determinístico – digamos, um gerador de números aleatórios ou um cálculo dinâmico – terá limites operacionais ou funcionais que você pode definir. Você deve ser capaz de prever limites de memória, rede, armazenamento ou processamento, limites de tempo e expectativas de outras dependências.
O determinismo é um tema amplo. Para fins de previsibilidade, o código determinístico deve ser robusto, confiável e resiliente.
- Robustez é a amplitude ou completude das situações que cobrimos. Limitações e casos extremos devem ser óbvios.
- Confiabilidade é agir como esperado nas situações que cobrimos. Devemos obter sempre os mesmos resultados.
- Resiliência é o quão bem lidamos com situações que não cobrimos; perturbações inesperadas nas entradas ou no ambiente operacional.
Observável ¶
O código deve ser observável no sentido da teoria de controle: podemos inferir seu estado interno de suas saídas. Isso só é possível quando o projetamos. Assim que vários componentes estiverem interagindo, especialmente de forma assíncrona, haverá um comportamento emergente e consequências não lineares.
Instrumentar o código desde o início significa que podemos obter dados valiosos para entender suas características de tempo de execução. Eu descrevo um modelo de quatro estágios – com dois estágios de bônus! – assim:
1.Instrumentação é o seu software dizendo o que está fazendo.
2.A telemetria está disponibilizando essas informações, seja por pull – algo pedindo – ou push – enviando mensagens; “medição à distância”.
3.Monitorar é receber instrumentação e torná-la visível.
4.Alertar é reagir aos dados monitorados ou padrões nos dados.
Bônus:
6.Prever é usar esses dados para antecipar eventos antes que eles aconteçam.
7.Adaptar é mudar o sistema dinamicamente, seja para antecipar ou se recuperar de uma perturbação prevista.
A maioria dos softwares nem passa da etapa 1. Existem ferramentas que interceptam ou modificam sistemas em execução para adicionar um nível de percepção, mas elas nunca são tão boas quanto a instrumentação deliberada projetada em um aplicativo.
Idiomático ¶
Todo mundo tem seu próprio estilo de codificação. Seja espaços versus tabulações, tamanho de recuo, convenções de nomenclatura de variáveis, colocação de chaves ou parênteses, layout de código em um arquivo de origem ou inúmeras outras possibilidades. Sobre isso, podemos colocar as opções de bibliotecas, cadeia de ferramentas, caminho para o live, até mesmo estilo de comentário de controle de versão ou granularidade de commit. (Você usa controle de versão, não é?)
Isso pode adicionar uma carga cognitiva estranha significativa ao trabalho com código desconhecido. Além de entender o domínio do problema e o espaço da solução, você precisa interpretar o que outra pessoa quis dizer e se suas decisões foram deliberadas e contextuais ou arbitrárias e habituais.
O maior traço de programação é a empatia; empatia pelos seus usuários; empatia pelo pessoal de apoio; empatia pelos futuros desenvolvedores; qualquer um deles pode ser você no futuro. Escrever “código que humanos possam entender” significa escrever código para outra pessoa. Isso é o que significa código idiomático.
Nesse contexto, seu público-alvo é:
- familiar com a linguagem, suas bibliotecas, sua cadeia de ferramentas e seu ecossistema
- um programador experiente que entende de desenvolvimento de software
- alguém tentando fazer o trabalho!
Expressões idiomáticas ¶
O código deve estar de acordo com as expressões idiomáticas do idioma. Algumas linguagens têm opiniões fortes sobre como o código deve ser, o que facilita a avaliação de quão idiomático é seu código. Outros são menos opinativos, o que coloca o ônus de você “escolher um estilo” e depois cumpri-lo. Go e Python são dois exemplos de uma linguagem opinativa.
Os programadores Python usam o termo “pythonic” para descrever o código idiomático. Há um [ovo de Páscoa](https://en.wikipedia.org/wiki/Easter_egg_(media) maravilhoso que aparece se você for import this do Python REPL ou executar python -m this a partir de um shell. Ele imprime uma lista de aforismos de programação chamada “The Zen of Python”, que inclui esta linha, capturando o espírito do código idiomático: “Deveria haver uma – e de preferência apenas uma – maneira óbvia de fazer isso”.
A linguagem Go vem com um formatador de código chamado gofmt que faz com que todo o código-fonte pareça o mesmo. Isso elimina de uma só vez quaisquer divergências sobre recuo, colocação de chaves ou outras peculiaridades sintáticas. Isso significa que todos os exemplos de código que você vê nos documentos ou tutoriais da biblioteca parecem consistentes. Eles até têm um documento chamado Effective Go que mostra o Go idiomático, além da definição do idioma.
No outro extremo do espectro estão linguagens como Scala, Ruby 5, JavaScript e o venerável Perl. Essas linguagens são deliberadamente multiparadigmáticas; Perl cunhou a sigla TIMTOWTDI – “há mais de uma maneira de fazer isso” – pronunciada “Tim Toady”. Você pode escrever código funcional, procedural ou orientado a objetos na maioria deles, o que cria uma curva de aprendizado superficial de qualquer linguagem que você conheça.
Para algo tão simples quanto processar uma sequência de valores, a maioria dessas linguagens permite:
- usar um iterador
- usar um loop for indexado
- usar um loop condicional while
- usar um pipeline de função com um coletor (“map-reduce”)
- escrever uma função recursiva
Isso significa que em qualquer tamanho de código não trivial, você provavelmente encontrará exemplos de cada um deles, geralmente combinados entre si. Novamente, tudo isso adiciona carga cognitiva, impactando sua capacidade de pensar sobre o problema em questão, aumentando a incerteza e reduzindo a alegria.
Idiomas de código ocorrem em todos os níveis de granularidade: nomeação de funções, tipos, parâmetros, módulos; layout de código; estrutura de módulos; escolha de ferramentas; escolha de dependências; como você gerencia dependências; e assim por diante.
Onde quer que sua pilha de tecnologia esteja no espectro da opinião, o código que você escrever será mais empático e alegre se você dedicar um tempo para aprender as expressões idiomáticas da linguagem, seu ecossistema, sua comunidade e seu estilo preferido.
Sua curva de aprendizado para uma tecnologia provavelmente será mais curta do que qualquer código que você escrever nela, por isso é importante resistir ao desejo de escrever um código que esteja bem para você agora, porque essa pessoa não ficará por muito tempo! A única maneira de ter certeza de que você está escrevendo código idiomático é dedicar um tempo para aprender os idiomas.
Expressões idiomáticas locais ¶
Quando uma linguagem não tem consenso em torno do estilo idiomático ou de várias alternativas, cabe a você e sua equipe decidir o que é “bom” e introduzir restrições e diretrizes para incentivar a consistência. Essas restrições podem ser tão simples quanto regras de formatação de código compartilhado em seu IDE, ferramentas de “build cop” (linst) que deslindam e criticam o código e um acordo sobre uma cadeia de configurações padrão.
Os Registros de Decisão de Arquitetura, ou ADRs, são uma ótima maneira de documentar suas escolhas sobre estilo e idiomas. Estas não são menos “decisões técnicas significativas” do que qualquer outra discussão arquitetônica.
Baseado em domínio ¶
Nós escrevemos software para atender a uma necessidade. Isso pode ser específico e situacional, ou genérico e de longo alcance. Qualquer que seja seu propósito, o código deve transmitir o que está fazendo na linguagem do domínio do problema, para minimizar a distância cognitiva entre o que você escreve e o que ele faz. Isso é mais do que “usar as palavras certas”.
Linguagem baseada em domínio ¶
As linguagens de programação e suas bibliotecas estão cheias de construções da ciência da computação, como mapas de hash, listas vinculadas, conjuntos de árvores, conexões de banco de dados e assim por diante. Eles têm tipos básicos que incluem inteiros, caracteres, valores booleanos. Você pode declarar o sobrenome de alguém como um string[30], que pode ser como ele é armazenado, mas definir um tipo
Surnameserá mais revelador da intenção. Pode até ter operações, propriedades ou restrições relacionadas ao sobrenome. Muitos bugs sutis no software bancário são devidos à representação de quantias em dinheiro como valores de ponto flutuante; programadores de software financeiro experientes definirão um tipo Money com a Currencye an Amount, que por si só é um tipo composto.
Nomear bem os tipos e as operações não se trata apenas de capturar ou prevenir bugs, mas de facilitar a articulação e a navegação no espaço da solução no código. Fiz desta minha contribuição para “97 Coisas que Todo Programador Deve Saber”, como “Código na Linguagem do Domínio”.
Um critério para o sucesso com código orientado a domínio é que um observador casual não pode dizer se as pessoas estão discutindo o código ou o domínio. Eu experimentei isso uma vez em um sistema de negociação eletrônico, onde um analista financeiro estava discutindo uma lógica complexa de preços comerciais com dois programadores. Eu pensei que eles estavam discutindo as regras de precificação, mas eles estavam apontando para uma tela cheia de código e o analista estava falando com os programadores através do algoritmo de precificação, que era linha por linha como o código era lido! A única distância cognitiva entre o domínio do problema e o código da solução era alguma pontuação de sintaxe!
Estrutura baseada em domínio ¶
O uso de linguagem baseada em domínio é importante, mas a forma como você estrutura seu código pode ser igualmente significativa. Muitos frameworks oferecem um “projeto esqueleto” com um layout de diretório e arquivos stub projetados para você começar rapidamente. Isso impõe uma estrutura a priori em seu código que não tem nada a ver com o problema que você está resolvendo.
Em vez disso, o layout do código - os nomes dos diretórios, os relacionamentos das pastas filhas e irmãs, o agrupamento e a nomenclatura de arquivos relacionados - devem espelhar o domínio do problema o mais próximo possível.
O framework de aplicativos Ruby on Rails popularizou essa abordagem no início dos anos 2000, incorporando-a em suas ferramentas, e a ampla adoção do Rails fez com que muitos frameworks posteriores copiassem a ideia.
CUPID é agnóstico para linguagens e frameworks, mas Rails é um estudo de caso útil para entender a diferença entre estrutura baseada em domínio e estrutura baseada em framework.
Abaixo está parte do layout do diretório de um aplicativo Rails esqueleto gerado, focando no diretório (app) onde um desenvolvedor passará a maior parte de seu tempo. O esqueleto completo é executado em cerca de 50 diretórios contendo 60 arquivos, 7 no momento da escrita.
app
├── assets
│ ├── config
│ ├── images
│ └── stylesheets
├── channels
│ └── application_cable
├── controllers
│ └── concerns
├── helpers
├── javascript
│ └── controllers
├── jobs
├── mailers
├── models
│ └── concerns
└── views
└── layouts
Imagine que este será um aplicativo de gestão hospitalar, com uma seção para prontuários de pacientes. Este layout sugere que precisaremos de pelo menos:
- um model, que mapeia para um banco de dados em algum lugar
- uma view, que renderiza o registro do paciente em uma tela
- um controller, que faz a mediação entre visualizações e modelos
Depois, há espaço para helpers, assets e vários outros conceitos de estrutura, como preocupações de model ou preocupações de controller , mailers , jobs , channels e talvez um controller JavaScript para acompanhar seu controller Ruby. Cada um desses artefatos vive em um diretório separado, embora sejam semanticamente integrados.
A probabilidade é que qualquer mudança não trivial no gerenciamento de registros de pacientes envolva código espalhado por toda a base de código. O princípio SOLID de Single Responsibility diz que o código de visualização deve ser separado do código do controlador, e frameworks como Rails interpretam isso como significando tê-los em lugares completamente diferentes. Isso aumenta a carga cognitiva, reduz a coesão e aumenta o esforço de fazer mudanças no produto. Como discuti anteriormente, essa restrição ideológica pode tornar o trabalho mais difícil e a base de código menos prazerosa.
Ainda precisamos de artefatos como models, views e controllers, independentemente da forma como disponhamos o código, mas agrupá-los por tipo não deve formar a estrutura primária. Em vez disso, o nível superior da base de código deve mostrar os principais casos de uso do gerenciamento hospitalar; talvez patient_history, appointments, staffinge compliance.
Adotar uma abordagem baseada em domínio para a estrutura do código torna fácil entender para que serve o código e fácil de navegar para onde quer que você precise estar para algo mais complicado do que “tornar esse botão azul claro”.
Limites baseados em domínio ¶
Quando estruturamos o código da maneira que queremos e o nomeamos da maneira que queremos, os limites do módulo se tornam limites de domínio e a implantação se torna direta. Tudo o que precisamos para implantar um componente como um único artefato está junto, para que possamos alinhar os limites do domínio com os limites da implantação e implantar componentes e serviços de negócios coesos. Independentemente de você empacotar seus produtos ou serviços como um único monolito, muitos microsserviços pequenos ou em qualquer lugar entre eles, esse alinhamento reduz a complexidade do seu caminho para viver e torna menos provável que você esqueça algo ou inclua artefatos de um ambiente diferente ou um subsistema diferente.
Isso não nos limita a um nível único, plano e superior de estrutura de código. Os domínios podem conter subdomínios; componentes podem conter subcomponentes; as implantações podem ocorrer em qualquer nível de granularidade que faça sentido para sua mudança e perfil de risco. Alinhar os limites do código com os limites do domínio torna todas essas opções mais fáceis de raciocinar e gerenciar.
Considerações finais ¶
Acredito que o código que possui mais dessas propriedades — de composição, filosofia Unix, previsibilidade ou ser idiomático ou baseado em domínio — é mais agradável de se trabalhar do que o código que não possui. Embora eu valorize cada característica independentemente, acho que elas se reforçam mutuamente.
Código que pode ser composto e abrangente – fazendo uma coisa bem – é como um amigo confiável. O código idiomático parece familiar, mesmo que você nunca o tenha visto antes. O código previsível oferece ciclos sobressalentes para você se concentrar em surpresas em outros lugares. O código baseado em domínio minimiza a distância cognitiva da necessidade à solução. Mover o código para o “centro” de qualquer uma dessas propriedades o deixa melhor do que você o encontrou.
Como CUPID é um backronym, eu tinha vários candidatos para cada letra. Escolhi esses cinco porque eles parecem “fundamentais” de alguma forma; podemos derivar todas as outras propriedades candidatas a partir delas. Artigos futuros explorarão algumas das propriedades da lista restrita que não foram selecionadas e observarão como elas são consequências naturais da criação de software CUPID.
Estou ansioso para ouvir as aventuras das pessoas com o CUPID. Já estou ouvindo sobre equipes que usam essas propriedades para avaliar seu código e desenvolver estratégias para limpar bases de código legadas, e mal posso esperar para ouvir relatos de experiência e estudos de caso. Enquanto isso, quero me aprofundar no CUPID, explorando cada uma das propriedades, para ver o que mais está escondido à vista de todos.
Origianl > https://dannorth.net/2022/02/10/cupid-for-joyful-coding/
Minha traduão no Dev.to https://dev.to/urielsouza29/cupid-para-codificacao-alegre-parte-2-31mk
Outros textos
https://www.tabnews.com.br/uriel/os-principios-anti-solid-cupid-primeira-parte-como-tudo-comecou
https://www.tabnews.com.br/uriel/cupid-para-codificacao-alegre-parte-2