Anotações Capítulo 3: Functions
Tags: books, codequality
Published: 2021-05-14 18:09:17

- As funções permaneceram durante a evolução da programação;

- O que torna uma função fácil de ler e entender?

## Pequenas!

- “A primeira regra para funções é que elas devem ser pequenas. A segunda é que precisam ser mais espertas do que isso”.
- Funções devem ter no máximo 20 linhas;
- Kent Beck escreveu um programa (Java/Swing) que ele chamava de “Sparkle” e cada função desse programa tinha duas, ou três, ou quatro linhas e essa deve ser o tamanho das nossas funções.

## Blocos e Indentação
- Os blocos dentro de `if, else, while` e outros devem ter apenas uma linha. Possivelmente uma chamada de função.
- O nível de indentação de uma função deve ser de, no máximo, um ou dois níveis.
- Facilita a leitura de compreensão das funções.

## Faça uma Coisa
- **“AS FUNÇÕES DEVEM FAZER UMA COISA. DEVEM FAZÊ-LA BEM. DEVEM FAZER APENAS ELA”**.
- Uma forma de saber se uma função faz mais de “uma coisa” é se você pode extrair outra função dela a partir de seu nome que não seja apenas uma reformulação de sua implementação.

## Seções Dentro de Funções
- Se temos seções, como declarações, inicializações, é um sinal de está fazendo mais de uma coisa;
- Não dá para dividir em seções as funções que fazem apenas uma coisa.

## Um Nível de Abstração por Função
- Vários níveis dentro de uma função sempre geram confusão.
- Leitores podem não conseguir dizer se uma expressão determinada é um conceito essencial ou um mero detalhe.

## Ler o Código de Cima para Baixo: Regra Decrescente
- Queremos que o código seja lido de cima para baixo, como uma narrativa.
- **Regra Decrescente**: Cada função seja seguida pelas outras no próximo nível de modo que possamos ler o programa descendo um nível de cada vez conforme percorremos a lista de funções.
- Acaba sendo muito difícil para programadores aprenderem a seguir essa regra e criar funções que fiquem em apenas um nível.
- É muito importante aprender esse truque, pois ele é o segredo para manter funções curtas e garantir que façam apenas “uma coisa”.
- Fazer com que a leitura do código possa ser feita de cima para baixo como uma série de parágrafos ”TO” é uma técnica eficiente para manter o nível consistente.

## Estrutura Switch
- É difícil criar uma estrutura `switch` pequena;
- Também é difícil criar uma que faça apenas uma coisa.
- Por padrão, os `switch` sempre fazem muitas coisas.
- Mas podemos nos certificar se cada um está em uma classe de baixo nível e nunca é repetido, usando o polimorfismo.
- A seguinte função mostra apenas uma das operações que podem depender 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);
    }
}
```
- Essa função acima tem vários problemas:
    - Primeiro: Ela é grande, e quando adicionarmos novos tipos de funcionários ela crescerá mais ainda;
    - Segundo: Ela faz mais de uma coisa;
    - Terceiro: Ela viola o Princípio da Responsabilidade Única (SRP - SOLID) por haver mais de um motivo para alterá-la;
    - Quarto: Ela viola o Princípio de Aberto-Fechado (OCP - SOLID), porque ela precisa ser modificada sempre que novos tipos forem adicionados;
    - E possivelmente o pior problema é a quantidade ilimitada de outras funções que terão a mesma estrutura, por exemplo:
```
isPayday(Employee e, Date date),
```
Ou 
```
deliverPay(Employee e, Money pay),
```
- A solução nesse caso é inserir uma estrutura switch no fundo de uma ABSTRACT FACTORY:
    - Assim, a factory usará o `switch` para criar instâncias apropriadas derivadas de Employee;
    - As funções: `calculatePay`, `isPayday` e `deliverPay`, serão enviadas de forma polifórmica através da interface `Employee`.
- A regra geral para estruturas `switch` é que são aceitáveis se aparecerem apenas uma vez para a criação de objetos polimórficos, e se estiverem escondidas atrás de uma relação de herança de modo que o resto do sistema não possa enxergá-la. Mas cada caso é um caso e podem haver casos de não respeitar todas essas regras.
- Assim temos como solução o seguinte código:
```
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);
        }
    }
}
```

## Use Nomes Descritivos
- É muito importante ter bons nomes;
- Lembre-se do princípio de Ward: “Você sabe que está criando um código limpo quando cada rotina que você lê é como você esperava”;
    - Metade do esforço para satisfazer esse princípio é escolher bons nomes para funções pequenas que fazem apenas uma coisa.
    - Quanto menor e mais centralizada é a função, mais fácil é pensar em um nome descritivo.
- Não tenha medo de criar nomes extensos, pois eles são melhores do que um pequeno e enigmático. Um nome longo e descritivo é melhor do que um comentário extenso e descritivo.
- Experimente diversos nomes até encontrar um que seja bem descritivo.
- Seja consistente nos nomes. Use as mesmas frases, substantivos e verbos nos nomes de funções de seu módulo.
- Exemplos:
    - `includeSetup-AndTeardownPages`, `includeSetupPages`, `includeSuiteSetupPage` , e `includeSetupPage`. 

## Parâmetros de Funções
- A quantidade ideal de parâmetros para uma função é zero. Depois vem um, seguido de dois. Sempre que possível devem-se evitar três parâmetros. Para mais de três deve-se ter um motivo muito especial, mesmo assim não devem ser usados.
- **Parâmetros são complicados**. Eles requerem bastante conceito.
- Os parâmetros são mais difíceis ainda a partir de um ponto de vista de testes:
    - Imagina a dificuldade de escrever todos os casos de testes para se certificar de que todas as várias combinações de parâmetros funcionem adequadamente.;
    - Se não houver parâmetros, essa tarefa é simples;
    - Se houver um, não é tão difícil assim;
    - Com dois, a situação fica um pouco desafiadora. Com mais de dois, pode ser desencorajador testar cada combinação de valores apropriados.
    - Os parâmetros de saída são mais difíceis de entender do que os de entrada.
- **Por fim, um parâmetro de entrada é a melhor coisa depois de zero parâmetro!**

## Formas Monádicas (Um parâmetro) Comuns
- Duas razões para se passar um único parâmetro a uma função:
    - Você pode estar fazendo uma pergunta sobre aquele parâmetro, exemplo: `boolean fileExists(“MyFile”’)`.
    - Ou você pode trabalhar parâmetro, transformando-o em outra coisa e retornando-o, exemplo: `InputStream fileOpen(“MyFile”)` transforma a `String` do nome de um arquivo em um valor retornado por InputStream.
    - Outro uso menos comum é para uma função de evento, neste caso há um parâmetro de entrada, mas nenhum de saída. Cuidado ao usar essa abordagem!
- Se uma função vai transformar seu parâmetro de entrada, a alteração deve aparecer como o valor retornado. Por exemplo:
```
StringBuffer transform(StringBuffer in)
```
É melhor do que:
```
void transform(StringBuffer out)
```

## Parâmetros Lógicos	
- Esses parâmetros são feios.
- Passar um booleano para uma função certamente é uma prática horrível, pois ele complica imediatamente a assinatura do método, mostrando explicitamente que a função faz mais de uma coisa.
- Ela faz uma coisa se o valor for verdadeiro, e outra se for falso!


## Funções Díades (Dois parâmetros)
- “Uma função com um parâmetro é mais difícil de entender do que com um”.
- Com dois parâmetros, é preciso aprender a ignorar um dos parâmetros, porém o local que ignoramos é justamente onde os bugs se esconderão.
- Casos em dois parâmetros são necessários:
    - Por exemplo, uma classe com eixos cartesianos, como por exemplo, `Point p = new Point(0, 0)`, é preciso ter os dois parâmetros;
    - Nesse caso os dois parâmetros **são componentes de um único valor**.
- Mesmo funções óbvias como `assertEquals(expected, actual)`, são problemáticas!
- Quantas vezes já colocou `actual` on deveria ser `expected`?
- Os dois parâmetros não possuem uma ordem pré-determinada natural;
- A ordem `expected`, `actual` é uma convenção que requer prática para assimilá-la.
- Funções com dois parâmetros **não são ruins** e vamos usá-las!
- Mas devemos tentar converter essas funções em funções de um parâmetro, usando outro método ou variáveis de classe ou ainda outra classe que recebe o parâmetro no construtor.

## Tríades (Três parâmetros)
- São consideravelmente mais difíceis de entender do que as com dois parâmetros;
- Pense bastante antes de criar uma tríade!
- O processo de ordenação, pausa e ignoração apresenta mais do que o dobro de dificuldade.

## Objetos como parâmetro
- “Quando uma função parece precisar de mais de dois parâmetros, é provável que alguns desses parâmetros devam ser agrupados em uma classe própria”. Por exemplo:
```
Circle makeCircle(double x, double y, double radius);
Circle makeCircle(Point center, double radius);
```
- Criar objetos para reduzir o número de parâmetros pode parecer trapaça, mas não é.

## Listas como parâmetro
- Quando queremos passar um número variável de parâmetros para uma função, como por exemplo:
```
String.format("%s worked %.2f hours.", name, hours);
```
- Se os parâmetros forem todos tratados da mesma forma, eles serão equivalentes a um único parâmetro do tipo `List`.
- Por isso, o `String.format` é uma função com dois parâmetros:
```
public String format(String format, Object... args)
```

## Verbos e palavras-chave
- Escolher bons nomes para uma função pode ajudar muito a explicar a intenção da função e a ordem e a intenção dos parâmetros.
- No caso de função com um parâmetro, a função e o parâmetro devem formar um par verbo/substantivo muito bom. Por exemplo, `write(name)`, qualquer que seja essa coisa “name” está sendo “write” (escrito). Um nome ainda melhor poderia ser `writeField(name)`, que indica que o “name” é um campo.
- Por exemplo: `assertEquals` pode ser melhor escrito como `assertExpectedEqualsActual(expected, actual)`, assim diminui o problema de ter que lembrar a ordem dos parâmetros.

## Evite Efeitos Colaterais
- “Efeitos colaterais são mentiras”.
- Se a função promete fazer apenas uma coisa, mas também faz outras coisas escondidas. Vamos ter efeitos indesejáveis. Por exemplo:
```
public class UserValidator {
    private Cryptographer cryptographer;

    public boolean checkPassword(String userName, String password) {
        User user = UserGateway.findByName(userName);
        if (user != User.NULL) {
            String codedPhrase = user.getPhraseEncodedByPassword();
            String phrase = cryptographer.decrypt(codedPhrase, password);
            if ("Valid Password".equals(phrase)) {
                Session.initialize();
                return true;
            }
        }
        return false;
    }
}
```
Essa função de verificar a senha, tem um efeito colateral, que é a chamada do `Session.initialize()`, o nome `checkPassword` indica que verifica a senha, mas não indica que também inicializa a sessão. Assim, podemos correr o risco de apagar os dados da sessão existente quando ele decidir autenticar o usuário.
- Assim, temos um efeito colateral de acoplamento. No caso o `checkPassword` só pode ser chamada quando realmente formos inicializar a sessão, do contrário dados serão perdidos.
- Se realmente queremos manter o acoplamento dessa forma, deveríamos deixar explícito no nome da função, como por exemplo, `checkPasswordAndInitializeSession`.

## Parâmetros de Saída
- Quando precisamos reler a assinatura da função para entender o que acontece com o parâmetro de entrada, temos um problema, e isso deve ser evitado!
- De modo geral, devemos evitar parâmetros de saída. Caso a função precise alterar o estado de algo, mude o estado do objeto que a pertence.

## Separação comando-consulta
- As funções **devem fazer ou responder algo, mas não ambos**. Ou alterar o estado de um objeto ou retorna informações sobre ele.
- Fazer as duas tarefas costuma gerar confusão. Por exemplo:
```
public boolean set(String attribute, String value);
```
Pode levar a instruções estranhas como:
```
if (set("username", "unclebob"))...
```
E fica um **caos a interpretação**, o que significa esse trecho de código, estamos perguntando se o atributo “username” recebeu o valor “unclebob”? Ou se “username” obteve êxito ao receber o valor “unclebob”?
A intenção neste código acima é ter o `set` como um adjetivo, assim deveríamos ler “se o atributo `username` anteriormente recebeu o valor `unclebob`, porém não fica bem claro, para isso devemos usar o nome melhor como `setAndCheckIfExists`, mesmo assim ainda tinhamos um código estranho:
```
if (attributeExists("username")) {
setAttribute("username", "unclebob");
...
}
```

## Prefira exceções a retorno de códigos de erro
- Fazer funções retornarem códigos de erros é uma leve violação da separação comando-consulta, pois os comandos são usados como expressões de comparação em estruturas `if`:
```
if (deletePage(page) == E_OK)
```
- Retornar código de erro se torna um problema para quem chama a função, já que ele vai ter que lidar com o erro e possivelmente criar estruturas aninhadas, deixando o código muito ruim.
- Mas se usarmos exceções, o código de tratamento de erro pode ficar separado do código e ser simplificado, por exemplo:
```
try {
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
}
catch (Exception e) {
    logger.log(e.getMessage());
}
```
- Extraia os blocos try/catch
    - Esses blocos não tem o direito de serem feios;
    - Eles confundem a estrutura do código e misturam o tratamento de erro com o processamento normal do código;
    - É melhor colocar esses blocos em suas próprias funções:
```
public void delete(Page page) {
    try {
        deletePageAndAllReferences(page);
    }
    catch (Exception e) {
        logError(e);
    }
}

private void deletePageAndAllReferences(Page page) throws Exception {
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.deleteKey(page.name.makeKey());
}

private void logError(Exception e) {
    logger.log(e.getMessage());
}
```
Assim, a função `delete` só faz o tratamento de erro, a função `deletePageAndAllReferences` só trata de processos que excluem toda página, e o log apenas adicionar a mensagem do erro no console.

## Tratamento de erro é uma coisa só
- Tratamento de erro é uma coisa só, portanto uma função que trata erros não deve fazer mais nada!
- Assim, a instrução `try` deve ser a primeira instrução da função e nada mais antes dela.
- Assim, podemos evitar o uso de classe de erros como por exemplo `Error.java`, sendo um Enum como vários erros e que tudo dependeria dessa classe.

## Evite repetição
- Repetição é um problema.
- Sempre será necessário modificar mais de um lugar quando o algoritmo mudar.
- E nisso podemos omitir erros gerando bugs.
- A duplicação pode ser a raiz de todo o mal no software.
- Muitos princípios e práticas têm sido criadas com a finalidade de controlar ou eliminar a repetição de código.

## Programação estruturada
- Programação estruturada de Edsger Dijkstra: “Cada função e bloco dentro de uma função deve ter uma entrada e uma saída”.
- Apenas em funções maiores tais regras proporcionam benefícios significativos.
- Se mantivermos funções pequenas, as várias instruções `return, break, continue` não trarão problemas.
Mas instruções como `goto` só devem existir em grandes funções e devemos evitá-las.

## Como escrever funções como essa?
- Talvez não seja possível aplicar todas as regras vistas até aqui de início.
- Nas funções, elas começam longas e complexas, com muitos níveis de indentações e loops aninhados, muitos parâmetros, nomes ruins e aleatórios, duplicação de código. 
- Porém depois organizamos, refinamos o código, dividindo em funções, trocamos os os nomes, removemos a duplicação, e no fim devemos ter uma função que respeite as regras vistas aqui.

## Conclusão
- As funções são os verbos e as classes os substantivos.
- “Essa é uma verdade muito antiga”.
- “A arte de programar é, e sempre foi, a arte do projeto de linguagem” (linguagem literal, narrativa).
- Seguindo as regras deste capítulo, suas funções serão curtas, bem nomeadas e bem organizadas.
- “Mas jamais se esqueça de que seu objetivo verdadeiro é contar a história do sistema”.