Este capítulo discute princípios para escrever funções de maneira limpa e legível, como:
1) Funções devem ser pequenas e fazer apenas uma coisa;
2) Devem evitar parâmetros, especialmente de saída, e usar no máximo dois parâmetros;
3) Nomes de funções devem ser descritivos do que elas fazem.
1. Código Limpo
Capítulo 3 - Funções
Bruno Blumenschein
Hélios Kárum de Oliveira Bastos
Rodrigo Oliveira Andrade
2. Regras
● Primeira Regra:
○ As funções devem ser pequenas;
● Segunda Regra:
○ "Elas precisam ser ainda menores";
● Não há como provar porque, foi baseado
nas tentativas e erros do autor.
● Não ultrapassar mais do que 20 linhas, com
150 carcteres por linha.
7. Blocos e Identação
● Seguindo a linha de se minimizar funções,
instruções como if, else e while devem
possuir apenas uma linha de código.
● E provavelmente esta linha será uma
chamada a uma função.
● Além de manter a função pequena agrega
valor de documentação, já que o nome da
função deve ser bem descritivo.
8. Blocos e Identação
● Essa estrutura também implica que as
funções não devem ter muitas estruturas
aninhadas.
● Para facilitar a estrutura e identação o
nível máximo de estruturas aninhadas deve
ser de uma ou duas.
9. Fazer apenas uma Coisa
● "Functions should do one thing. They should
do it well. The should do it only."
● Funções devem fazer uma única coisa. Elas
devem fazê-la bem. Elas devem fazer
apenas ela.
● O problema é saber o que é "uma única
coisa".
10. Fazer apenas uma Coisa
● O que faz o programa do exemplo anterior
(RenderPageWithSetupsAndTeardowns):
1. Determina se a página é de teste;
2. Se for, inclui setUps e tearDowns;
3. Renderiza a página em HTML.
● É apenas uma função ou são três?
● Note que as três operações realizadas estão
a um nível abaixo do nome da função.
● Então ela está fazendo uma coisa só.
11. Fazer apenas uma Coisa
● O motivo de criarmos uma função é para
decompor um conceito maior (em outras
palavras o nome da função) em uma série
de passos no próximo nível de abstração.
12. Seções em Funções
● Funções que fazem apenas apenas uma
coisa não podem ser razoavelmente
divididas em seções.
● Caso isto aconteça é um sintoma de estar
fazendo mais de uma coisa.
13. Um Nível de Abstração por Função
● A fim de confirmar se uma função só faz
uma coisa, precisamos verificar se todas as
intruções dentro da função estão no mesmo
nível de abstração.
● Vários níveis de abstração dentro de uma
função sempre geram confusão.
● Os leitores podem não conseguir dizer se
uma expressão determinada é um conceito
essêncial ou um mero detalhe.
14. Ler o Código de Cima para Baixo
● O código deve ser lido de maneira top-
down.
● Desejamos que cada função seja seguida
pelas outras no próximo nível de abstração,
de modo que possamos ler o programa um
nível de cada vez.
● Chamamos isso de regra descendente.
15. Exemplo
● Para incluir setups e teardowns, nós incluimos
primeiro os setups, depois nós incluimos o
conteúdo da página de testes, e então incluimos
os teardowns;
● Para incluir os setups, nós incluimos o suite
setup se for uma suite, e então incluimos o setup
regular;
● Para incluir o suite setup, nós procuramos na
hierarquia pela página "SuiteSetUp", e então
adicionamos uma instrução com o caminho para
aquela página.
● Para procurar a hierarquia a cima...
16. Ler o Código de Cima para Baixo
● Acaba sendo difícil para o programador
aplicar essa regra, mas quando ele passa a
dominar esse truque, ele passa a ter o
controle de verificação para saber se uma
função só faz apenas uma coisa.
17. Switch
● É difícil criarmos um swtich pequeno.
● Mesmo o menor switch possível, com dois
casos, já é maior do que eu gostaria de ter
em um único bloco de código.
● É difícil criar um switch que faça apenas
uma coisa.
● Pela sua natureza eles são criados para
fazerem N coisas.
● Infelizemente nós não podemos evitar o uso
de switchs.
18. Switch
● Mas nós podemos nos assegurar que cada
instrução está um nível de abstração abaixo
e que nunca se repetem.
● Switch geralmente tem vários problemas
com o Principio do Aberto-Fechado. Já que
para cada alteração, deve-se abrir a função
e alterar o switch.
19. Use Nomes Descritivos
● Devemos criar nomes que descrevem bem o
que as funções fazem.
● Princípo 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 essa máxima
é escolher bons nomes para as funções que
fazem apenas uma coisa.
● Quanto menor e mais centralizada for a função
mais fácil será para se pensar em um nome
descritivo.
20. Use Nomes Descritivos
● Nomes extensos são maiores do que nomes
pequenos e enigmáticos.
● Um nome extenso e descritivo é melhor que
um comentário extenso e descritivo.
● Use uma convenção de nomenaclatura que
possibilite uma fácil leitura das funções
com vários nomes.
● Não se preocupe com o tempo para criar
um nome, e não tenho medo de modificá-lo
caso tenha encontrado uma opção melhor.
21. Use Nomes Descritivos
● É comum que ao se buscar nomes
adequados resulte em uma boa
reestruturação do código.
● Seja consistente nos nomes, utilize sempre
as mesmas frases, substantivos e verbos.
22. Parâmetros de Função
● O número ideal de argumentos para um
método é zero.
● Depois, um argumento, mônade, e em
seguida, dois argumentos, díade.
● Métodos com três argumentos, tríade, ou
mais, devem ser evitados sempre que
possível. Necessitam de uma boa
justificativa caso sejam utilizados.
23. Parâmetros de Função
● Parâmetros são complicados. Eles requerem
bastante conceito. Nos exemplos eles foram
até retirados.
● É mais fácil enteder o método:
includeSetupPage() do que o método:
includeSetupPageInto(newPage-Content).
24. Parâmetros de Função
● Os parâmetros são ainda mais
problemáticos do ponto de vista de testes.
● Imagine criar todos os cenários de testes
posíveis para todas as combinações
existentes entre os parâmetros.
● Se não houver nenhum parâmetro, esta é
uma tarefa simples, se já existir um, não é
tão difícil assim.
● Com dois a situação já passa a ser
desafiadora.
25. Parâmetros da Função
● Os parâmetros de saída são ainda mais
difíces de entender do que os de entrada.
● Geralmente não esperamos dados saídos por
parâmetros.
● Um parâmetro de entrada é a melhor coisa
depois do zero parâmetro.
●É facil entender:
○ SetupTeardown-Includer.render(pageData)
○ E fica bem claro que renderizemos os dados em
pageData.
26. Formas Mônades Comuns
● Há duas razões comuns para se passar um
único parâmetro para uma função:
○ Você pode estar fazendo uma pergunta sobre
aquele parâmetro:
■ boolean fileExists(“MyFile”).
○ Você pode estar trabalhando aquele parâmetro,
transformando-o em outra coisa e retornando-o
■ InputStream fileOpen(“MyFile”)
■ Transforma a String do nome de um arquivo em
um valor retornado pela InputStream.
27. Formas Mônades Comuns
● Outra forma menos comum é para a
utilização de eventos:
○ Há um parâmetro de entrada, mas não há um de
saida.
○ O programa em si serve para interpretar a chamada
da função como um evento, e usar o parâmetro
para alterar o estado do sistema.
○ void passwordAttemptFailedNtimes(int attempts).
28. Formas Mônades Comuns
● Tente evitar funções que não sigam estas
formas, como:
○ void includeSetupPageInto(StringBuffer pageText)
● Usar um parâmetro de saída em vez de um
valor de retorno para uma modificação fica
confuso.
● Se uma função vai transformar o seu
parâmetro de entrada, a alteração deve
aparecer como o valor retornado.
29. Parâmetros Lógicos
● "Parâmetros lógicos são feios".
● Utilizar um valor booleano como parâmetro
em uma função é certamente uma prática
horrível, pois ele complica imediatamente a
assinatura do método, mostrando
explicitamente que o método faz mais de
uma coisa.
30. Exemplo
● Um método: render(true) é difícil de ser
interpretado por um leitor simples.
● Ele fica melhor: render(boolean isSuite),
mas nem tanto.
● O melhor seria dividí-lo em:
○ renderForSuite() e
○ renderForSingleTest().
31. Funções Díades
● Uma função com dois parâmetros é mais fácil
de enteder do que uma com apenas um. A
função writeField(name) é mais fácil de
compreender do que a writeField(output-
Stream, name).
● Embora as duas estejam claras, a primeira
apresenta seu propósito explicitamente quando
lemos.
● Já a segunda requer uma pequena pausa até
que aprendemos a ignorar o primeiro
parâmetro.
32. Funções Díades
● E é ai que mora o problema, porque é nas
partes do código que ignoramos que mora o
problema.
● Díades não são ruins, e certamente terão de
ser utilizá-dos. Entretanto deve-se estar
ciente de que haverá um preço a pagar e,
portanto, deve-se em tirar proveito dos
mecanismos disponíveis a você para
convertê-los em mônades.
33. Tríades
● Funções que recebem três parâmetros são
consideravelmente mais difíceis de
entender do que as que utiliza ois
parâmetros.
● A questão de ordenação, pausa, ignoração
apresentam mais do que o dobro de
dificuldade.
● Portanto as utilize somente quando houver
extrema necessidade.
34. Tríades - Exemplo
● assertEquals(message, expected, actual).
● O parâmetro message precisa ser deduzido,
e as vezes não é entendido pelo
programados qual é a sua função.
35. Tríades - Exemplo 2
● Um método como:
○ Circle makeCircle(double x, double y, double radius)
● Ser transformado para:
○ Circle makeCircle(Point center, double radius);
● Pode parecer uma trapaça, mas x e y são
partes de um conceito maior e merecer ter
uma denominação diferenciada.
36. Nomenclatura
● Como dito anteriormente, é importante que
as funções tenham nomes que expliquem
diretamente o que elas fazem.
● Mas tão importante quanto isso, é que estes
nomes também relacionem como o
parâmetro vai interagir com a função.
37. Nomenclatura - Exemplos
● Em caso de de mônades a função e o
parâmetro devem ser criados baseados em
um bom par de verbo/substantivo.
○ write(name)
● O que quer que seja o parâmetro "name",
sebemos que é ele que será "escrito"
(write).
● Um nome melhor ainda seria:
○ writeField(name)
● Que nos dias ainda que o nome é um campo
(field).
38. Evite Efeitos Colaterais
● "Efeitos colaterais são mentiras".
● Você promete fazer uma coisa com a sua
função, mas ela faz outras coisas
escondidas, isto é errado.
● Veremos no exemplo a seguir o que seria
um exemplo de efeito colateral em um
sistema que está fazendo login.
40. Evite Efeitos Colaterais
● O efeito colateral deste código está na
linha:
○ Session.initialize();
● Pelo nome da função (checkPassword), ela
verifica a senha, mas não é dito que ela
inicia a sessão.
● Então alguém que acredita no que diz o
nome da função, correrá o risco de apagar
todos os dados da sessão existente caso ele
deseje autenticar o usuário.
41. Evite Efeitos Colaterais
● Este efeito colateral cria um acoplamento
temporário.
● Esta função só poderá ser chamada em
casos específicos (quando for seguro
inicializar a sessão).
● Se for chamada fora de ordem sem querer,
os dados serão perdidos.
● Um nome melhor para o método poderia se:
○ checkPasswordAndInitializeSession
42. Parâmetros de Saída
● Parâmetros são comumente interpretados
como entradas de uma função.
● Quando encontramos parâmetros de saída
em alguma função, temos que gastar um
tempo bem maior para ler e relê-la.
● Como por exemplo:
○ appendFooter(s);
● Esta função anexa s como um rodapé
(footer) em algo? Ou anexa um rodapé a s?
● s é uma entrada ou uma saída?
43. Parâmetros de Saída
● Analisando a assinatura da função:
○ public void appendFooter(StringBuffer report);
● A questão é esclarecida, mas a custa da
verificação da declaração da função.
● Isto é considerado uma relida, uma
iterrupção de raciocínio e deve ser evitado.
● De modo geral deve-se evitar parâmetros
de saída. Uma utilização melhor do método
seria:
○ report.appendFooter();
44. Parâmetros de Saída
● Caso a função precise alterar o estado de
algo, faça-a mudar o estado do objeto a
que pertence.
45. Separação Comando-Consulta
● Funções devem fazer algo ou responder a
algo. Mas nunca as duas coisas.
● Sua função ou altera o estado de um objeto
ou retorna informações sobre ele.
● Efetuar as duas tarefas gera confusão.
46. Separação Comando-Consulta
Exemplo
● Analisando o método:
○ public boolean set(String attribute, String value);
● Esta função define o valor de um dado
atributo e retorna verdadeiro se obtiver
êxito e falso se tal atributo não existir.
● Isto leva a instruções estranhas que podem
ser difíceis de serem interpretadas como:
○ if (set("username", "unclebob"))...
47. Separação Comando-Consulta
Exemplo
● Do ponto de vista de um leitor:
○ Está perguntando se o atributo "username"
anteriormente recebeu o valor "unclebob"?
○ Ou se "username" obtêve êxito ao receber o valor
"ubclebob"?
● Fica difícil adivinhar.
48. Prefira Exceções a Retorno de Código
de Erro
● Fazer funções retornarem códigos de erros
é uma leve violação da separação comando-
consulta.
● Estruturas deste tipo podem gerar trechos
de códigos aninhados, fazendo com que o
erro deve ser tratado imediatamente.
● Com a utilização de exções podemos tratar
os erros em separado.
51. Extraia os Blocos de Try/Catch
● Blocos de código de Try/Catch são
particularmente "feios" a seu modo.
● Por isso, é melhor extrair os corpos de try e
catch em funções fora da sua própria
estrutura.
● Como no exemplo a seguir:
53. Tratamento de Erro é uma Coisa Só
● Funções devem fazer apenas uma coisa.
● Tratamento de erro é uma coisa.
● Criar classes de erro em java, como a
seguir, não é interessante.
54. Tratamento de Erro é uma Coisa Só
● Classes como estas são "chamarizes a
dependência".
● Muitas outras classes devem importá-las e
usá-las.
● O que coloca uma pressão na classe Error.
● Os programadores não querem colocar
novos erros para não ter que recompilar.
● A melhor maneira de evitar isto é utilizando
exceções.
55. Evite Reptição
● As vezes não é fácil identificar repetição de
código,consequentemente arrumar erros em
repetições são difíceis.
● Repetições causam trabalhos dobrados caso
o código precisa ser modificados.
● Repetição de código é o mal de toda
progamação.
58. Programação Estruturada
● Conceito de Edsger Dijkstra.
● Cada função deve ter apenas uma entrada e
uma saída.
● Não há muita vantagem em seguir essa
estrutura em funções pequenas.
● Se você conseguir manter funções
pequenas, instruções como return, break e
continue podem sem vantajosas.
● GoTo nunca deve ser utilizado.
59. Como escrever Funções como Esta?
● Não é um processo rápido, envolvem vários
passos.
● Primeiro pense em como resolver o
problema e faça da maneira que desejar.
● Só depois vá refinando as funções e
aplicando as regras explicadas no capítulo
para que fiquem melhores estruturadas.
● Não hesite em decompor toda uma classe se
sentir necessário, valerá a pena no futuro.
60. Conclusão
● Funções são os verbos do sistema.
● As Funções são essenciais para o
entendimento do sistema.
● As funções precisam ajudar o
funcionamento do sistema.
● Funções precisam ser curtas, bem
nomeadas, e bem organizadas.