Clean Code (Código Limpo) – Funções

Funções são a primeira linha de organização de qualquer programa. E por isso é importante elas serem bem escritas. Para isso temos que responder, enquanto escrevemos nosso código, as seguintes perguntas:
– Esta fácil de ler e entender?
– Como eu faço para a função comunicar sua intenção?
– Quais atributos ela deve receber para permitir um leitor casual possa entender oque ela faz?

Pequenas

A primeira regra de funções é que elas devem ser pequenas. A segunda regra é que elas devem ser menores que isso. Pelo menos, isso é o que a experiência do autor do livro mostrou durante as décadas que ele trabalhou com desenvolvimento de software.

Algumas regras como: “a função deve caber no monitor”, foram difundidas, porem na verdade foram originadas quando os monitores tinham 24 linhas por 80 colunas. Mas hoje em dia, podemos colocar mais de 100 linhas de código em um monitor.

No final, elas devem ser pequenas ao ponto que cada uma deve ser claramente obvia, cada uma deve contar uma estória e cada uma leva à próxima em uma ordem lógica.

É assim que as funções devem ser.

Blocos e Identação

Blocos de código dentro do “if”, “else” e “while” devem ter o mínimo de linhas possível, e deveriam ser uma chamada de função. Essa prática além de manter as funções pequenas, ajuda a documentar o código, pois ela vai possuir um nome bem descritivo.

Então a identação de um código não deve passar de um ou dois (tab)

Devem fazem somente uma coisa

Funções devem fazer somente uma coisa. Devem fazer isso bem. E somente isso.

A razão que escrevemos funções é decompor um conceito maior em uma série de passos que vão levar a outros conceitos de abstração.

Existe uma maneira simples de saber se uma função esta fazendo mais de uma coisa. Elas estará se você conseguir extrair outra função dela com um nome que não é meramente a descrição da implementação.

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

Misturar vários níveis de abstração dentro da função é sempre confuso. Quem está lendo pode não conseguir diferenciar o que é essencial do que é detalhe. Essa mistura, assim como janelas quebradas tendem a aumentar dentro da função causado muitos problemas.

Ler o código de cima para baixo (Step down rule)

O código deve ser feito como uma narrativa, de cima para baixo. Queremos que cada função seja seguida por aquelas que estão no próximo nível de abstração. Formando um código com a estrutura de um artigo.

Um dos pontos-chave para obter esse sucesso é manter as funções pequenas e fazendo somente uma ação.

Declarações Switch

Em sua natureza switch sempre fazem mais de uma coisa. Infelizmente não podemos sempre evitar switch mas podemos garantir que eles fiquem na classe mais baixa e nunca seja repetido. Podemos atingir esse objetivo fazendo uso de polimorfismo.

Ruim

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

A regra para switch é que eles são tolerados se aparecem apenas uma vez, sendo usados para criar objetos polimórficos e estão escondidos atrás de uma herança onde o resto do sistema não pode ve-los.

Bom

    public class Employee
    {
        public abstract bool isPayday();
        public abstract Money calculatePay();
        public abstract void deliverPay(Money pay);
    }
    public interface IEmployeeFactory
    {
        public Employee makeEmployee(EmployeeRecord r);
    }

    public class EmployeeFactoryImpl : IEmployeeFactory
    {
        public Employee makeEmployee(EmployeeRecord r)
        {
            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);
        }
    }

Claro que existem momentos que isso não é possível mas sempre devemos evitá-los.

Use nomes descritivos

É difícil superestimar o valor de bons nomes. Lembre-se, você sabe que está trabalhando em um código limpo quando cada rotina é exatamente o que você espera dela.

Não tenha medo de usar um nome longo. Um nome descritivo longo é melhor do que um curto enigmático. Um nome descritivo longo é melhor do que um comentário descritivo longo.

Use uma convenção de nomenclatura que permita que várias palavras sejam facilmente lidas nos nomes das funções, e, em seguida, use essas palavras para dar à função um nome que diz o que ele faz.

Não tenha medo de perder tempo escolhendo um nome. Na verdade, você deve tentar vários nomes diferentes e ler o código com cada um no lugar.

A escolha de nomes descritivos esclarecerá o design do módulo em sua mente e irá
ajudá-lo a melhorá-lo. Não é incomum que a busca por um bom nome resulte em uma reestruturação favorável do código.

Argumentos de função

O número ideal de argumentos para uma função é zero. Em seguida, um, seguido perto por dois.

Três argumentos devem ser evitados sempre que possível. Mais de três requer uma justificativa muito especial e então não deve ser usado de qualquer maneira.

Menos argumentos deixam os testes mais fáceis de serem feitos.

Os argumentos de saída são mais difíceis de entender do que os argumentos de entrada. Quando lemos uma função, estamos acostumados com a ideia de que as informações entram para a função por meio de argumentos e saem por meio do valor de retorno.

Então não esperamos que as informações sejam divulgadas através dos argumentos. E por este motivo, os argumentos de saída costumam nos fazer pensar duas vezes.

Funções com um argumento

Existem duas razões muito comuns para passar um único argumento para uma função como por exemplo:
bool FileExists("MyFile");
Podemos operar com base nesse argumento, transformando-o em outra coisa e devolvendo-o como retorno, por exemplo:
InputStream FileOpen("MyFile");

Usando um argumento de saída em vez de um o valor de retorno de uma transformação é confuso. Se uma função vai transformar seu argumento de entrada, a transformação deve aparecer como o valor de retorno

Argumentos tipo flag

Argumentos de flag são feios. Passar um Boolean em uma função é uma prática verdadeiramente terrível. Complica imediatamente a assinatura do método, proclamando em voz alta que esta função faz mais de uma coisa. Ele faz uma coisa se a flag for true e outra se for false.

Devemos então criar uma função para tipo de chamada, true ou false.

Funções com dois argumentos

Uma função com dois argumentos é mais difícil de entender do que uma função com um. Por exemplo:
WriteField(name);
É mais fácil de entender do que
WriteField(output-Stream, name);

Embora o significado de ambos seja claro, o primeiro desliza pelo olho, facilmente depositando seu significado. O segundo requer uma pequena pausa até aprendermos a ignorar o primeiro parâmetro.

Há momentos, é claro, em que dois argumentos são apropriados. Por exemplo:
Point p = new Point (0,0);
É perfeitamente razoável. Os pontos cartesianos naturalmente levam dois argumentos.
Na verdade, ficaríamos muito surpresos em ver o seguinte:
Point p = new Point (0);

Funções com três argumentos

Funções que usam três argumentos são significativamente mais difíceis de entender do que com dois. Logo que vemos uma função, nossa cabeça executa pensamentos de ordenar, interpretar e ignorar. Isso acontece para todos das funções mas em funções com 3 argumentos, essa ação que são mais do que duplicadas.

Eu sugiro que você pense muito com cuidado antes de criar uma função com 3 argumentos.

Objetos como argumento

Quando uma função parece precisar de mais de dois ou três argumentos, é provável que alguns destes esses argumentos devem ser agrupados em uma classe própria. Considere, por exemplo, a diferença entre as duas seguintes declarações:
Circle makeCircle (double x, double y, double radius);
Circle makeCircle (Point center, double radius);

Reduzir o número de argumentos criando objetos a partir deles pode parecer trapaça, mas não é.

Quando grupos de variáveis são passados juntos, a maneira como x e y estão no exemplo acima, provavelmente fazem parte de um conceito que merece um nome próprio.

Sem efeitos colaterais

Os efeitos colaterais são mentiras. Se sua função promete fazer uma coisa, mas também faz outra coisa oculta.

Às vezes, por exemplo ela fará alterações inesperadas nas variáveis de sua própria classe. Às vezes, ela os alterará parametros globais do sistema. Em ambos os casos, são inverdades tortuosas e prejudiciais que muitas vezes resultam em estranhos acoplamentos e dependências.

Por exemplo, neste código abaixo, além de verificar o password o código também inicializa a sessão do usuário.

        public bool CheckPassword(String userName, String password)
        {
            User user = UserGateway.FindByName(userName);
            if (user != null)
            {
                string codedPhrase = user.GetPhraseEncodedByPassword();
                string phrase = Cryptographer.Decrypt(codedPhrase, password);
                if ("Valid Password".Equals(phrase))
                {
                    Session.Initialize();
                    return true;
                }
            }
            return false;
        }

Argumento de saída

Os argumentos são mais naturalmente interpretados como entradas para uma função.

Se você programa há mais do que alguns anos, tenho certeza de que analisou novamente um argumento que na verdade era de saída em vez de uma entrada.

Qualquer coisa que force você a verificar a assinatura da função equivale a uma dupla checagem. Esta é uma quebra cognitiva e deve ser evitada sempre que possível.

Argumentos de saída devem ser evitados. Se sua função deve mudar o estado de algo, que altere o estado de uma propriedade de seu objeto.

Separação de comandos

As funções devem fazer algo ou responder a algo, mas não ambos. Ou a sua função deve alterar o estado de um objeto, ou deve retornar algumas informações sobre esse objeto.

Fazer as duas coisas costuma causar confusão. Considere, por exemplo, o seguinte função:

public bool Set(String atribute, String value);


if(Set(“name”, “unclebob”)){
//code
}

O autor pretendia definir como um verbo, mas no contexto da instrução “if” parece que é um adjetivo. Portanto, a declaração diz “Se o atributo de nome de usuário foi previamente definido para
unclebob faça..” e não “definir o atributo de nome de usuário para unclebob”.

Prefira exceções que códigos de erro

Retornar códigos de erro de funções de comando é uma violação sutil da separação de comando. Promove comandos usados como expressões nos predicados de instruções “if” não é uma boa prática.

Se você usar exceções em vez de códigos de retornos de erro, o código de processamento pode ser separado do código de caminho feliz e pode ser simplificado mais facilmente.

Extrair blocos try/catch

Eles confundem a estrutura do código e misturam o processamento de erros com o processamento normal. Portanto, é melhor extrair o conteúdo e capturar os blocos em funções próprias.

Tratar erro é fazer uma coisa

As funções devem fazer uma coisa. A manipulação de erros é uma coisa. Assim, uma função que lida com os erros não devem fazer mais nada. Isso implica que se a palavra-chave “try” existe em uma função, deve ser a primeira palavra e não deve ser nada após os blocos “catch/finally

Não precisa repetir

A duplicação pode ser a raiz de todos os males do software.

Muitos princípios e práticas foram criados com o objetivo de controlá-lo ou eliminá-lo. Considere como a programação orientada a objetos serve para concentrar o código em classes básicas que iriam, caso contrário, ser redundantes.

A programação estruturada, a programação orientada a aspectos e a programação orientada a componentes são todas, em parte, estratégias para eliminar a duplicação. Parece que, desde a invenção da sub-rotina, as inovações no desenvolvimento de software têm sido uma tentativa contínua de eliminar a duplicação de nosso código-fonte.

Como escrever funções dessa maneira

Escrita de software é como qualquer outro tipo de escrita. Quando você escreve um artigo, primeiro você regula seus pensamentos, depois organiza-os até que se leia bem.

O primeiro esboço pode ser desajeitado e desorganizado, então você o constrói em palavras, o reestrutura e o refina até ele ficar de facil leitura.

Quando acabo de escrever funções, elas são longas e complicadas. Elas têm muita indentação e loops aninhados e têm longas listas de argumentos. Então, eu organizo e refino esse código, dividindo funções, mudando nomes, eliminando a duplicação. Eu reduzo os métodos e os reordeno.

Conclusão

Funções são os verbos dessa língua e as classes são os substantivos. E a arte da programação é, e sempre foi, a arte da linguagem.

Elas podem ter ficado mais rica e expressiva e isso pode e deve ser usado para conta uma história.

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 3

Leave a Reply

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