Qual é o comprimento ideal de um método para você? [fechadas]

110

Na programação orientada a objetos, não há nenhuma regra exata sobre o comprimento máximo de um método, mas eu ainda acho essas duas citações um pouco contraditórias, então eu gostaria de ouvir o que você pensa.

Em Código Limpo: Um Manual de Artesanato Ágil de Software , Robert Martin diz :

The first rule of functions is that they should be small. The second rule of functions is that they should be smaller than that. Functions should not be 100 lines long. Functions should hardly ever be 20 lines long.

e ele dá um exemplo do código Java que ele vê de Kent Beck:

Every function in his program was just two, or three, or four lines long. Each was transparently obvious. Each told a story. And each led you to the next in a compelling order. That’s how short your functions should be!

Isso parece ótimo, mas, por outro lado, no código concluído , Steve McConnell diz algo muito diferente:

The routine should be allowed to grow organically up to 100-200 lines, decades of evidence say that routines of such length no more error prone then shorter routines.

E ele dá uma referência a um estudo que diz que rotinas de 65 linhas ou longas são mais baratas de se desenvolver.

Portanto, embora haja opiniões divergentes sobre o assunto, existe uma prática recomendada para você?

    
por iPhoneDeveloper 05.02.2012 / 11:26
fonte

14 respostas

105

As funções normalmente devem ser curtas, entre 5-15 linhas é minha "regra prática" pessoal quando se codifica em Java ou C #. Este é um bom tamanho por vários motivos:

  • Ele cabe facilmente na sua tela sem rolar
  • É sobre o tamanho conceitual que você pode ter na sua cabeça
  • É suficientemente significativo para exigir uma função por si só (como uma parte significativa e lógica da lógica)
  • Uma função menor que 5 linhas é uma dica de que você talvez esteja quebrando demais o código (o que dificulta a compreensão se você precisar navegar entre as funções). Ou isso ou você está esquecendo seus casos especiais / tratamento de erros!

Mas não acho que seja útil definir uma regra absoluta, pois sempre haverá exceções / razões válidas para divergir da regra:

  • Uma função de acesso de uma linha que executa um tipo de conversão é claramente aceitável em algumas situações.
  • Existem algumas funções muito curtas, mas úteis (por exemplo, trocar como mencionado pelo usuário desconhecido) que claramente precisam de menos de 5 linhas. Não é grande coisa, algumas funções de 3 linhas não causam nenhum dano à sua base de código.
  • Uma função de 100 linhas que é uma única instrução de troca grande pode ser aceitável se estiver extremamente claro o que está sendo feito. Esse código pode ser conceitualmente muito simples, mesmo que exija muitas linhas para descrever os diferentes casos. Às vezes é sugerido que isso deve ser refatorado em classes separadas e implementado usando herança / polimorfismo, mas IMHO está levando a OOP longe demais - eu prefiro ter uma grande declaração de switch de 40 direções do que 40 novas classes para lidar.
  • Uma função complexa pode ter muitas variáveis de estado que ficariam muito confusas se passadas entre diferentes funções como parâmetros. Nesse caso, você poderia razoavelmente argumentar que o código é mais simples e fácil de seguir se você mantiver tudo em uma única função grande (embora, como Mark corretamente aponta, isso também possa ser um candidato a se transformar em uma classe para encapsular a lógica e estado)
  • Às vezes, funções menores ou maiores têm vantagens de desempenho (talvez por motivos inlining ou JIT, como Frank menciona). Isso é altamente dependente da implementação, mas pode fazer a diferença - certifique-se de fazer um benchmark!

Então, basicamente, use o bom senso , use pequenos tamanhos de funções na maioria dos casos, mas não seja dogmático se tiver uma boa razão para fazer um função incomumente grande.

    
por 05.02.2012 / 11:44
fonte
28

Embora eu concorde com os comentários de outras pessoas quando disseram que não há regras rígidas sobre o número correto de LOC, aposto que se olharmos para os projetos que analisamos no passado e identificarmos todas as funções acima, digamos 150 linhas de código Acredito que chegaríamos a um consenso de que 9 de 10 dessas funções quebram o SRP (e muito provavelmente o OCP também), têm muitas variáveis locais, muito controle de fluxo e são geralmente difíceis de ler e manter. / p>

Assim, enquanto o LOC não pode ser um indicador direto de código ruim, é certamente um indicador indireto decente de que determinada função poderia ser melhor escrita.

No meu time, caí na posição de líder e, por qualquer motivo, as pessoas parecem estar me ouvindo. O que eu geralmente resolvi é dizer à equipe que, embora não haja limite absoluto, qualquer função com mais de 50 linhas de código deve, no mínimo, levantar uma bandeira vermelha durante a revisão do código, para que possamos dar uma segunda olhada nele e reavaliá-lo para complexidade e violações de SRP / OCP. Depois desse segundo olhar, podemos deixá-lo em paz ou podemos mudá-lo, mas pelo menos faz as pessoas pensarem sobre essas coisas.

    
por 06.02.2012 / 04:36
fonte
19

Entrei em um projeto que não tinha nenhum cuidado com as diretrizes de codificação. Quando olho para o código, às vezes, encontro classes com mais de 6000 linhas de código e menos de 10 métodos. Este é um cenário de horror quando você tem que consertar bugs.

Uma regra geral de quão grande um método deve ser no máximo às vezes não é tão boa. Eu gosto da regra de Robert C. Martin (Tio Bob): "Os métodos devem ser pequenos, menores que pequenos". Eu tento usar essa regra o tempo todo. Estou tentando manter meus métodos simples e pequenos, esclarecendo que meu método faz apenas uma coisa e nada mais.

    
por 05.02.2012 / 12:15
fonte
10

Não é sobre o número de linhas, é sobre o SRP. De acordo com este princípio, seu método deve fazer uma e somente uma coisa.

Se o seu método fizer isso E isso E isso OU isso = > provavelmente está fazendo muito. Tente olhar para esse método e analisar: "aqui eu recebo esses dados, organizo e obtenho elementos que preciso" e "aqui eu processo esses elementos" e "aqui eu finalmente os combino para obter o resultado". Esses "blocos" devem ser refatorados para outros métodos.

Se você simplesmente seguir o SRP, a maior parte do seu método será pequena e com intenção clara.

Não é correto dizer "este método é > 20 linhas por isso está errado". Pode ser uma indicação de que algo pode estar errado com este método, não mais.

Você pode ter um switch de 400 linhas em um método (geralmente acontece em telecom), e ainda é uma responsabilidade única e é perfeitamente OK.

    
por 05.02.2012 / 13:51
fonte
8

Acho que um problema aqui é que a duração de uma função não diz nada sobre sua complexidade. LOC (Linhas de Código) são um instrumento ruim para medir qualquer coisa.

Um método não deve ser excessivamente complexo, mas há cenários em que um método longo pode ser facilmente mantido. Observe que o exemplo a seguir não diz que não pode ser dividido em métodos, apenas que os métodos não alterariam a capacidade de manutenção.

por exemplo, um manipulador para dados de entrada pode ter uma grande instrução switch e, em seguida, um código simples por caso. Eu tenho esse código - gerenciando dados de entrada de um feed. 70 (!) Manipuladores numericamente codificados. Agora, alguém dirá "use constantes" - sim, exceto que a API não fornece e eu gosto de ficar perto de "source" aqui. Métodos? Claro - infelizmente todos eles lidam com dados das mesmas duas enormes estruturas. Nenhum benefício em dividi-los, exceto talvez ter mais métodos (legibilidade). O código não é intrinsecamente complexo - um switch, dependendo de um campo. Então cada caso tem um bloco que analisa x elementos de dados e os publica. Nenhum pesadelo de manutenção. Há uma repetição "se condição que determina se um campo tem dados (pField = pFields [x], se pField- > IsSet () {blabla}) - o mesmo para todos os campos ...

Substitua por uma rotina muito menor contendo um loop aninhado e muitas declarações de switching reais, e um método grande pode ser mais fácil de manter do que um menor.

Então, desculpe, LOC não é uma boa medida para começar. Se houver alguma coisa, os pontos de complexidade / decisão devem ser usados.

    
por 05.02.2012 / 11:49
fonte
8

Depende , sério, realmente não há uma resposta sólida para essa pergunta porque a linguagem com a qual você trabalha varia de cinco a quinze linhas mencionadas in esta resposta pode funcionar para C # ou Java, mas em outros idiomas não é muito útil trabalhar com você. Da mesma forma, dependendo do domínio em que você está trabalhando, você pode se encontrar escrevendo valores de configuração de código em uma estrutura de dados grande. Com algumas estruturas de dados você pode ter dezenas de elementos que você precisa definir, você deve dividir as funções separadas apenas porque sua função está em execução por muito tempo?

Como outros já observaram, a melhor regra geral é que uma função deve ser uma única entidade lógica que lida com uma única tarefa. Se você tentar impor regras draconianas que dizem que as funções não podem ser maiores que as linhas n e você tornar esse valor muito pequeno, seu código ficará mais difícil de ser lido conforme os desenvolvedores tentarem usar truques sofisticados para se locomover a regra. Da mesma forma, se você definir muito alto, isso não será um problema e poderá levar a um código incorreto devido à preguiça. Sua melhor aposta é apenas realizar revisões de código para garantir que as funções estejam lidando com uma única tarefa e deixá-la assim.

    
por 06.02.2012 / 05:01
fonte
5

Se eu encontrar um método longo - eu posso apostar que este método não é testado corretamente na unidade ou na maior parte do tempo ele não tem teste unitário. Se você começar a fazer o TDD, nunca criará métodos de 100 linhas com 25 responsabilidades diferentes e 5 loops aninhados. Testes obrigam você a refatorar constantemente sua bagunça e a escrever o código limpo de Bob do tio.

    
por 11.04.2013 / 22:16
fonte
5

Eu estive nessa maluca, de um jeito ou de outro, desde 1970.

Em todo esse tempo, com duas exceções que eu vou chegar em um momento, eu nunca vi uma "rotina" bem projetada (método, procedimento, função, sub-rotina, seja o que for) que precisava ser mais de um página impressa (cerca de 60 linhas) longa. A grande maioria deles foi um pouco curta, da ordem de 10 a 20 linhas.

Eu vi, no entanto, um monte de código de "fluxo de consciência", escrito por pessoas que aparentemente nunca ouviram falar de modularização.

As duas exceções foram casos muito especiais. Um deles é, na verdade, uma classe de casos de exceção, que eu agrupo: grandes autômatos de estados finitos, implementados como grandes comandos de switch feios, geralmente porque não há uma maneira mais limpa de implementá-los. Essas coisas geralmente aparecem em equipamentos de teste automatizados, analisando os registros de dados do dispositivo em teste.

O outro foi a rotina de torpedos de fótons do jogo STARTRK de Matuszek-Reynolds-McGehearty-Cohen, escrito no CDC 6600 FORTRAN IV. Tinha que analisar a linha de comando, depois simular o vôo de cada torpedo, com perturbações, verificar a interação entre o torpedo e cada tipo de coisa que poderia atingir, e a propósito simular a recursão para fazer conectividade de 8 vias em cadeias de novae de torpedear uma estrela que estava ao lado de outras estrelas.

    
por 28.07.2014 / 20:26
fonte
4

Vou apenas adicionar mais uma citação.

Programs must be written for people to read, and only incidentally for machines to execute

-- Harold Abelson

É muito improvável que funções que aumentem para 100-200 sigam essa regra

    
por 20.06.2014 / 14:17
fonte
1

Não há regras absolutas sobre o tamanho do método, mas as seguintes regras foram úteis:

  1. O objetivo principal da função é encontrar o valor de retorno. Não há outro motivo para sua existência. Depois que esse motivo for preenchido, nenhum outro código deverá ser inserido nele. Isso necessariamente mantém as funções pequenas. Chamar outras funções só deve ser feito se facilitar a localização do valor de retorno.
  2. Por outro lado, as interfaces devem ser pequenas. Isso significa que você tem um grande número de classes, ou você tem grandes funções - uma das duas acontecerá quando você começar a ter código suficiente para fazer algo significativo. Grandes programas podem ter ambos.
por 05.02.2012 / 17:15
fonte
1

IMHO, você não deveria ter que usar a barra de rolagem para ler sua função. Assim que você precisar mover a barra de rolagem, leve mais algum tempo para entender como funciona a função.

Assim, depende do ambiente de programação habitual do trabalho da equipe (resolução da tela, editor, tamanho da fonte, etc ...). Nos anos 80, foram 25 linhas e 80 colunas. Agora, no meu editor, exibo quase 50 linhas. O número de colunas que eu exibo não mudou, já que dividi minha tela em duas para exibir dois arquivos às vezes.

Em resumo, depende da configuração de seus colegas de trabalho.

    
por 05.07.2013 / 10:36
fonte
1

Os autores significam a mesma coisa por "função" e "rotina"? Normalmente, quando digo "função" quero dizer uma sub-rotina / operação que retorna um valor e "procedimento" para um que não faz (e cuja chamada se torna uma única instrução). Esta não é uma distinção comum em toda a SE no mundo real, mas eu a vi em textos de usuário.

De qualquer forma, não há resposta certa para isso. A preferência por um ou outro (se houver preferência) é algo que eu esperaria que fosse muito diferente entre idiomas, projetos e organizações; assim como acontece com todas as convenções de código.

O bit que eu acrescentaria é que as operações "operações longas não são mais propensas a erros do que as operações curtas" não são estritamente verdadeiras. Além do fato de que mais código é igual a mais espaço de erro potencial, é extremamente óbvio que quebrar código em segmentos tornará os erros mais fáceis de serem evitados e mais fáceis de localizar. Caso contrário, não haveria motivo para quebrar o código em partes, exceto a repetição. Mas isso talvez seja verdade apenas se esses segmentos estiverem suficientemente documentados para que você possa determinar os resultados de uma chamada de operação sem ler ou rastrear o código real (projeto por contrato baseado em especificações, em vez de dependência concreta entre áreas de código).

Além disso, se você quiser que operações mais longas funcionem bem, convém adotar convenções de código mais rigorosas para suportá-las. Lançar uma instrução de retorno no meio de uma operação pode ser bom para uma operação curta, mas em operações mais longas isso pode criar uma grande seção de código que é condicional mas não está obviamente dependente de uma leitura rápida (apenas para um exemplo).

Então, eu acho que o estilo que é menos provável de ser um pesadelo cheio de bugs dependeria, em grande parte, de quais convenções você seguia para o resto do seu código. :)

    
por 28.07.2014 / 22:48
fonte
1

Acho que a resposta da TomTom chegou perto de como me sinto a respeito disso.

Cada vez mais eu me encontro com complexidade ciclomática ao invés de linhas.

Eu normalmente não viso mais de uma estrutura de controle por método, com exceção de quantos loops forem necessários para lidar com uma matriz multidimensional.

Às vezes, eu me pego colocando ifs de uma linha em casos de troca porque, por algum motivo, esses tendem a ser casos em que dividi-lo dificulta em vez de ajudar.

Note que não conto a lógica de guarda contra esse limite.

    
por 29.07.2014 / 06:42
fonte
-2

No OOP tudo é objeto e existem esses recursos:

  1. Polimorfismo
  2. Abstração
  3. Herança

Quando você observa essas regras, seus métodos geralmente são pequenos, mas não existem regras para regras pequenas ou muito pequenas (por exemplo, de 2 a 3 linhas). Um benefício do método pequeno (pequena unidade, por exemplo, método ou função) é:

  1. melhor legível
  2. manter melhor
  3. bug corrigido melhor
  4. muda melhor
por 05.02.2012 / 11:45
fonte