/ Getting Started

Clean Code - Funções

Olá pessoal, no artigo anterior abordamos Nomes Significativos, este primeiro artigo traz recomendações de como tornar seu código legível. Agora podemos iniciar a segunda etapa citada nos 3 R's, a reutilização de funcionalidades. Nesta segunda etapa de artigos sobre Clean Code abordaremos os melhores conceitos para a criação de funções reutilizáveis.

Funções Devem ser Pequenas

O livro nos aconselha a construir funções cada vez menores, pois são mais fáceis de realizar manutenção. Na década de 1980, era de costume dizer que uma função não deveria ser maior que a tela, isso era em torno de 24 linha por 80 colunas, hoje em dia temos a possibilidade de colocar 150 caracteres em uma linha e em torno de 100 linhas por tela. Porém a recomendação é: As funções devem ter no máximo 20 linhas.

Blocos de indentação

É fortemente recomendado que blocos dentro de instruções if, else, while e outros devem ter apenas uma linha, possivelmente seja uma chamada de outra função. Esta recomendação mantém a função pequena e adiciona um valor significativo, pois a função chamada dentro do bloco deve conter um nome descritivo. Esta recomendação também implica que as funções não devem ser grandes e possuir vários níveis de aninhamento, ocasionando em um nível de indentação de no máximo dois blocos.

Faça apenas uma coisa

No SRP(Princípio de Responsabilidade Única) há uma regra sobre funções de uma única ação. Quando se tem mais de uma ação sendo executada dentro de uma única função pode-se gerar erros difíceis de serem encontrados e é mais propenso a erros. As funções devem fazer uma coisa e devem fazê-la bem.

No início do projeto pode-se realizar um mapeamento de ações que devem ser implementados e por meio desse mapeamento podemos decompor as ações em funções menores, pensando sempre em fazer apenas uma ação. A implementação dentro de uma função deve corresponder ao seu nome, portanto se algum trecho dentro desta função não corresponde a ideia que está sendo passada pelo nome, a função não atende a esta regra!

Observe o exemplo a seguir:
function emailClients(clients) {
    clients.forEach((client) => {
        const clientRecord = database.lookup(client);
        if (clientRecord.isActive()) {
              email(client);
        }
    });
}

Esta função faz a violação da regra, pois realiza duas tarefas dentro de sua estrutura. A primeira tarefa executada realiza a busca de clientes e a segunda tarefa faz o envio de email para todos os clientes ativos. A melhor maneira de se corrigir este problema é reescrevendo a função em duas partes, como exemplificado a seguir:

function emailActiveClients(clients) {
  clients
    .filter(isActiveClient)
    .forEach(email);
}

function isActiveClient(client) {
  const clientRecord = database.lookup(client);
  return clientRecord.isActive();
}

Criando a nova função é notável uma melhoria sobre a leitura da função, sabemos exatamente o que elas executam e tornamos a manutenção deste trecho mais simplificada.

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

A fim de confirmar se nossa função tem uma responsabilidade, é necessário verificar se todas as instruções dentro da função estão no mesmo nível de abstração. O nível de abstração diz respeito aos conceitos implementados dentro da função, deve-se deixar bem claro o nível de importância sobre aquele trecho separando meros detalhes de conceitos extremamente importantes.

public static String testableHtml(
PageData pageData,
boolean includeSuiteSetup
) throws Exception {
    WikiPage wikiPage = pageData.getWikiPage();
    StringBuffer buffer = new StringBuffer();
    if (pageData.hasAttribute("Test")) {
        if (includeSuiteSetup) {
            WikiPage suiteSetup =
            PageCrawlerImpl.getInheritedPage(
            SuiteResponder.SUITE_SETUP_NAME, wikiPage
        );
        if (suiteSetup != null) {
        WikiPagePath pagePath =
        suiteSetup.getPageCrawler().getFullPath(suiteSetup);
        String pagePathName = PathParser.render(pagePath);
        buffer.append("!include -setup .")
        .append(pagePathName)
        .append("\n");
    }
}

Neste exemplo do livro pode-se notar que a função possui tanto ações de nível de abstração alto, como:

String pagePathName = PathParser.render(pagePath);

mas também possui níveis mais baixos, como:

append("\n").

Regra decrescente

Como em uma narrativa, queremos que o código seja lido de cima para baixo e que cada próximo conceito passe a ideia de um contexto maior que se complementa a cada novo trecho. Cada parágrafo deve descrever o nível atual e fazer referência aos parágrafos consecutivos do próximo nível. Temos a orientação da leitura nas direções da esquerda para a direita e de cima para baixo, devemos facilitar aos usuários a leitura dessa forma padrão.

Use nomes descritivos

Na primeira seção sobre Clean Code ressaltamos a importância dos nomes para nosso projeto, essa importância está presente neste capítulo sobre funções. Os nomes atribuídos às funções devem se auto explicativos e claros, devem transmitir ao leitor a ideia colocada pelo desenvolvedor dentro daquela função. Não tenha medo de criar nomes extensos para as funções, pois estes são melhores que nomes pequenos e enigmáticos e também poupa o trabalho de adicionar comentários de descrição sobre as funções. Lembre-se sempre das convenções de nomenclaturas, elas possibilitam a melhor leitura dos nomes.

function addToDate(date, month) {
  // ...
}

const date= new Date();

addToDate(date, 1);

Neste exemplo a função indica que está adicionando um elemento ao objeto date, porém é difícil dizer pelo nome da função o que está sendo adicionado. Ela pode ser reescrita da seguinte forma:

function addMonthToDate(month, date) {
  // ...
}

const date = new Date();
addMonthToDate(1, date);

Seja consistente na escolha de nomes e lembre-se de sempre revisar para que sejam aplicadas melhorias.

Parâmetros de funções

A quantidade ideal de parâmetros para uma função é de zero, seguida por um parâmetro, depois dois parâmetros e assim por diante. Deve-se, sempre que possível, evitar funções com três parâmetros, apenas com motivos muito especiais devemos ignorar essa regra. Esta regra é imposta por ser uma tarefa difícil complementar o nome da função com os nomes dos parâmetros.

Parâmetros são complicados, eles requerem muito conceito. Um parâmetro de entrada é a melhor coisa depois do zero parâmetro. É mais fácil entender a função includeSetupPage() do que includeSetupPage(newPage-Content).

Funções mônades comuns

As funções citadas no livro como mônades, são funções que possuem um único parâmetro. Elas são utilizadas quando, você está fazendo uma pergunta sobre aquele parâmetro, como em boolean fileExists("Myfile"), ou você pode trabalhar sobre aquele parâmetro transformando-o em outro elemento ou retornando-o. Para exemplificar, o seguinte trecho: InputStream fileOpen("MyFile") transforma a String do nome de um arquivo em um valor retornado por InputStream. Tenha em mente que os nomes dados a essas funções mônades devem expressar qual das utilizações está sendo implementada na função.

Parâmetros lógicos

Mais conhecidos como flags, não passam de valores booleanos passados a uma função. Certamente é uma prática horrível, pois viola a regra de que a função deve ter apenas uma responsabilidade. caso o falor seja falso ela terá um comportamento, caso contrário será outro comportamento diferente. O ideal é que sejam divididas em duas funções que são responsáveis para cada estado da flag.

Funções díades

Como mencionado anteriormente, funções díades possuem uma dificuldade maior na leitura em comparação com funções mônades. Há casos que dois parâmetros são necessários e devemos realizar uma análise sobre os parâmetros passados, filtrando a utilização apenas em casos que os parâmtros são valores idenpendentes e necessários.

As funções díades não são ruins e de alguma forma você precisará usá-las, entretanto o uso pode ocasionar em erros simples que podem gerar erros complexos como a troca de ordem dos parâmetros. Esses riscos devem ser vistos na utilização e uma atenção maior deve ser dada a esse tipo de função.

Funções tríades

Devemos evitar ao máximo o uso desse tipo de funções, além de ser consideravelmente mais difíceis de entender, ainda possuem questões de ordenação e pausa que são mais recorrentes.

Evite efeitos colaterais

Efeitos colaterais são mentiras ocasionadas pelo contexto da função. Se sua função atende a regra de fazer apenas uma coisa, não teremos efeitos colaterais pois sabemos bem o que está sendo executado. Ações a mais em uma função podem fazer alterações desnecessárias aos parâmetros conhecidos, sejam os da função ou globais.

let name = 'Ryan McDermott';

function splitIntoFirstAndLastName() {
  name = name.split(' ');
}
splitIntoFirstAndLastName();

console.log(name); // ['Ryan', 'McDermott'];

Visualize este exemplo e foque na variável name, caso seja necessário reutilizá-la em outro trecho teríamos um array de String que levaria à quebra do código.

Podemos evitar esse problema da seguinte forma:

function splitIntoFirstAndLastName(name) {
  return name.split(' ');
}

const name = 'Ryan McDermott';
const newName = splitIntoFirstAndLastName(name);

console.log(name); // 'Ryan McDermott';
console.log(newName); // ['Ryan', 'McDermott'];

Separação comando-consulta

As funções devem fazer ou responder algo, nunca ambos. Lembre-se de criar funcionalidades fazendo a separação entre a alteração de informações e retorno delas, efetuar as duas tarefas costuma gerar confusão.

Evite Duplicação

Faça uma análise das funcionalidades implementadas e caso note a semelhança entre ações, transforme em uma só. A duplicação no código pode passar despercebida e isso leva a maiores oportunidades para a omissão de erros.

A duplicação pode ser a raiz de todo o mal no software, muitos princípios e práticas têm sido criados com a finalidade de controlá-la ou eliminá-la.Considere que a programação orientada a objeto serve para centralizar o código em classes-base que seriam redundantes em outro conceito.

Como escrever funções?

Criar um software é como qualquer outro tipo de escrita. ao escrever um artigo você primeiro coloca seus pensamentos no papel e depois organiza de modo que fiquem fáceis de ler. O primeiro rascunho pode ficar desastroso e desorganizado, então você o molda, reestrutura e refina até que fique como deseja.

A escrita de funções começam longas e complexas, com muitas indentações e loops aninhados; com longas listas de parâmetros e nomes arbitrários. A partir desse caos devemos reorganizar e refinar o código. As funções enormes são quebradas em menores, os nomes são trocados por nomes mais explicativos, elimina-se a duplicação.

Conclusão

Cada sistema é construído a partir de uma linguagem específica a um domínio, desenvolvida por programadores para descrever um sistema. as funções são os verbos da linguagem e classes são os substantivos, então promova seu código a um nível de entendimento simplificado ao leitor.
Programadores experientes vêem seus sistemas como histórias a serem contadas e usam recursos da linguagem de programação para enriquecer este enredo.

Este capítulo falou sobre como escrever bem as funções, basicamente suas funções serão curtas, bem nomeadas e bem organizadas e jamais esqueça que o verdadeiro objetivo é contar a história do sistema e que é necessária uma perfeita sincronia entre as funções agregadas a uma linguagem clara e precisa que irá lhe ajudar na narração.

Referências

Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship.
Disponível em https://www.investigatii.md/uploads/resurse/Clean_Code.pdf.

Augusto, Felipe. Conceitos de Código Limpo.
Disponível em https://github.com/felipe-augusto/clean-code-javascript#Índice