quarta-feira, 16 de março de 2016

Tópicos relevantes do livro Código Limpo (Clean Code)

Tópicos relevantes do livro Código Limpo: Clean Code A Handbook of Agile Software Craftsmanship


O livro Código Limpo (Clean Code - A Handbook of Agile Software Craftsmanship) é basicamente um atalho pra você aprender as boas práticas dos grandes programadores poupando dores de cabeça e, principalmente, tempo e dinheiro. Eu mesmo, durante minha carreira relativamente curta de desenvolvimento de software já colecionava uma série de mandingas, cacoetes e práticas que poderiam ser (algumas) consideradas boas e outras eufêmicamente "teimosas".
Basicamente, quando leio um livro, gosto de ter à mão um lápis e uma caneta de grifar para poder destacar e anotar o que julgo importante. Também tenho um Kindle™ que me permite fazer isso de forma “ecologicamente correta”. Entretanto, desta vez o livro não era meu, então, tive que anotar minhas considerações em blocos de papel e, posteriormente, no documento que aqui compartilho convosco. Todavia, antes de começar, recomendo a todos que leiam o livro na íntegra, pois realmente vale cada um dos 60 reais que o meu brother William Lopes (conhecido pelas alcunhas Maica, Capi, Non, entre outros) pagou (acabei de consultar a Amazon.com e vi que está custando 170 hoje: http://www.amazon.com.br/Clean-Code-Handbook-Software-Craftsmanship/dp/0132350882).


A organização desse post segue-se mais ou menos assim: o título é o capítulo de onde retirei a informação e os tópicos são as práticas que considerei relevantes seguidas de algum comentário (in-) oportuno meu. Também vale a pena frisar que há muito mais no livro do que eu aqui vos transcrevo, no entanto, anotei os pontos que mais chamaram a atenção pra minha insignificante vida de programador.


Nomes
  1. Nomes significativos, independente do tamanho, o nome deve expressar o que a variável/método/classe se propõe a fazer. É melhor um nome do tipo somarOTotalPagoAFornecedores do que um método chamado somar e um comentário gigantesco, perecível e ignorável dizendo o que o dissilábico método faz. E é isso que a anotação seguinte expressa:
  2. Melhor um nome extenso e significativo do que um nome curto e confuso. Isso quer dizer que você não deve se preocupar em economizar caracteres para descrever o comportamento do seu método enquanto o nomeia. No entanto, cautela e bom-senso devem sempre predominar nas escolhas.
  3. Nem sempre é (ou não é) possível começar pelo ótimo, o ideal é fazer um brainstorming inicial e refatorar usando-se as ferramentas (IDE). Em outras palavras, você “vomita” o código funcional e depois faz o refactoring. Tentar começar pelo "código limpo" em muitas ocasiões me custou muito tempo, simplesmente pela minha vaidade de não colocar uns ifs dentro dos outros.
  4. O código deve contar uma história. Quem vai lê-lo deve entendê-lo na integra, sem auxílio de comentários e documentações. Aqui a dica vai além da nomenclatura dos artefatos. "Contar uma história facilmente compreensível" é basicamente a dica do livro.
  5. Os nomes devem ser refatorados com o tempo. Uma classe/função/método/variável pode perder o significado original, portanto, é sempre importante manter a nomenclatura em acordo com a intenção real e atual do método. Aqui cabe ressaltar o trabalho extra que os comentários geram. Eu, particularmente, até ler o livro, era um cara que gostava de enfeitar as classes com belos textos, às vezes até rebuscados, detalhando o funcionamento das minhas rotinas. Percebi (leia-se: fui convencido) mais tarde que esses comentários só serviam para ficar desatualizados.
  6. Nomes pronunciáveis são sempre melhores que nomes acrônomicos. AgentConsoleLoginRequest é melhor que ACLRequest. Obvio, não?.
  7. Classes → substantivos, métodos → verbos. Básico de qualquer curso de programação; classe define coisas e, os métodos, como essas coisas se comportam.
  8. Nomes devem ser significativos dentro do contexto (domínio) do software. A palavra "nota" pode ter significados diferentes em um sistema de vendas, um acadêmico e um sistema de anotações pessoais, por isso é sempre bom estar de acordo com o vocabulário do seu cliente. Vai por mim, já passei muita raiva porque o cliente mencionava uma funcionalidade ou artefato usando uma palavra e no software estar representada por outra. Usar um vocabulário comum é essencial.


Funções

  1. Devem ser pequenas! 5 ou 10 linhas é mais que o necessário. É isso! O Joshua Kerievsky fala no seu livro "Refatoração para Padrões" que a média de linhas dos métodos do seu sistema não devem ser maior do que 5 (“Your methods can be no longer than five lines of code”).
  2. Devem fazer somente o que se propõem a fazer.
  3. Devem fazer somente 1 tarefa. A 2 e a 3 se complementam. Um método denominado somar(a, b) deve somar a e b (ponto). Não tem que incrementar outras variáveis, não tem que validar se o usuário pode somar a com b, não tem redirecionar para outra página, não tem que fazer mais nada. Tem que somar a com b! Outra dica interessante é que se você usar “e” ou “ou” na nomenclatura do seu método tem grandes chances dele estar acumulando responsabilidades; na dúvida: refatore.
  4. Quanto menos parâmetros, melhor:
    1. zero parâmetros: o ideal;
    2. 1 parâmetro: bom;
    3. 2 parâmetros: aceitável;
    4. 3 parâmetros: ruim;
    5. 4 parâmetros: melhor repensar;
  5. Exceções são melhores do que códigos de erro. Concordamos que não devemos exibir um UnauthorizedException, na tela do usuário, mas ainda assim é melhor do que exibir um "error 403".
  6. Uma função que trata códigos de erro não deve fazer mais nada além de tratar os códigos de erro. Basicamente as anotações 1, 2 e 3 juntas.


Comentários

Essa foi pra mim a parte mais dolorosa: parar de comentar. Quando eu aprendi a fazer meu primeiro if no saudoso Clipper 5, mais ou menos no verão de 1996, comentários eram o que diferenciavam o bom programador do ruim; o cara organizado do porco relaxado. Acabei carregando esse hábito comigo para as outras linguagens: C, C++, PHP e mais tarde pro Java. Até pouco tempo atrás eu ficava bravo se alguém me falasse que comentar era ruim.
  1. Comentários são ruins. Isso é um fato, aceite.
  2. Javadoc é aceitável, mas também é ruim. Geralmente são gerados pelas IDEs aqueles cabeçalhos de métodos entre “/**” e “*/” com as meta-variáveis @param, @see, @author, etc. Isso é de certa forma, segundo o autor, aceitável, porém desencorajado. Você acaba refatorando o método e se esquece de alterar a quantidade de parâmetros, tipos, etc; é justamente isso que diz a a notação seguinte:
  3. O programador acaba alterando a função mas não a documentação.
  4. A regra básica é: sempre que precisar comentar, refatore. Resumidamente:  "se você se sentir tentado a colocar um comentário para explicar seu código, é porque ele não está compreensível" (coloquei entre aspas porque não me lembro quem disse isso).
  5. Um tipo de comentário válido é aquele que expressa algo que não pode ficar claro no código: uma decisão de negócio, por exemplo:
// desculpe, me obrigaram a fazer isso


Formatação

  1. Melhora a legibilidade.
  2. Deve ser empregada por toda a equipe de maneira consensual.  “Ao ler um código, não ser possível identificar quem o escreveu”. Todos devem praticar de forma igual. Eu costumo usar o padrão da IDE para evitar entrar numa luta recursiva toda vez que alguém usar o recurso de auto-formação antes de commitar uma classe.
  3. Espaçamentos devem ser padronizados. Entre métodos, entre declarações de variáveis, entre parênteses, entre chaves, enfim, reúna a equipe e entre em um acordo sobre o que deverá ser empregado como padrão de espaçamento, redija um documento e disponibilize à todos.
  4. Separar contextos dentro da classe. Declaração de variáveis estáticas privadas de públicas; declaração de variáveis dos métodos e por aí vai. No meu caso, uso somente uma quebra de linha simples para separar os contextos, sem traços, pontos ou aquelas horrorosas série de sinais de igualdade (ex: “=======”).
  5. Variáveis devem ser declaradas próximo de onde serão usadas (dentro do método). Eu aprendi a declarar as variáveis nos inícios dos métodos, quando comecei a programar em C. Não tenho certeza se isso era um requisito da linguagem, mas sei que me acostumei a fazer isso e acabou sendo outra mania que migrei para as demais linguagens. Não o faça! Declare suas variáveis próximas aos pontos onde você irá utilizá-las.
  6. Métodos devem ser posicionados imediatamente abaixo dos métodos que os chamam. Se um método A chama um método B, posicione o método B imediatamente abaixo do método A. Isso facilita a leitura de quem pegar seu código no futuro. Geralmente as ferramentas de refactoring já fazem isso automaticamente quando você vai extrair um bloco de código para um método.
  7. Linhas devem ser quebradas em qualquer posição entre 100 e 120 caracteres (eu uso 128... porque sim). Tanto no Netbeans quanto no Eclipse, eu coloco a margem da direita na posição 128 e NUNCA, repito, NUNCA deixo meu código ultrapassá-la. Acho que isso é uma prática que veio das telas 80x24 (tipo Clipper) que a galera usava para programar antigamente; 80 colunas por 24 linhas. Assim, para uma melhor visualização do código, os programadores recomendavam não ultrapassar a coluna 80 para não gerar rolagem horizontal. Hoje, com as telas alta resolução isso não se faz mais necessário, mas o autor acha importante quebrar a linha em algum ponto entre o começo e o fim do seu “range” visual.
  8. Aninhamento horizontal são ruins porque geralmente as IDEs não os respeitam. Aquelas declarações de variáveis, enums e qualquer outra coisa que você tenta alinhar em colunas, colocando os sinais de igual exatamente um abaixo do outro, não funcionam. O primeiro aloprado que usar a formatação automática vai destruir em milésimos de segundos os minutos preciosos que você gastou tentando alinhá-las. Eu adotei uma regra simples: não brigue com a IDE.


Estrutura de dados

  1. Um objeto não deve expor seus atributos internos. A primeira ideia que vem a mente ao ler isso é gerar métodos get e set para todos os atributos, o que é de certa forma válido, mas ainda devemos considerar a observação seguinte:
  2. Getters e setters acabam quebrando o encapsulamento quando indiscriminadamente usados. A lição aqui é ser cauteloso no que sua classe vai expor. Talvez não haja a necessidade da sua classe externar atributos como precoCusto e margemDeLucro quando você pode simplesmente expor um método que retorna o preço final (eu sei que foi um exemplo medíocre, desculpa).
  3. Lei de Demeter: o módulo não deve enxergar o interior do objeto que manipula. Uma função f de uma classe c só deve chamar
    1. métodos de c;
    2. objetos usados por f;
    3. objetos passados por parâmetro para f;
    4. variáveis de instância de c.
Esse assunto por si só, rende um post inteiro, portanto, se quiser se aprofundar mais, dá uma olhada nesse post aqui.
  1. Train Wrecks (acidentes ferroviários) ocorrem quando há encadeamento de chamada de métodos. Quando há encadeamento, melhor separá-los em variáveis locais conhecidas:
a.getB().getC();
deve ficar:
B b = a.getB();
C c = b.getC();
No entanto, o ideal é que a classe A retorne o objeto esperado a partir de um método adequadamente nomeado e especificado (comportamento estaria em a):
a.getQualquerCoisaQueBFazComC();
  1. Objetos DTO não devem possuir comportamentos. São burros, ou como o pessoal gosta de dizer: anêmicos.


Exceções e erros

  1. Sempre preferir exceções ao invés de retornar códigos de erro. Como foi mencionado em uma nota anterior, é melhor lançar uma UnauthorizedException do que um “Error 403”.
  2. Sempre optar por exceções não verificadas. Exceções verificadas propagam alterações para níveis que não deveriam, por exemplo: um método que passa a lançar uma nova exceção verificada deverá alterar todas as estruturas de outros níveis que o usa.
  3. Contextualize as exceções. Geralmente um tipo de exceção por módulo é mais do que o suficiente.
  4. Nunca retorne null. Retornar null implica em verificação if-null por todo o código, sem contar as atormentadoras NullPointerExceptions que fazem qualquer programador se sentir um newbie.
  5. Null deve ser encarado como uma condição anormal. Você deve considerar que um null em qualquer ponto do código é algo anormal e deve ser verificado.
  6. Nunca passar ou receber null como parâmetro.


Classes

  1. Devem ser pequenas. Pode ser difícil definir “pequena” aqui, mas considere a dica do  Joshua Kerievsky no "Refatoração para Padrões" sobre o tamanho médio dos métodos para mensurar suas classes.
  2. Possuir um único propósito e ter a responsabilidade bem definida (coesão). Uma dica: geralmente quando a descrição (Javadoc, por exemplo) de uma classe usa “e” para descrevê-la, significa que ela possui mais de uma responsabilidade.
  3. Escolher um bom nome pode ser a forma exata de separar adequadamente as responsabilidades.
  4. Recomenda-se começar declarando variáveis públicas e estáticas (constantes) seguidas pelas privada e estáticas, seguidas pelas, seguidas pelas variáveis de instância privadas.


E é isso. As práticas acima, como eu disse, são só um amontoado de anotações que eu considerei importantes  para usar no meu dia a dia  enquanto eu lia; nem sequer abrange o conteúdo todo do livro. Me arrependi muito de ter negligenciado tal material por tanto tempo e se há um livro de TI que eu recomendaria para alguém que está iniciando sua carreira, não tenho dúvidas que seria esse.




Nenhum comentário: