Código Limpo de Robert Martin. Resumo. Como escrever um código claro e bonito?

Decidi escrever um compêndio do livro, que todos conhecem, e o autor chama de "Escola dos Professores de Código Puro". O olhar de Martin parece estar dizendo:

“Eu vejo através de você. Você não segue os princípios do código limpo novamente? ”

imagem

Capítulo 1. Código Limpo


O que é esse código de versão Martin mais limpo em poucas palavras? É um código sem duplicação, com um número mínimo de entidades, fácil de ler, simples. Como lema, pode-se escolher: "A clareza está acima de tudo!".

Capítulo 2. Nomes Significativos


Os nomes devem transmitir as intenções do programador


O nome de uma variável, função ou classe deve indicar por que essa variável existe, o que faz e como é usada. Se o nome exigir comentários adicionais, ele não transmitirá as intenções do programador. É melhor escrever exatamente o que é medido e em quais unidades.

Um exemplo de um bom nome de variável: daysSinceCreation;
Objetivo: remover a não-obviedade.

Evite desinformação


Não use palavras com significados ocultos que não sejam o pretendido. Cuidado com diferenças sutis nos nomes. Por exemplo, XYZControllerForEfficientHandlingOfStrings e XYZControllerForEfficientStorageOfStrings.

Exemplos verdadeiramente assustadores de nomes desinformados são encontrados ao usar o "L" minúsculo e o "O" maiúsculo nos nomes de variáveis, especialmente em combinações. Naturalmente, surgem problemas devido ao fato de que essas letras quase não diferem das constantes "1" e "0", respectivamente.

Use diferenças significativas


Se os nomes forem diferentes, eles deverão indicar conceitos diferentes.

As "séries de números" da forma (a1, a2, ... aN) são o oposto da nomeação consciente. Eles não carregam informações e não dão uma idéia das intenções do autor.

Palavras não informativas são redundantes. A palavra variável nunca deve aparecer nos nomes das variáveis. A tabela de palavras nunca deve aparecer nos nomes das tabelas. Por que NameString é melhor que Name? Um nome pode ser, digamos, um número real?

Use nomes de ortografia: generationTimestamp é muito melhor que genymdhms.

Escolha nomes pesquisáveis


Nomes de letra única podem ser usados ​​apenas para variáveis ​​locais em métodos curtos.

Evite esquemas de codificação de nomes


Normalmente, os nomes codificados são pouco pronunciados e é fácil digitar erros de digitação.

Interfaces e implementações


Eu (o autor do livro) prefiro deixar nomes de interface sem prefixos. O prefixo I, tão comum no código antigo, na melhor das hipóteses, distrai e, na pior, transmite informações desnecessárias. Não vou dizer aos meus usuários que eles estão lidando com uma interface.

Nomes de classe


Os nomes de classe e objeto devem ser substantivos e suas combinações: Cliente, WikiPage, Conta e AddressParser. Evite usar palavras como Gerente, Processador, Dados ou Informações nos nomes das classes. O nome da classe não deve ser um verbo.

Nomes dos métodos


Os nomes dos métodos são verbos ou frases verbais: postPayment, deletePage, save, etc. Os métodos e predicados de leitura / gravação são formados a partir do valor e do prefixo get, set e estão de acordo com o padrão javabean.

Abster-se de trocadilhos


A tarefa do autor é tornar o código o mais claro possível. O código deve ser percebido rapidamente, sem a necessidade de um estudo cuidadoso. Concentre-se no modelo de literatura popular, em que o próprio autor deve expressar livremente seus pensamentos.

Adicione um contexto significativo.


O contexto pode ser adicionado usando os prefixos: addrFirstName, addrLastName, addrState, etc. Pelo menos o leitor de código entenderá que as variáveis ​​fazem parte de uma estrutura maior. Obviamente, seria mais correto criar uma classe chamada Address, para que até o compilador saiba que as variáveis ​​fazem parte de algo mais.

Variáveis ​​com contexto pouco claro:

private void printGuessStatistics(char candidate, int count) { String number; String verb; String pluralModifier; if (count == 0) { number = "no"; verb = "are"; pluralModifier = "s"; } else if (count == 1) { number = ~_~quot quot~_~; verb = "is"; pluralModifier = ""; } else { number = Integer.toString(count); verb = "are"; pluralModifier = "s"; } String guessMessage = String.format( "There %s %s %s%s", verb, number, candidate, pluralModifier ); print(guessMessage); } 

A função é um pouco longa e as variáveis ​​são usadas o tempo todo. Para dividir a função em fragmentos semânticos menores, você deve criar a classe GuessStatisticsMessage e transformar três variáveis ​​nos campos dessa classe. Dessa forma, forneceremos um contexto óbvio para as três variáveis ​​- agora é absolutamente óbvio que essas variáveis ​​fazem parte do GuessStatisticsMessage.

Variáveis ​​com contexto:

 public class GuessStatisticsMessage { private String number; private String verb; private String pluralModifier; public String make(char candidate, int count) { createPluralDependentMessageParts(count); return String.format( "There %s %s %s%s", verb, number, candidate, pluralModifier ); } private void createPluralDependentMessageParts(int count) { if (count == 0) { thereAreNoLetters(); } else if (count == 1) { thereIsOneLetter(); } else { thereAreManyLetters(count); } } private void thereAreManyLetters(int count) { number = Integer.toString(count); verb = "are"; pluralModifier = "s"; } private void thereIsOneLetter() { number = ~_~quot quot~_~; verb = "is"; pluralModifier = ""; } private void thereAreNoLetters() { number = "no"; verb = "are"; pluralModifier = "s"; } } 

Não adicione contexto redundante


Nomes abreviados geralmente são melhores que nomes longos, se apenas o seu significado estiver claro para o leitor de código. Não inclua mais contexto do que o necessário no nome.

Capítulo 3. Funções


Compact!


Primeira regra: as funções devem ser compactas.
A segunda regra: as funções devem ser ainda mais compactas.

Minha experiência prática me ensinou (à custa de muitas tentativas e erros) que as funções devem ser muito pequenas. É desejável que o comprimento da função não exceda 20 linhas.

Regra de uma operação


Uma função deve executar apenas uma operação. Ela deve executar bem. E ela não deve fazer mais nada. Se uma função executar apenas as ações que estão no mesmo nível com o nome declarado da função, essa função executará uma operação.

Seções de Função


Uma função que executa apenas uma operação não pode ser significativamente dividida em seções.

Um nível de abstração por função


Para garantir que a função execute “apenas uma operação”, é necessário verificar se todos os comandos da função estão no mesmo nível de abstração.

Misturar níveis de abstração dentro de uma função sempre cria confusão.

Lendo código de cima para baixo: regra de downgrade


O código deve ler como uma história - de cima para baixo.

Cada função deve ser seguida por funções do próximo nível de abstração. Isso permite que você leia o código, diminuindo sequencialmente os níveis de abstração enquanto lê a lista de funções. Eu chamo essa abordagem de "regra de downgrade".

Comandos de troca


Escrever um comando de switch compacto é bastante difícil. Mesmo um comando switch com apenas duas condições ocupa mais espaço do que um único bloco ou função deve ocupar, na minha opinião. Também é difícil criar um comando switch que faça uma coisa - por natureza, os comandos switch sempre executam N operações. Infelizmente, os comandos do switch nem sempre são dispensados, mas pelo menos podemos garantir que esses comandos estejam ocultos em uma classe de baixo nível e não duplicados no código. E, claro, o polimorfismo pode nos ajudar com isso.

O exemplo mostra apenas uma operação, dependendo do tipo de funcionário.

 public Money calculatePay(Employee e) throws InvalidEmployeeType { switch (e.type) { case COMMISSIONED: return calculateCommissionedPay(e); case HOURLY: return calculateHourlyPay(e); case SALARIED: return calculateSalariedPay(e); default: throw new InvalidEmployeeType(e.type); } } 

Esse recurso tem várias desvantagens. Primeiro, é ótimo e, com a adição de novos tipos de trabalhadores, ele crescerá. Em segundo lugar, ele obviamente executa mais de uma operação. Em terceiro lugar, viola o princípio da responsabilidade única, pois possui várias razões possíveis para a mudança.

Quarto, viola o Princípio Aberto Fechado, porque o código de função deve mudar toda vez que novos tipos são adicionados.

Mas talvez a desvantagem mais séria seja que o programa possa conter um número ilimitado de outras funções com uma estrutura semelhante, por exemplo:

isPayday (Empregado e, Data da data)

ou

deliverPay (Empregado e, Pagamento em dinheiro)

e assim por diante.

Todas essas funções terão a mesma estrutura defeituosa. A solução para esse problema é enterrar o comando switch na base da fábrica abstrata e não mostrá-lo a ninguém. A fábrica usa o comando switch para criar as instâncias apropriadas dos descendentes de Employee, e chama as funções calcularPay, isPayDay, deliverPay, etc., passam transferência polimórfica pela interface Employee.

 public abstract class Employee { public abstract boolean isPayday(); public abstract Money calculatePay(); public abstract void deliverPay(Money pay); } ----------------- public interface EmployeeFactory { public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType; } ----------------- public class EmployeeFactoryImpl implements EmployeeFactory { public Employee makeEmployee(EmployeeRecord r) throws InvalidEmployeeType { switch (r.type) { case COMMISSIONED: return new CommissionedEmployee(r) ; case HOURLY: return new HourlyEmployee(r); case SALARIED: return new SalariedEmploye(r); default: throw new InvalidEmployeeType(r.type); } } } 

Minha regra geral sobre os comandos do switch é que esses comandos são válidos se ocorrerem uma vez no programa, são usados ​​para criar objetos polimórficos e se escondem atrás dos relacionamentos de herança para permanecerem invisíveis para o resto do sistema. Obviamente, não há regras sem exceções e, em algumas situações, é necessário violar uma ou mais condições desta regra.

Use nomes significativos


Metade do esforço para implementar esse princípio se resume a escolher bons nomes para funções compactas que executam uma única operação. Quanto menor e mais especializada a função, mais fácil é escolher um nome significativo para ela.

Não tenha medo de usar nomes longos: um nome longo e significativo é melhor que um nome curto e obscuro. Escolha um esquema que facilite a leitura das palavras no nome da função e crie um nome a partir dessas palavras que descreva o objetivo da função.

Argumentos de Função


No caso ideal, o número de argumentos da função é zero. A seguir, são apresentadas funções com um argumento (unário) e com dois argumentos (binário). Funções com três argumentos (ternários) devem ser evitadas sempre que possível.

Os argumentos de saída confundem a situação ainda mais rápido que a entrada. Como regra, ninguém espera que uma função retorne informações em argumentos. Se você não conseguir gerenciar sem argumentos, tente pelo menos limitar-se a um argumento de entrada.

Conversões que usam um argumento de saída em vez de um valor de retorno confundem o leitor. Se a função converter seu argumento de entrada, o resultado
deve ser passado no valor de retorno.

Argumentos de sinalizadores


Os argumentos do argumento são feios. Passar o significado lógico de uma função é um hábito verdadeiramente terrível. Implica imediatamente a assinatura do método, proclamando em voz alta que a função executa mais de uma operação. Se o sinalizador for verdadeiro, uma operação será executada e, se falso, outra.

Funções binárias


Uma função com dois argumentos é mais difícil de entender do que uma função unária. Obviamente, em algumas situações, uma forma de dois argumentos é apropriada. Por exemplo, chamando o ponto p = novo ponto (0,0); absolutamente razoável. No entanto, dois argumentos no nosso caso são componentes ordenados com o mesmo valor.

Objetos como argumentos


Se uma função receber mais de dois ou três argumentos, é altamente provável que alguns desses argumentos sejam empacotados em uma classe separada. Considere as duas declarações a seguir:

 Circle makeCircle(double x, double y, double radius); Circle makeCircle(Point center, double radius); 

Se as variáveis ​​forem transferidas juntas como um todo (como as variáveis ​​xey neste exemplo), provavelmente, juntas, elas formarão um conceito que merece seu próprio nome.

Verbos e Palavras-chave


A escolha de um bom nome para uma função pode explicar amplamente o significado da função, bem como a ordem e o significado de seus argumentos. Nas funções unárias, a própria função e seu argumento devem formar um par natural de verbo / substantivo. Por exemplo, uma chamada no formulário write (name) parece muito informativa.

O leitor entende que não importa qual seja o "nome", ele está em algum lugar "escrito". Melhor ainda é o registro writeField (nome), que informa que o "nome" é gravado no "campo" de alguma estrutura.

A última entrada é um exemplo do uso de palavras-chave em um nome de função. Nesse formulário, os nomes dos argumentos são codificados no nome da função. Por exemplo, assertEquals pode ser gravado como assertExpectedEqualsActual (esperado, real). Isso resolve em grande parte o problema de lembrar a ordem dos argumentos.

Separação de comandos e solicitações


Uma função deve fazer algo ou responder a alguma pergunta, mas não simultaneamente. A função altera o estado do objeto ou retorna informações sobre esse objeto. A combinação de duas operações geralmente cria confusão.

Isolar blocos try / catch


Os blocos try / catch parecem muito feios. Eles confundem a estrutura do código e misturam a manipulação de erros com o processamento normal. Por esse motivo, recomenda-se que os corpos dos blocos try e catch sejam alocados em funções separadas.

Manipulação de erro como uma operação


As funções devem executar uma operação. O tratamento de erros é uma operação. Isso significa que a função que processa erros não deve fazer mais nada. Daqui resulta que, se a palavra-chave try estiver presente na função, ela deverá ser a primeira palavra na função e, após os blocos catch / finalmente, não haverá mais nada.

Isso conclui o capítulo 3.

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


All Articles