Você vai odiar isso ou uma história sobre como o código deve ficar bom

Quantas cópias estão quebradas e ainda estão quebradas em busca do código perfeito.  Aparentemente, chegou a hora e eu devo participar disso :)


Bom dia a todos. Algum tempo atrás, conversei com os alunos sobre o tópico “O que esperamos de um bom código” e decidi duplicá-lo aqui. No processo de tradução, o texto mudou um pouco, mas a essência permaneceu a mesma. O artigo acabou por ser simples (e certamente não completo), mas há um grão racional aqui.


Código deve funcionar


Coisas óbvias não trazem menos problemas pelo fato de serem óbvias. Muitos projetos entram em colapso apenas porque o desenvolvimento se desviou completamente da solução de problemas reais do usuário


Vamos falar sobre o que esperamos do código. Bem, para começar, deve funcionar.


Parece óbvio, é claro, mas cada um de nós tentou ou lançou com êxito um código que nem iria, então não ria. O segundo ponto - o código deve funcionar corretamente com situações incorretas. Isso é pegar erros. Mas vamos voltar ao primeiro ponto e falar um pouco sobre isso.


Periodicamente, recebo uma tarefa que não tenho idéia de como fazer. Ou seja, em geral (eu tento olhar em volta e constantemente experimentar algo novo). E aqui fui imediatamente atraído a escrever algumas abstrações, algum tipo de infraestrutura, a fim de atrasar o momento do trabalho real. Então, isso está errado. O código deve funcionar. Sei que estou me repetindo, mas esse é um ponto importante. Se você não sabe como resolver o problema, não se apresse em criar interfaces, módulos e isso é tudo. Essa é uma péssima idéia, que terminará no final do seu tempo e você não irá a lugar algum. Lembre-se de que um código que funciona mal é muitas vezes melhor que um código bom, mas não funciona.


Há uma antiga parábola sobre duas empresas de software que fabricaram o mesmo produto. O primeiro fez de qualquer maneira, mas o primeiro entrou no mercado, e o segundo fez tudo perfeitamente e estava atrasado. Como resultado, a primeira campanha conseguiu conquistar o mercado e comprou a segunda empresa. É um pouco sobre outro, mas a idéia principal ainda é a mesma. Primeiro resolvemos o problema, depois tornamos o código bonito.


Em geral, primeiro faça um protótipo funcional. Que seja coxo, torto e infeliz, mas quando solicitado, você pode dizer que a solução já está lá, resta integrá-la. E reescreva-o como deveria. Você pode tentar expressar isso com essa máxima - se você sabe como executar a tarefa - faça-o bem. Se você não sabe, primeiro resolva-o de alguma forma.


E há um ponto importante. Eu gostaria que você entendesse. Esta não é uma chamada para escrever código incorreto. O código deve estar bom. Esta é uma chamada para First Thing First - primeiro o código funciona, depois refatora.


Agora vamos falar sobre a merda acontece. Então, nós temos o código, ele até funciona. Pelo contrário, "funciona". Vejamos um exemplo simples:


public string Do(int x) { using (WebClient xx = new WebClient()) { return xx.DownloadString("https://some.super.url"); } } 

Este é um ótimo exemplo de código "funcional". Porque Como não leva em consideração que, mais cedo ou mais tarde, nosso ponto final cairá. Este exemplo não leva em consideração o chamado caso de borda - borderline, "casos ruins". Quando você começar a escrever código, pense no que pode dar errado. Na verdade, não estou falando apenas de chamadas remotas, mas de todos os recursos que estão fora do seu controle - entrada do usuário, arquivos, conexões de rede e até o banco de dados. Tudo o que pode quebrar vai quebrar no momento mais inoportuno, e a única coisa que você pode fazer com isso é estar preparado para isso o máximo possível.


Infelizmente, nem todos os problemas são tão óbvios. Há várias áreas problemáticas que quase garantem a geração de bugs. Por exemplo, trabalhando com código de idioma, com fusos horários. É dor e gritos "tudo funciona na minha máquina". Eles só precisam conhecer e trabalhar com eles com cuidado.


A propósito, sobre a entrada do usuário. Existe um princípio muito bom, que diz que qualquer entrada do usuário é considerada incorreta até prova em contrário. Em outras palavras, sempre valide o que o usuário digitou. E sim, no servidor também.


Total:


  • Primeiro faça o código funcionar,
  • Então faça ele ser bom
  • Não se esqueça dos casos extremos e do tratamento de erros.

Agora vamos falar sobre o suporte ao código


O suporte é um conceito complexo, mas eu incluiria três componentes aqui - o código deve ser fácil de ler, fácil de alterar e consistente.


Quem escreve os comentários em russo? Ninguém está escrevendo? Ótimo Em geral, um dos problemas é o código que não é o inglês. Não faça isso. Eu tinha um pedaço de código com aulas em norueguês e simplesmente não conseguia pronunciar o nome deles. Isso foi triste. Obviamente, apoiar esse código (para não noruegueses) não será uma tarefa trivial. Mas isso é raro.


Em geral, a facilidade de leitura é sobre nomeação e estrutura. Os nomes das entidades - classes, métodos, variáveis, devem ser simples, legíveis e ter significado. Veja o nosso exemplo anterior.


 public string Do(int x) { using (WebClient xx = new WebClient()) { return xx.DownloadString("https://some.super.url"); } } 

Você consegue entender o que o método Do faz apesar da implementação? Dificilmente. Da mesma forma com nomes de variáveis. Para entender que tipo de objeto xx você precisa procurar sua declaração. Isso leva nosso tempo, nos impede de entender o que, em termos gerais, acontece no código. Portanto, os nomes devem refletir a essência da ação ou significado. Por exemplo, se você renomear o método Do como GetUserName, o código se tornará um pouco mais claro e, em alguns casos, não precisaremos mais examinar sua implementação. Da mesma forma, com nomes de variáveis ​​no formato x e xx. É verdade que geralmente há exceções aceitas na forma de e para erros, i, k para contadores de ciclo, n para dimensões e muito mais.


Novamente, por exemplo, pegue o código que você escreveu há um mês e tente lê-lo fluentemente. Você entende o que está acontecendo lá? Se sim, parabenizo você. Caso contrário, você tem um problema com a legibilidade do código.


Em geral, há uma citação tão interessante:


"Existem apenas duas coisas difíceis na Ciência da Computação: invalidação de cache e nomeação de coisas." © Phil Karlton

Há apenas duas coisas complexas na Ciência da Computação: invalidação e nomeação de cache.


Lembre-se dela quando der nomes às suas entidades.


O segundo componente do código legível é sua complexidade ou estrutura. Estou falando daqueles que gostam de escrever seis ifas aninhados ou escrever um retorno de chamada em um retorno de chamada dentro do retorno de chamada. JavaScript ainda tem um termo chamado Callback Hell .


Falar sobre código perfeito é fácil, mas escrevê-lo é um pouco mais difícil. A coisa mais importante aqui é pelo menos não mentir para si mesmo. Se o seu código for ruim - não chame de doce, mas pegue e termine


Tenha pena de seus colegas e de si mesmo. Após uma semana, você precisará literalmente percorrer esse código para corrigir ou adicionar algo nele. Evitar isso não é tão difícil:


  • Escreva funções curtas,
  • Evite muitas ramificações ou aninhamentos,
  • Separe os blocos lógicos de código em funções separadas, mesmo que você não os reutilize,
  • Use polimorfismo em vez de se

Agora, vamos falar de mais uma coisa complicada: é fácil mudar esse código. Quem sabe o termo Grande bola de barro? Se alguém não estiver familiarizado - veja a foto.


Em geral, a esse respeito, eu realmente gosto de código aberto. Quando seu código está aberto para o mundo inteiro, de alguma forma, quero torná-lo pelo menos normal.


Cada módulo depende de cada módulo e as mudanças no contrato em um local provavelmente levarão ao advento da raposa polar ou, pelo menos, a uma depuração muito longa. Teoricamente, lidar com isso é bastante simples - reduza a dependência do seu código em seu próprio código. Quanto menos seu código souber sobre detalhes de implementação, melhor será para ele. Mas, na prática, é muito mais complicado e leva à re-complicação do código.


Na forma de dicas, eu colocaria desta maneira:


  • Oculte seu código o mais profundamente possível. Imagine que amanhã você terá que removê-lo manualmente do projeto. Quantos lugares você terá que consertar e quão difícil será? Tente minimizar essa quantidade.
  • Evite dependências circulares. Separe o código em camadas (lógica, interface, acesso a dados) e verifique se as camadas do nível "inferior" não dependem das camadas do nível "superior". Por exemplo, o acesso aos dados não deve depender da interface do usuário.
  • Agrupe a funcionalidade em módulos (projetos, pastas) e oculte as classes dentro deles, deixando apenas a fachada e as interfaces.

E desenhar. Basta desenhar em um pedaço de papel como os dados são processados ​​pelo aplicativo e quais classes são usadas para isso. Isso o ajudará a entender os lugares complicados antes que tudo se torne irreparável.


E, finalmente, sobre uniformidade. Sempre tente aderir ao estilo uniforme adotado pela equipe, mesmo que pareça errado para você. Isso se aplica à formatação e às abordagens para resolver o problema. Não use ~~ para arredondar, mesmo que seja mais rápido. A equipe não vai gostar. E ao começar a escrever um novo código, sempre observe o projeto, talvez algo que você precise já tenha sido implementado e possa reutilizá-lo.


Total:


  • Nomeação adequada
  • Boa estrutura
  • Uniformidade.

O código deve ser bastante produtivo


Vamos tomar um pouco de frio. O próximo requisito que consideraremos é que o código seja bastante produtivo.


O que quero dizer com a palavra "suficiente"? Provavelmente todo mundo já ouviu falar que otimizações prematuras são más, matam a legibilidade e complicam o código. Isso é verdade Mas também é verdade que você deve conhecer sua ferramenta e não escrever nela, para que o cliente de webmail carregue o Core I7 em 60%. Você deve conhecer os problemas típicos que levam a problemas de desempenho e evitá-los mesmo no estágio de escrever o código.


Vamos voltar ao nosso exemplo:


 public string GetUserName(int userId) { using (WebClient http = new WebClient()) { return http.DownloadString("https://some.super.url"); } } 

Este código tem um problema - download síncrono pela rede. Esta é uma operação de E / S que congela nosso fluxo até que seja executado. Em aplicativos de desktop, isso levará a uma interface dangling, e em aplicativos de servidor, à reserva de memória inútil e à exaustão do número de solicitações ao servidor. Apenas conhecendo esses problemas, você já pode escrever um código mais otimizado. E na maioria dos casos isso será suficiente.


Mas às vezes não. Portanto, antes de escrever o código, você precisa saber com antecedência quais requisitos foram definidos em termos de desempenho.


Agora vamos falar sobre os testes.


Este não é um tópico menos holístico do que o anterior, e talvez até mais. Tudo é complicado com os testes. Vamos começar com a declaração - acredito que o código deve ser coberto por um número razoável de testes.


Por que precisamos mesmo de cobertura e testes de código? Em um mundo ideal, eles não são necessários. Em um mundo ideal, o código é escrito sem erros e os requisitos nunca mudam. Mas como vivemos em um mundo distante do ideal, precisamos de testes para garantir que o código funcione corretamente (não há bugs) e que funcione corretamente após alterar alguma coisa. Esse é o benefício que os testes nos trazem. Por outro lado, mesmo 100% (devido às especificidades das métricas de cálculo) cobertos por testes não garantem que você cobriu absolutamente tudo. Além disso, cada teste adicional atrasa o desenvolvimento, pois após alterar o funcional, você também precisará atualizar os testes. Portanto, o número de testes deve ser razoável e a principal dificuldade consiste em encontrar um compromisso entre a quantidade de código e a estabilidade do sistema. Encontrar essa faceta é bastante difícil e não existe uma receita universal de como fazer isso. Mas existem algumas dicas que podem ajudá-lo a fazer isso.


  • Cobrir a lógica de aplicativos de negócios. A lógica de negócios é tudo para o qual um aplicativo é criado e deve ser o mais estável possível.
  • Cubra coisas complexas e calculadas. Cálculos, transformações, mesclagem de dados complexos. Um onde é fácil cometer um erro.
  • Cobrir insetos. Um bug é um sinalizador que indica que o código estava vulnerável aqui. E este é um bom lugar para escrever um teste aqui.
  • Cubra o código frequentemente reutilizado. É altamente provável que seja atualizado com freqüência e precisamos ter certeza de que adicionar uma coisa não prejudica a outra.

Não cubra sem muita necessidade


  • Bibliotecas estrangeiras - procure bibliotecas cujo código já esteja coberto por testes.
  • Infraestrutura - DI, mapeamento automático (se não houver mapeamento complicado) e assim por diante. Existem e2e ou testes de integração para isso.
  • Coisas triviais - atribuir dados a campos, encaminhar chamadas e assim por diante. Você certamente encontrará lugares muito mais úteis para cobri-los com testes.

Bem, é basicamente isso.


Para resumir. Bom código é


  • Código de trabalho
  • Fácil de ler
  • Fácil de mudar
  • Rápido o suficiente
  • E coberto em testes na quantidade certa.

Boa sorte desta maneira difícil. E provavelmente, você vai odiar isso. Bem, se ainda não, seja bem-vindo.


De fato, este é um mundo incrível de descobertas, oportunidades e criatividade. Aqui apenas na vizinhança existe o tédio da palmada, a melancolia de mais de 20 anos de código legado e um campo de muletas para termos duradouros. Então, eu prefiro chamá-lo de ponte de linha fina (C). Procure por projetos que você irá gravar. Tente fazer algo de bom. Simplesmente, faça do mundo um lugar melhor e tudo ficará bem.

Source: https://habr.com/ru/post/pt429732/


All Articles