Docsity
Docsity

Prepare-se para as provas
Prepare-se para as provas

Estude fácil! Tem muito documento disponível na Docsity


Ganhe pontos para baixar
Ganhe pontos para baixar

Ganhe pontos ajudando outros esrudantes ou compre um plano Premium


Guias e Dicas
Guias e Dicas

Análise e Refatoração de Código: Cenários e Métricas, Resumos de Lógica

Este documento aborda diferentes cenários e métricas no contexto da análise e refatoração de código. São discutidos conceitos como composição de métodos, evitar estruturas encadeadas e uso de exceções, além de métricas como nec, np, ecr e ncc. O objetivo é fornecer ferramentas para avaliar a qualidade do código e identificar possíveis melhorias. A classe metrics é usada como um caso de estudo para ilustrar esses conceitos.

Tipologia: Resumos

2022

Compartilhado em 07/11/2022

Agua_de_coco
Agua_de_coco 🇧🇷

4.6

(326)

768 documentos

1 / 74

Toggle sidebar

Esta página não é visível na pré-visualização

Não perca as partes importantes!

bg1
Código Limpo e seu Mapeamento para Métricas de Código Fonte
Lucianna Thomaz Almeida
João Machini de Miranda
Orientador: Prof. Dr. Fabio Kon
Coorientador: Paulo Roberto Miranda Meirelles
São Paulo, Dezembro, 2010
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14
pf15
pf16
pf17
pf18
pf19
pf1a
pf1b
pf1c
pf1d
pf1e
pf1f
pf20
pf21
pf22
pf23
pf24
pf25
pf26
pf27
pf28
pf29
pf2a
pf2b
pf2c
pf2d
pf2e
pf2f
pf30
pf31
pf32
pf33
pf34
pf35
pf36
pf37
pf38
pf39
pf3a
pf3b
pf3c
pf3d
pf3e
pf3f
pf40
pf41
pf42
pf43
pf44
pf45
pf46
pf47
pf48
pf49
pf4a

Pré-visualização parcial do texto

Baixe Análise e Refatoração de Código: Cenários e Métricas e outras Resumos em PDF para Lógica, somente na Docsity!

Código Limpo e seu Mapeamento para Métricas de Código Fonte

Lucianna Thomaz Almeida

João Machini de Miranda

Orientador: Prof. Dr. Fabio Kon

Coorientador: Paulo Roberto Miranda Meirelles

São Paulo, Dezembro, 2010

ii

iv

Sumário

Capítulo 1

Introdução

A indústria de software busca continuamente por melhorias nos métodos de desenvolvimento. Do ponto de vista prático, os processos tradicionais investem tempo especificando requisitos e planejando a arquitetura do software para que o trabalho possa ser dividido entre os programadores, cuja função é transformar o design anteriormente concebido em código. Diante de uma perspectiva dos métodos ágeis, é dado muita importância à entrega constante de valor ao cliente (AgM, 2001). Nesse caso, se há um agente financiador ou uma comunidade de software livre como cliente, o foco está na entrega de funcionalidades que possam ser rapidamente colocadas no ambiente produção e receber feedback constante. Em ambos os casos, não há como ignorar o fato de que o código-fonte da aplicação será desenvolvido gradativamente e diferentes programadores farão alterações e extensões continuamente. Nesse contexto, novas funcionalidades são adicionadas e falhas sanadas. Uma situação comum durante o desenvolvimento, é um programador lidar com um trecho que ainda não teve contato para fazer alterações. Nessa tarefa, por exemplo, o programador (i) lida com métodos extensos e classes com muitas linhas de código que se comunicam com diversos objetos; (ii) encontra comentários desatualizados relacionados a uma variável com nome abreviado e sem signifi- cado, e (iii) se depara com métodos que recebem valores booleanos e possuem estruturas encadeadas complexas com muitas operações duplicadas. Depois de um longo período de leitura, o desenvolvedor encontra o trecho de lógica em que deve fazer a mudança. A alteração frequêntemente consiste na modificação de poucas linhas e o desenvolvedor considera seu trabalho como terminado, sem ter feito melhorias no código confuso com o qual teve dificuldades. O quadro exemplificado ilustra uma circustância com aspectos a serem observados. Primeiramente, há uma diferença significativa na proporção entre linhas lidas e as inseridas. Para cada uma das poucas linhas que escreveu, o desenvolvedor teve que compreender diversas outras (Beck, 2007). Além disso, não houve melhorias na clareza ou flexibilidade do código, fazendo com que os responsáveis pela próxima alteração, seja o próprio desenvolvedor ou o seguinte, tenham que enfrentar as mesmas dificuldades. Tendo essa situação em vista, neste trabalho apresentamos um estilo de programação baseado no paradigma da Orientação a Objetos que busca o que denominamos de “Código Limpo”, concebido e aplicado por renomados desenvolvedores de software como Robert C. Martin (Martin, 2008) e Kent Beck (Beck, 2007). Um Código Limpo é fundamentado em testes e decisões técnicas que visam a clareza, flexibilidade e simplicidade do código-fonte, o que pode ter um impacto importante no desenvolvimento de software, aumentando a produtividade e diminuindo os custos de manutenção. Por fim, ao longo desta monografia também apresentaremos uma maneira de utilizar métricas de

3

6 INTRODUÇÃO 1.

código-fonte para auxiliar no desenvolvimento de um código limpo. Beneficiando-se da automação da coleta de métricas e de uma maneira objetiva de interpretar seus valores, os desenvolvedores poderão acompanhar a limpeza de seus códigos e detectar cenários problemáticos.

1.1 Objetivos

O primeiro objetivo deste trabalho é apresentar um levantamento teórico de conceitos relaciona- dos ao que denominamos código limpo, buscando expor um conjunto de técnicas e boas decisões que possam ser adotadas ao longo do desenvolvimento para auxiliar a criação de um código mais expres- sivo, simples e flexível. Essa apresentação permite um contato com recomendações e preocupações de desenvolvedores de software reconhecidos internacionalmente ao trabalharem com o paradigma da Orientação a Objetos. Além disso, também temos como objetivo o mapeamento entre um grupo de métricas de código- fonte e os conceitos acima citados, de forma a facilitar a detecção de trechos de código que poderiam receber melhorias. Para que as métricas possam ser mais facilmente incorporadas no cotidiano dos programadores, também temos como objetivo a apresentação de uma maneira de interpretar os va- lores das métricas através de cenários problemáticos que ocorrem frequentemente durante o desen- volvimento. Desse modo, queremos apresentar uma maneira de utilizar o poder da automatização das métricas associado a interpretação dos valores calculados, facilitando assim a aproximação dos conceitos relacionados ao código limpo.

1.2 Desenvolvimento do Trabalho

Para definir um código limpo e selecionar o conjunto de conceitos e técnicas desta monografia, trabalhamos sobre duas principais referências: Implementation Patterns (Beck, 2007) de Kent Beck e Clean Code (Martin, 2008) de Robert C. Martin. Ambos os livros foram escolhidos pelo grande reconhecimento dado a esses autores pela comunidade de desenvolvimento de software. Baseado em suas experiência, Beck e Martin expõem uma série de recomendações para a criação de um bom código orientado a objetos. Ao longo desse trabalho, escolhemos uma parcela dos conceitos comuns em ambos os livros. Di- versos desses aspectos são bastante conhecidos e apresentamos aqui exemplos práticos que explicitam como as técnicas alteram a clareza e legibilidade do código. Posteriormente, trabalhamos sobre uma implementação do algoritmo de Dijkstra para encontrar a árvore geradora de custo mínimos, buscando encontrar melhorias que ilustrassem a aplicação dos tópicos estudados. Para elaborar o mapeamento das métricas de código-fonte, tivemos como ponto de partida as 22 métricas da ferramenta Analizo (Terceiro et al., 2010), a qual colaboramos para o desenvolvimento por mais de um ano. Além disso, baseamos nossos estudos no livro Object Oriented Metrics In Practice (Lanza e Marinescu, 2006) de Michele Lanza e Radu Marinescu, que apresenta uma maneira bastante objetiva de avaliar o design de aplicações orientadas a objetos. A união da nossa experiência no desenvolvimento da Analizo e o estudo das referências tornou possível a seleção das métricas apresentadas nessa monografia. No entanto, desde o desenvolvimento e estudo sobre a Analizo, nosso grupo de pesquisa enfrentou dificuldades com a compreensão dos valores das métricas, o que nos motivou a focar no seu entendimento. Para tal, trabalhamos sobre pequenos

  • 1 Introdução
    • 1.1 Objetivos
    • 1.2 Desenvolvimento do Trabalho
    • 1.3 Organização do Trabalho
  • 2 Código Limpo
    • 2.1 O Desenvolvimento de um Código Limpo
    • 2.2 Nomes
    • 2.3 Métodos
      • 2.3.1 Fundamentação Teórica
      • 2.3.2 Técnicas
    • 2.4 Classes
      • 2.4.1 Responsabilidades e Coesão
      • 2.4.2 Acoplamento entre Classes
    • 2.5 Unindo Conceitos
  • 3 Mapeamento de Código Limpo em Métricas de Código-fonte
    • 3.1 Métricas de Código-Fonte
    • 3.2 O Mapeamento
      • 3.2.1 Conjunto de Métricas
      • 3.2.2 Os Cenários
    • 3.3 Resumo dos Cenários
  • 4 Estudo de Caso
    • 4.1 Analizo
    • 4.2 Estado Inicial
    • 4.3 Segundo Estado
    • 4.4 Terceiro Estado
    • 4.5 Estado Final
      • 4.5.1 Resultados finais
  • 5 Conclusão
  • A Artigo Analizo SBES
  • Referências Bibliográficas
  • 2 SUMÁRIO 0.
  • 6 INTRODUÇÃO 1.

Capítulo 2

Código Limpo

A definição de um bom código não é precisa. Do mesmo modo que enfretamos dificuldades para definir o que é arte, não podemos definir um conjunto de parâmetros lógicos e mensuráveis para deli- mitar a diferença de qualidade entre códigos-fonte. Podemos considerar aspectos como testabilidade, eficiência, facilidade de modificação, processo pelo o qual foi desenvolvido, entre outros. Diante dessa dificuldade, nos deteremos a trabalhar com o chamado código limpo orientado a objetos no âmbito deste trabalho. No livro Clean Code (Martin, 2008), o autor entrevistou grandes especialistas em desenvolvimento de software como Ward Cunningham (colaborador na criação do Fit, do Wiki e da Programação Extrema (Beck, 1999)) e Dave Thomas (fundador da OTI - Object Technology International - e muito envolvido no Projeto Eclipse) questionando-os quanto a uma definição para código limpo. Cada um dos entrevistados elaborou respostas diferentes, destacando características subjetivas, como elegância, facilidade de alteração e simplicidade, e outras puramente técnicas, incluindo a falta de duplicações, presença de testes de unidade e de aceitação e a minimização do número de entidades. Em certo sentido, um código limpo está inserido em um estilo de programação que busca a proximidade a três valores: expressividade, simplicidade e flexibilidade. Tais termos são utilizados por Kent Beck no livro Implementation Patterns (Beck, 2007) que estão em conformidade com a unidade de pensamento que permeia as respostas dos especialistas.

Expressividade

Dentre as definições encontradas pela pesquisa de Robert Martin acima citada, algumas afirmações se destacam quanto a expressividade. Na visão de Grady Booch (Booch, 2007), um código limpo pode ser lido como um texto em prosa e deixa claro as intenções do autor através de operações e abstrações bem escolhidas. Já para Dave Thomas e Kent Beck, a medida para a expressividade está na facilidade para um desenvolvedor, que não o autor original do trecho de código, entender, modificar e utilizá-lo. Grande parte dessas ideias receberam influência do livro de Donald Knuth, Literate Programming (Knuth, 1992). A intenção era programar em linguagens naturais, como o inglês, e intercalar as expressões com macros e pequenos trechos de código-fonte tradicional. O arquivo poderia ser lido como um texto comum, mas no plano de fundo seriam executados os trechos correspondentes a cada uma das expressões em linguagem natural. Nesse sentido, um código é expressivo quando pode ser facilmente lido nas diferentes camadas de abstração, deixando detalhes de implementação “escondidos” em níveis mais baixos. Nas palavras de

2.2 NOMES 9

Além de auxiliar nas alterações, os testes compõem parte da expressividade, simplicidade e flexi- bilidade de um código. Para que desenvolvedores que não o autor de um trecho de código o compreen- dam, os testes podem reportar como o elemento deve ser usado. Por fim, a medida para a simplicidade pode ser feita através da suíte de teste: se os testes estão todos corretos, não é necessário adicionar complexidade, apenas melhorias sem adicionar funcionalidade. Outro tipo de empecilho que podemos encontrar são os comentários e documentações para métodos e classes. Para qualquer alteração feita no código, em tese os comentários e formas de documentação também deveriam ser mudados para ficarem de acordo com o estado atual. Uma contraposição é muito frequente, como aponta Robert Martin: por um lado, se nos policiarmos para que a atualização do código e comentários seja simultânea, muito provavelmente deixaremos de fazer alterações devido ao excesso de trabalho; por outro, se deixarmos de atualizar uma das formas de documentação, estaremos tornando-as dispensáveis, além de potencialmente se tornarem fontes de informações incorretas. A solução dada é concentrar a atenção no desenvolvimento de um código limpo. Se as variáveis estiverem devidamente nomeadas, não precisaremos criar um comentário para explicá-las. Se os mé- todos estiverem bem nomeados e possuírem uma única tarefa, não será necessário documentar o que são os parâmetros e o valor de retorno. E por fim, se os testes estiverem bem executados, teremos uma documentação executável do código que garante a corretude. Robert Martin enfatiza que o desejado é não engessar o desenvolvimento e melhoria do código com dificuldades para os programadores, mas compreender quais elementos do software precisam de documentação e contruí-la adequadamente.

As seções subsequentes apresentam aspectos importantes para termos um código limpo no que diz respeito a detalhes quanto aos nomes, métodos e classes.

2.2 Nomes

Em uma primeira análise sobre um código-fonte, uma das afirmações que pode ser feita é que ele é constituído por um conjunto de palavras reservadas da linguagem e um número imenso de nomes escolhidos pelos desenvolvedores. Por exemplo, um código escrito em Smalltalk praticamente não contém palavras da sintaxe da linguagem, contendo uma grande quantidade de envio de mensagens aos objetos e usos de variáveis, ambos com nomes escolhidos pelos desenvolvedores.

Nomes que Revelam Intenção

De acordo com Robert Martin, o nome de uma variável, método ou classe deveria responder a todas as questões a cerca do elemento sendo nomeado. Deveria contar porque o elemento existe, o que faz e como deve ser usado, de forma que comentários se tornem redundantes. O exemplo abaixo ilustra a dificuldade do leitor em compreender um código com nomes pouco reveladores.

def caminhoR ( V e r t i c e v ) : V e r t i c e w e s t [ v ] = 0 f o r (w = 0 ; w < tamanho ( ) ; w++) i f ( a d j ( v , w) ) i f ( e s t [ w ] == −1) :

10 CÓDIGO LIMPO 2.

imprime (w) caminhoR (w) Listing 2.1: Exemplo de nomes pouco reveladores

Esse método, pertencente a uma classe Grafo, é difícil de ser compreendido sem um vasto conheci- mento da convenção de notações e o algoritmo utilizado. Muitas perguntas poderiam ser feitas como (i) O que faz caminhoR?, (ii) O que é o vértice v? E quanto ao w? Eles tem algo em comum?, (iii) O que significa adj(v, w) ser igual a true?. A seguir segue o mesmo código com alterações somente nos nomes das variáveis e métodos, sem nenhuma alteração na implementação.

def imprimeVerticesDoPasseioComOrigemEm ( V e r t i c e origem ) : V e r t i c e proximo ; e s t a d o [ origem ] = JA_VISITADO f o r ( proximo = 0 ; proximo < numeroDeVertices ( ) ; proximo++) i f ( s a o A d j a c e n t e s? ( origem , proximo ) ) i f ( e s t a d o [ proximo ] == NAO_VISITADO) : imprime ( proximo ) imprimeVerticesDoPasseioComOrigemEm ( proximo ) Listing 2.2: Melhorias nos nomes podem tornar o código mais claro

Muitas questões podem ser respondidas depois da leitura desse trecho. O nome da função deixa claro que sua tarefa é passear pelos vértices do grafo começando pelo vértice origem passado como pa- râmetro. O vetor est foi renomeado para estado e as constante 0 e -1 receberam um nome, revelando o significado das operações em que constam. A simples mudança de tamanho() para numeroDeVertices() provê um contexto mais completo para o entendimento do laço. O exemplo poderia ser melhorado para ficar mais alinhado com o paradigma da orientação a objetos e com os conceitos que serão apresentados mais adiante. Nesta seção, tratamos a importância da escolha de nomes e como pode auxiliar o entendimento. É possível revelar o significado de elementos dando nomes para as constantes, por exemplo. Nomes como origem e próximo podem nomear objetos da mesma classe para mostrar o contexto e semântica de como estão sendo usados. Métodos podem trazer mais expressividade e fluência à leitura se forem nomeados levando em conta o código cliente e nas circunstâncias em que será chamado.

Diferenças Significativas

Durante a criação de um método ou uma classe, é muito comum que, no mesmo contexto, haja objetos de tipos iguais. Nessa situação, não basta escolher nomes que representem bem o que é o objeto, mas também é preciso criar uma diferença significativa entre eles. O intuito é deixar claro ao leitor do código (ou seja, o programador) o papel de cada uma das partes, tanto para facilitar o uso de métodos deixando claro a ordem dos parâmetros, quanto para que a semântica de uma operação fique mais clara. Quando um leitor se depara com um método como copiaElementos(lista1, lista2), pode não ficar claro qual é a ordem dos argumentos: os elementos da lista1 são copiados para lista2 ou vice-versa? Com uma simples distinção criada através dos nomes, a leitura não levanta nenhum tipo de dúvidas em copiaElementos(origem, destino).

12 CÓDIGO LIMPO 2.

papel e o que exatamente está sendo executado. Essas são as características desejadas para os nomes dos métodos formarem em conjunto uma narrativa da execução.

2.3 Métodos

Os métodos são o núcleo fundamental para a criação de um código expressivo. Considerando as ideias levantadas por Grady Booch e Kent Beck, o objetivo é desenvolver um código que seja lido como um texto em prosa e, nessa analogia, a intenção é utilizar os nomes dos métodos para narrar a execução. De acordo com os princípios já abordados anteriormente, buscamos minimizar as repetições e queremos que as mudanças sejam localizadas, de forma que a lógica esteja perto dos dados que manipula. Nesse sentido, os métodos encapsulam um trecho de código, provendo um escopo fechado para variáveis locais e permitindo chamadas como uma maneira de evitar duplicações.

2.3.1 Fundamentação Teórica

O primeiro fundamento levantado no livro Clean Code quanto aos métodos é uma preocupação quanto ao tamanho. Segundo Robert Martin, métodos devem ser pequenos (e se possível menores do que isso). O autor também cita que não existe uma fundamentação científica para tal afirmação, além de não podermos afirmar o que é “pequeno” e definir limitantes quanto ao número de linhas. A proposta é que cada método seja pequeno o suficiente para facilitar sua leitura e compreensão. Devemos ter em vista a dificuldade de assimilação de grandes porções de informação durante a leitura e o fato de que nem sempre uma instrução é clara. A partir dessas ideias, um método será uma porção curta de código que trabalha com poucas variáveis e tem um nome explicativo que espelha a sua funcionalidade. Uma maneira bastante interessante de pensar sobre o tamanho dos métodos não é simplesmente contar seu número de linhas, mas compreender bem a tarefa que realiza. Nas palavras de Robert Martin, “Funções deveriam ter uma única tarefa. Deveriam fazê-la bem. E fazê-la somente.” Isso significa que para criar uma lista de números primos usando o algoritmo do Crivo de Erastó- tenes, não queremos ter um único método que contenha todos os detalhes da criação de uma lista de inteiros e a marcação de múltiplos que não tem chance de serem primos. Seria muito difícil entendê-lo e modificar detalhes da implementação. Queremos várias métodos pequenos que fazem cada uma das tarefas necessárias para essa computação, de forma que a leitura seja suficiente e uma documentação externa redundante. Outra questão teórica importante e enfatizada por Kent Beck são os níveis de abstrações e a simetria de operações dentro de um método. Um incremento de uma variável de instância está fun- damentalmente em outro nível de abstração do que a chamada de um método. Não queremos um método com essas duas instruções. Isso possivelmente faz com que o leitor não saiba se uma operação é um detalhe de implementação ou um conceito importante dentro da lógica, além de abrir as portas para mais ocorrências deste mesmo tipo, aumentando a complexidade do código a cada alteração. Pensando em outros fatores que podem levar a dificuldades para a leitura do nosso código, sabemos que estamos trabalhando em um código limpo quando cada rotina que lemos faz o que esperávamos (Martin, 2008). No contexto de métodos, como seus nomes são a documentação da tarefa que reali- zam, não queremos que, ao olhar o corpo de um deles, nos deparemos com uma operação que não

2.3 MÉTODOS 13

imaginávamos. Ao criar um método, temos que considerar que os leitores não terão a mesma linha de pensamento que temos naquele momento e, diante disso, temos que ter uma crescente preocupação com todas as nossas decisões. O último fundamento teórico quanto aos métodos também diz respeito ao programa como um todo. Queremos ter um fluxo normal bem estabelecido, deixando-o simples e o tratamento de erros separado. Em outras palavras, não queremos que o código fiquei cheio de verificações de erro misturados com a lógica sem erros.

2.3.2 Técnicas

Tendo em vista a fundamentação teórica, a seguir descrevemos algumas técnicas que nos possibi- litam criar métodos que se encaixem no estilo de programação buscado.

Composição de Métodos

A Composição de Métodos é a base para a criação de um código limpo. A proposta é compor nossos métodos em chamadas para outros métodos rigorosamente no mesmo nível de abstração. Usando o exemplo do algoritmo do Crivo de Erastótenes, para encontrarmos os primos de 1 a N devemos criar um conjunto de elementos que represente os inteiros de 1 a N, marcar todos números múltiplos de outros e depois coletar todos aqueles que não estão marcados (os primos). Para isso criamos um método da classe GeradorDePrimos:

def primosAte (N) : c r i a I n t e i r o s D e s m a r c a d o s A t e (N) m a r c a M u l t i p l o s ( ) colocaNaoMarcadosNoResultado ( ) r e t o r n a r e s u l t a d o Listing 2.5: Algoritmo do Crivo de Erastótenes decomposto em métodos

Nesse exemplo, compomos o método primosAte(N) a partir de chamadas para outros métodos, cada um com uma tarefa e um nome explicativo. Dessa forma, o leitor pode entender facilmente como funciona o algoritmo de uma maneira geral. Também ficam bastante claros os níveis de abstrações criados. A função primosAte(n) está em um nível e os métodos que invoca estão todos no mesmo patamar logo abaixo. Queremos criar essa estrutura em cadeia, tornando possível que o leitor só precise entender os níveis que interessem no momento. A composição de métodos não é uma técnica que desconhecemos, uma vez que a todo momento criamos estruturas dessa maneira sem ter conhecimento da sua efetividade. Entretanto, a facilidade de leitura propiciada faz com que esse seja o padrão mais relevante do livro Implementation Patterns segundo o próprio autor (Inf, 2008; Beck, 2007).

Métodos Explicativos (Explaining Methods)

Para auxiliar na expressividade de um trecho de código, podemos criar um método com nome específico do contexto em que será invocado. A operação pode ser bastante simples como um incre- mento de variável ou a chamada de um método em um objeto guardado em uma variável de instância,

2.3 MÉTODOS 15

Evitar Estruturas Encadeadas

Tendo em vista a busca por métodos pequenos e com uma única tarefa, um aspecto relevante é a criação de estruturas encadeadas. Se um método tem uma cadeia de ifs e elses, o leitor terá dificuldades para compreender todos os casos e fluxos possíveis. Em ambos os livros estudados, os autores enfatizam o uso de chamadas de métodos logo em seguida de um condicional. Novamente a operação que será encapsulada estará dentro de um método com nome expressivo, o que deixa esse método curto e expressivo. Mais uma vez utilizando o exemplo do Crivo de Eristótenes, é necessário marcar todos os números que sejam múltiplos de um primo, representado por um número não marcado.

def m a r c a M u l t i p l o s ( ) : f o r ( c a n d i d a t o = 0 ; c a n d i d a t o <= l i m i t e ; c a n d i d a t o++) marcaMultiplosSeNaoEstaMarcado ( c a n d i d a t o )

def marcaMultiplosSeNaoEstaMarcado ( c a n d i d a t o ) : i f ( naoMarcado ( c a n d i d a t o ) ) marcaMultiplosDe ( c a n d i d a t o )

def marcaMultiplosDe ( primo ) : f o r ( m u l t i p l o = 2∗ primo ; m u l t i p l o < l i m i t e ; m u l t i p l o += i ) numeros [ m u l t i p l o ]. marca ( ) Listing 2.11: Alternativa para evitar estruturas encadeadas usando funções pequenas

Cada método encapsula um nível na cadeia de estruturas encadeadas. Ao invés de muitos fors e ifs encadeados, obtivemos funções pequenas e muito focadas em uma única tarefa que podem ser mais facilmente testadas de forma independente.

Cláusulas Guarda (Guard Clauses)

Outra técnica para evitar o uso de estruturas complexas de condicionais são as cláusulas guarda. Quando criamos uma expressão condicional (if), o leitor naturalmente espera um bloco com a contra- partida (else). A ideia é criar um retorno breve para expressar que uma das partes dessa contraposição é mais relevante. Portanto, a estrutura exemplificada no trecho de código 2.13 é mais recomendada do que no trecho de código 2.12 nos exemplos abaixo, pois revela ao leitor que o fluxo mais relevante é o caso em que ocorre a inicialização, uma vez que, no caso contrário, nenhuma operação é executada.

def i n i c i a l i z a ( ) : i f (! j a I n i c i a l i z a d o ) // Implementacao do c o d i g o de i n i c i a l i z a c a o Listing 2.12: Método sem Cláusula Guarda

def i n i c i a l i z a ( ) : return i f ( j a I n i c i a l i z a d o ) // Implementacao do c o d i g o de i n i c i a l i z a c a o Listing 2.13: Método com Cláusula Guarda

16 CÓDIGO LIMPO 2.

Objeto Método (Method Object)

Ao dividir os métodos em outros menores com uma única tarefa, podemos criar métodos com grande complexidade e difícil refatoração: muitas variáveis, muito parâmetros necessários e/ou muitas estruturas encadeadas. Diante dessa situação, uma alternativa possível é utilizar um objeto (Method Object) que encapsule essa lógica. O exemplo 2.14 ilustra uma classe ImpressorDePrimos cujo trabalho é imprimir o enésimo primo. Sendo assim, calcula tal número através do método calculaPrimo que implementa o Crivo de Erastó- tenes, operação complexa que, apesar de receber poucos parâmetros, possui muitas tarefas.

c l a s s e ImpressorDePrimos : def imprimePrimo ( n ) : primo = c a l c u l a P r i m o ( n ) p r i n t "−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−" p r i n t "O primo numero " + n + " eh " + primo p r i n t "−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−"

def c a l c u l a P r i m o ( n ) : // implementacao do Crivo de E r a s t o t e n e s Listing 2.14: Classe ImpressorDePrimos com método complexo

Abaixo exemplificada, a classe CalculadorDoEnesimoPrimo recebe como parâmetro de seu cons- trutor o valor n relativo ao número do primo a ser calculado e o atribui a uma variável de instância. Sua interface é somente composta pelo método calcula. Com essa mudança, todos os detalhes da operação complexa foram encapsulados em uma classe que terá testes, aumentando nossa confiança e flexibilidade para refatorações de um método com muitas tarefas.

c l a s s e ImpressorDePrimos : def imprimePrimo ( n ) : primo = c a l c u l a P r i m o ( n ) p r i n t "−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−" p r i n t "O primo numero " + n + " eh " + primo p r i n t "−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−"

def c a l c u l a P r i m o ( n ) : c a l c u l a d o r = CalculadorDoEnesimoPrimo ( n ) return c a l c u l a d o r. c a l c u l a ( )

c l a s s e CalculadorDoEnesimoPrimo : instVar numeroDoPrimoProcurado

def c o n s t r u t o r ( n ) : numeroDoPrimoProcurado = n

def c a l c u l a ( ) : // implementacao do Crivo de E r a s t o t o n e s

Listing 2.15: Classe ImpressorDePrimos delega a tarefa para um novo objeto da classe CalculadorDoEnesimoPrimo