Código LimpoCapítulo 3 - Funções
Bruno BlumenscheinHélios Kárum de Oliveira BastosRodrigo Oliveira Andrade
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.
Exemplo
Exemplo - Continuação
Exemplo - Primeira Melhoria
Exemplo - Segunda Melhoria
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.
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.
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".
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ó.
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.
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.
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.
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.
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...
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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).
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.
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.
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().
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.
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.
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.
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.
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.
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.
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).
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.
Evite Efeitos Colaterais - Exemplo
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.
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
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?
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();
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.
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.
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"))...
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.
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.
Exemplo Ruim
Exemplo Bom
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:
Extraia os Blocos de Try/Catch Exemplo
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.
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.
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.
Exemplo Reptição
Exemplo Repetição
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.
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.
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.
Recommended