Clean Code (Código limpo) – Formatação

Quando as pessoas olham o código-fonte, queremos que elas fiquem impressionadas com a limpeza, consistência e atenção aos detalhes. Queremos que eles fiquem impressionados com a ordem e queremos que suas sobrancelhas se levantem à medida que percorrem os módulos. No final, queremos que elas percebam que profissionais estiveram trabalhando nisso.

Se, em vez disso, virem uma massa de código embaralhada que parece ter sido escrita por um bando de marinheiros bêbados, é provável que concluam que a mesma falta de atenção aos detalhes permeia todos os outros aspectos do projeto.

O objetivo da formatação

A formatação do código é importante.

É muito importante para ser ignorada, e é muito importante tratar dela religiosamente. A formatação do código trata da comunicação, e a comunicação é a primeira prioridade do desenvolvedor profissional.

A funcionalidade que você cria hoje tem uma boa chance de mudar na próxima versão, mas a legibilidade do seu código terá um efeito profundo em todas as mudanças que serão feitas.

O estilo de codificação e a legibilidade estabelecem precedentes que continuam a afetar a capacidade de manutenção e extensibilidade muito depois de o código original ter sido alterado de forma irreconhecível.

Formatação vertical

Arquivos pequenos geralmente são mais fáceis de entender do que arquivos grandes. Embora não deva ser uma regra rígida é desejável que seus arquivos não tenham muito mais que 100 linhas de código.

Analisando projetos conhecidos, podemos ver que um terço dos arquivos tem entre 40 e 100+ linhas.

A comparação com o jornal

Um jornal é composto de muitos artigos; a maioria é muito pequena. Alguns são um pouco maiores. Muito poucos contêm tanto texto quanto uma página pode conter. Isso torna o jornal utilizável. Se o jornal fosse apenas uma longa história contendo uma aglomeração desorganizada de fatos, datas e nomes, simplesmente não o leríamos.

Gostaríamos que um código-fonte fosse como um artigo de jornal. O título deve ser simples, mas explicativo. O título, por si só, deve ser suficiente para nos dizer se estamos no módulo certo ou não. As partes superiores do código-fonte devem fornecer os conceitos e algoritmos de alto nível. Os detalhes devem aumentar conforme avançamos a leitura, até que no final encontraremos as funções de nível mais baixo e os detalhes do código-fonte.

Distancia vertical entre conceitos

Quase todo o código é lido da esquerda para a direita e de cima para baixo. Cada linha representa uma expressão ou cláusula, e cada grupo de linhas representa um pensamento completo. Esses pensamentos devem ser separados uns dos outros por linhas em branco.

Considere, por exemplo, o código abaixo. Existem linhas em branco que separam a declaração do pacote, a importação e cada uma das funções. Essa regra extremamente simples tem um efeito profundo no layout visual do código. Cada linha em branco é uma dica visual que identifica um conceito novo e separado. Conforme você examina, seus olhos são atraídos para a primeira linha que segue uma linha em branco.

using System;
 
namespace EntityFrameworkSample
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
        }

        static void Function()
        {
            Console.WriteLine("Hello Function!");
        }
    }
}

Proximidade vertical por associação

Se o distanciamento separa os conceitos, então a proximidade vertical implica uma associação próxima. Portanto, as linhas de código estreitamente relacionadas devem aparecer verticalmente densas.

Podemos ver o problema neste grupo de comentários inúteis no exemplo abaixo, distanciamento conceitos que deveriam estar próximos.

Ruim

namespace EntityFrameworkSample.Models
{
    public class GeometricShape
    {
        // The ID
        public long Id { get; set; }

        // The Geometric Shape Name
        public string Name { get; set; }

        // Other useless comment
        public bool IsRound { get; set; }

        public void Draw()
        {
            //Code
        }
    }
}

As propriedades de uma classe, por exemplo, devem ser mantidas juntas.

Bom

namespace EntityFrameworkSample.Models
{
    public class GeometricShape
    {
        public long Id { get; set; }
        public string Name { get; set; }
        public bool IsRound { get; set; }
    }
}

Distância vertical geral

Os conceitos intimamente relacionados devem ser mantidos verticalmente próximos uns dos outros. Obviamente, essa regra não funciona para conceitos que pertencem a arquivos separados. Mas conceitos intimamente relacionados não devem ser separados em arquivos diferentes, a menos que você tenha um bom motivo. Na verdade, esta é uma das razões pelas quais as variáveis protegidas devem ser evitadas.

Declarações de variáveis: As variáveis devem ser declaradas o mais próximo possível de seu uso. Como nossas funções são muito curtas, as variáveis locais devem aparecer no topo de cada função.

using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
 
namespace LastIterationLoop
{
    class ForLoopClass
    {
        public void IdentifyLastItem_UsingCountControl(List<SampleClass> myList)
        {
            var listSize = myList.Count;
            int i = 0;
            foreach (var item in myList)
            {
                if (++i == listSize)
                {
                    Console.WriteLine("LAST ITEM " + item);
                }
                else
                {
                    Console.WriteLine("NOT LAST " + item);
                }
            }
        }
    }
}

Variáveis de controle para loops geralmente devem ser declaradas dentro da instrução de loop que é o único responsável por manipulá-la.

Variáveis de instância: devem ser declaradas no topo da classe. Isso não deve aumentar a distância vertical dessas variáveis, porque em uma classe bem projetada, elas são usadas por muitos, senão todos, os métodos da classe.

O importante é que as variáveis de instância sejam declaradas em um local conhecido. Todos devem saber aonde ir para ver as declarações.

Funções dependentes: se uma função chamar outra, elas devem estar verticalmente próximas e quem realiza a chamada deve estar acima do receptor, se possível. Se a convenção for seguida de forma confiável, os leitores serão capazes de confiar que as definições de função seguirão logo após seu uso e isso dá ao programa um fluxo natural.

using System;
using System.Threading.Tasks;
using TokenControl.Models;

namespace TokenControl.Services
{
    public class CardControlsService : ICardControlsService
    {
        public async Task<CardControlDTO> ProcessCustomerCard()
        {
            //do the stuff
            return ReturnCardInformation(cardControl);
        }

        private CardControlDTO ReturnCardInformation(CardControl cardControl)
        {
            //do the stuff
            return returnCardinformation;
        }
    }
}

Afinidade conceitual: certos bits de código querem estar próximos de outros bits. Eles têm uma certa afinidade conceitual. Quanto mais forte essa afinidade, menor a distância vertical deve haver entre eles.

Como vimos, essa afinidade pode ser baseada em uma dependência direta, como uma função chamando outra ou uma função usando uma variável. Mas existem outras causas possíveis de afinidade. A afinidade pode ser causada porque um grupo de funções executam operações semelhantes.

public class Assert {
    static public void assertTrue(String message, boolean condition) {
        if (!condition)
            fail(message);
    }

    static public void assertTrue(boolean condition) {
        assertTrue(null, condition);
    }

    static public void assertFalse(String message, boolean condition) {
        assertTrue(message, !condition);
    }

    static public void assertFalse(boolean condition) {
        assertFalse(null, condition);
    }
}

Essas funções têm uma forte afinidade conceitual porque compartilham um esquema de nomenclatura comum e executam variações da mesma tarefa básica. O fato de se ligarem é secundário. Mas, mesmo que não o fizessem, elas ainda deveriam de ficar próximas.

Ordenação Vertical

Em geral, queremos que as dependências das chamadas de uma função apontem na direção descendente. Ou seja, uma função que é chamada deve estar abaixo de uma função que faz a chamada. Isso cria um bom fluxo descendente no módulo que estamos lendo.

Como em artigos de jornal, esperamos que os conceitos mais importantes surjam primeiro e que sejam expressos com o mínimo de detalhes poluentes. Esperamos que os detalhes de baixo nível venham por último. Isso nos permite examinar os arquivos de origem, obtendo a essência das primeiras funções, sem ter que mergulhar nos detalhes

Formatação Horizontal

Qual deve ser a largura de uma linha?
Para responder a isso, podemos olhar para o tamanho de linhas em programas conhecido. E os dados mostram que 40% das linhas estão entre 20 e 60 caracteres e que 30% é menor que 10 caracteres. Estes dados demonstram que os programadores claramente preferem linhas curtas.

O antigo limite de 80 caracteres é um pouco arbitrário, e não me oponho a linhas que chegam a 100 ou mesmo 120. Mas além disso provavelmente é apenas descuido e deve ser evitado.

Existia uma regra de que você nunca deveria rolar para a direita. Mas os monitores são muito largos para isso hoje em dia, e os programadores mais jovens podem reduzir a fonte para tão pequeno que podem colocar 200 caracteres na linha. Não faça isso.

Distancia horizontal e densidade

Usamos o espaço em branco horizontal para associar coisas que estão fortemente relacionadas e desassociar coisas que estão menos relacionadas.

Cerque os operadores de atribuição (=) com espaço em branco para acentuá-los. As atribuições têm dois elementos distintos e principais: o lado esquerdo e o lado direito. Os espaços tornam essa separação óbvia.

Por outro lado, não coloque espaços entre os nomes das funções e o parêntese de abertura ( void DoSomething(){} ). Isso ocorre porque a função e seus argumentos estão intimamente relacionados.

Separe os argumentos dentro do parêntese da chamada de função para acentuar a vírgula e mostrar que os argumentos são separados
( void DoSomething(int one, int two){} ).

Outro uso para o espaço em branco é acentuar a precedência dos operadores
(var total = left + rifgt;).

Alinhamento horizontal

Linguagens modernas possuem IDE avançadas que ajudam a manter os alinhamentos automaticamente. Então não se preocupe com esse tópico.

Recuo

Para tornar a hierarquia de escopos visível, recuamos as linhas do código-fonte na proporção de sua posição. As instruções no nível do arquivo, como a maioria das declarações de namespace, não são recuadas.

Os métodos dentro de uma classe são recuados um nível à direita da classe. As implementações desses métodos são implementadas em um nível à direita da declaração do método. As implementações de bloco são implementadas um nível à direita do bloco que o contém e assim por diante.

Os programadores dependem muito desse esquema de identação. Eles alinham visualmente as linhas à esquerda para ver em que escopo aparecem. Isso permite que eles pulem rapidamente os escopos, como implementações de instruções ‘if‘ ou ‘while‘, que não são relevantes para sua situação atual. Eles examinam a esquerda em busca de novas declarações de método, novas variáveis e até mesmo, novas classes.

Sem identação, os programas seriam praticamente ilegíveis por humanos.

Este é o mesmo código que usei em um exemplo anterior, mas sem a identação.

namespace LastIterationLoop { class ForLoopClass {public void IdentifyLastItem_UsingCountControl(List<SampleClass> myList){
var listSize = myList.Count;int i = 0;foreach (var item in myList){if (++i == listSize){Console.WriteLine("LAST ITEM " + item);
}else{Console.WriteLine("NOT LAST " + item);}}}}}

Quebrando recuo: Às vezes, é tentador quebrar a regra de recuo para instruções ‘if‘ curtas, laço de repetição ‘while‘ ou funções curtas. Sempre que sucumbi a essa tentação, quase sempre voltei e coloquei a reentrância.

Portanto, evito reduzir os escopos para uma linha como esta:

void print(int x) { Console.WriteLine(x); }

Scopes fictícios

Às vezes, o corpo de uma instrução é vazio, como mostrado abaixo. Eu não gosto desses tipos de estruturas e tento evitá-los. Quando não posso evitá-los, certifico-me de que o está devidamente recortado e cercado por colchetes.

Não sei dizer quantas vezes fui enganado por um ponto-e-vírgula sentado silenciosamente no final de um laço de repetição ‘while‘ de uma linha portando a menos que você torne esse ponto-e-vírgula visível recuando em sua própria linha, é muito difícil de notá-lo e por isso deve ser evitado.

while(true)
    ;

Regras da Equipe

Uma equipe de desenvolvedores deve concordar com um único estilo de formatação e, em seguida, cada membro dessa equipe deve usar esse estilo. Queremos que o software tenha um estilo consistente. Não queremos que pareça ter sido escrito por um bando de indivíduos discordantes.

Lembre-se de que um bom sistema de software é composto de um conjunto de documentos de leitura. Eles precisam ter um estilo consistente e suave. O leitor precisa ser capaz de confiar que os padrões de formatação que ele viu em um arquivo de origem significarão a mesma coisa em outros.

A última coisa que queremos fazer é adicionar mais complexidade ao código-fonte, escrevendo-o em uma mistura de diferentes estilos individuais.

Conclusão

Um código bem formatado é mais fácil de ler e entender. Vale lembra que os leitores neste caso são programadores, então grandes chances de já terem conhecimento de padrões de formatação que são definidos pelos mantenedores das linguagens. Por exemplo, o Visual Studio já mantêm e corrigi determinadas formatações que são padrão do C#.

Qualquer dúvida ou dicas, entre em contato: leandrolt@gmail.com

Referência
– Clean Code: A Handbook of Agile Software Craftsmanship (English Edition) – Robert C. Martin – Capítulo 5

Leave a Reply

Your email address will not be published. Required fields are marked *