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

122

Na programação orientada a objetos, é claro que não existe uma regra exata sobre o tamanho máximo de um método, mas eu ainda encontrei essas duas aspas que se contradizem, de modo que gostaria de ouvir o que você pensa.

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

A primeira regra das funções é que elas devem ser pequenas. A segunda regra das funções é que elas devem ser menores que isso. As funções não devem ter 100 linhas. As funções quase nunca devem ter 20 linhas.

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

Todas as funções do programa tinham apenas duas, três ou quatro linhas. Cada um era claramente óbvio. Cada um contou uma história. E cada um levou você ao próximo em uma ordem convincente. É assim que suas funções devem ser curtas!

Isso parece ótimo, mas por outro lado, no Code Complete , Steve McConnell diz algo muito diferente:

Deve-se permitir que a rotina cresça organicamente até 100-200 linhas, décadas de evidência dizem que rotinas com esse tamanho não são mais propensas a erros do que rotinas mais curtas.

E ele faz referência a um estudo que diz que rotinas com 65 linhas ou mais são mais baratas de desenvolver.

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

iPhoneDeveloper
fonte
17
As funções devem ser fáceis de entender. A duração deve seguir, dependendo das circunstâncias.
Henk Holterman
56
Eu acho que o limite real é de 53 linhas. Com um tamanho médio de linha de 32,4 caracteres. Sério, não há resposta definitiva. Um método de 100 linhas pode ser muito claro e sustentável, e um método de 4 linhas pode ser um pesadelo para entender. Geralmente, porém, os métodos longos tendem a ter muitas responsabilidades e são mais difíceis de entender e manter do que os menores. Eu pensaria em termos de responsabilidades e tentaria ter uma única responsabilidade por método.
23
Existe um termo na programação chamado "coerência funcional". O comprimento de uma função deve variar, desde que sua implementação ainda constitua uma única unidade coerente de lógica em seu aplicativo. A divisão arbitrária de funções para torná-las menores tem mais probabilidade de inchar seu código e prejudicar a manutenção.
9
E, se você quiser limitar a complexidade de suas funções, deve medir a complexidade ciclomática , não o comprimento. Uma switchdeclaração com 100 casecondições é mais sustentável que 10 níveis de ifdeclarações aninhadas uma na outra.
10
A abordagem de Bob Martin é de 2008, de Steve Mc Connell de 1993. Eles têm diferentes filosofias sobre o que é "bom código" e o IMHO Bob Martin tenta obter um nível muito mais alto de qualidade de código.
Doc Brown)

Respostas:

115

As funções normalmente devem ser curtas, entre 5-15 linhas é minha "regra de ouro" pessoal ao codificar em Java ou C #. Esse é um bom tamanho por vários motivos:

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

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

  • Uma função de acessador de uma linha que executa uma conversão de tipo é claramente aceitável em algumas situações.
  • Existem algumas funções muito curtas, mas úteis (por exemplo, swap conforme 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 danos à sua base de código.
  • Uma função de 100 linhas que é uma única declaração de chave grande pode ser aceitável se for extremamente claro o que está sendo feito. Este código pode ser conceitualmente muito simples, mesmo que exija muitas linhas para descrever os diferentes casos. Às vezes, sugere-se que isso seja refatorado em classes separadas e implementado usando herança / polimorfismo, mas IMHO está levando o OOP muito longe - eu prefiro ter apenas uma declaração grande de 40 vias do que 40 novas classes para lidar, além disso para uma instrução switch de 40 direções para criá-los.
  • 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 assinala corretamente, 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 inline ou JIT, como Frank menciona). Isso depende muito da implementação, mas pode fazer a diferença - certifique-se de fazer benchmark!

Então, basicamente, use o bom senso , atenha-se a pequenos tamanhos de função na maioria dos casos, mas não seja dogmático se você tiver um motivo genuinamente bom para criar uma função extraordinariamente grande.

Mikera
fonte
9
Também há uma razão técnica / perf para métodos curtos: acertos no cache JIT. Muitos métodos menores e reutilizáveis ​​têm mais probabilidade de serem chamados antes. Ah, e um benefício extra de diag, o StackTraces está mais focado na lógica que surgiu.
Luke Puplett
20
"usar o bom senso" é o conselho mais importante
Simon
Deve-se estar ciente de que essa heurística resultará em reclamações de "código ravioli" devido à profundidade da pilha nos pontos de interrupção durante a depuração.
Frank Hileman
5
@FrankHileman tomarei ravioli sobre o espaguete qualquer dia ao escrever código
11
@ Snowman: essas opções não são mutuamente exclusivas ... profundidade de pilha pequena sem espaguete é o ideal. Profundidades profundas da pilha, com espaguete ao lado?
19414 Frank Hileman
30

Embora eu concorde com os comentários de outros quando eles disseram que não há uma regra rígida sobre o número LOC correto, aposto que se olharmos para os projetos que vimos no passado e identificarmos todas as funções acima, digamos 150 linhas de código, eu ' Acho que chegamos a um consenso de que 9 em cada 10 dessas funções quebram o SRP (e provavelmente o OCP também), têm muitas variáveis ​​locais, muito fluxo de controle e geralmente são difíceis de ler e manter.

Portanto, embora o LOC possa não ser um indicador direto de código incorreto, é certamente um indicador indireto decente que determinada função possa ser melhor escrita.

Na minha equipe, caí na posição de líder e, por qualquer motivo, as pessoas parecem estar me ouvindo. Em geral, decidi 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 e reavaliar por complexidade e violações de SRP / OCP. Após esse segundo olhar, podemos deixá-lo em paz ou mudar, mas pelo menos isso faz as pessoas pensarem sobre essas coisas.

DXM
fonte
3
Isso parece sensato - LOC não significa nada em relação à complexidade ou à qualidade do código, mas pode ser um bom marcador para a possibilidade de que as coisas devam ser refatoradas.
cori 31/05
4
"chegar a um consenso de que 9 em cada 10 dessas funções quebrar SRP" - eu discordo, eu tenho certeza que 10 de 10 dessas funções vai quebrá-lo ;-)
Doc Brown
11
+1 por levantar uma bandeira durante a revisão do código: em outras palavras, não é uma regra rígida, mas vamos discutir esse código como um grupo.
22

Entrei em um projeto que não teve 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ê precisa corrigir bugs.

Uma regra geral de quão grande um método deve ter no máximo às vezes não é tão boa. 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.

Smokefoot
fonte
4
Eu não vejo por que uma função 6000 linha é mais difícil para depurar do que um mais curto ... A menos que ele tem outros problemas também (por exemplo, a repetição.)
Calmarius
17
Não tem nada a ver com depuração .. é sobre complexidade e manutenção. Como você estenderia ou alteraria um método de 6000 linhas feito por outro?
Smokefoot
10
@Calmarius, a diferença é geralmente que as funções de 6000 linhas tendem a conter variáveis ​​locais que foram declaradas muito distantes (visualmente), dificultando ao programador a construção do contexto mental necessário para ter alta confiança no código. Você pode ter certeza de como uma variável é inicializada e criada em um determinado momento? Você tem certeza de que nada vai mexer com sua variável depois de configurá-la na linha 3879? Por outro lado, com 15 métodos de linha, você pode ter certeza.
Daniel B
8
A @Calmarius concordou, mas ambas as declarações são um argumento contra as 6000 funções do LOC.
Daniel B
2
Uma variável local em um método de 600 linhas é essencialmente uma variável global.
MK01 30/11
10

Não se trata de número de linhas, é de SRP. De acordo com esse princípio, seu método deve fazer uma e apenas uma coisa.

Se o seu método faz isso AND this AND this OR that => provavelmente está fazendo muito. Tente analisar esse método e analisar: "aqui eu recebo esses dados, os classifico e obtemos os elementos necessários" 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, grande parte do seu método será pequeno e com clara intenção.

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

Você pode ter um switch de 400 linhas em um método (geralmente acontece em telecomunicações), e ainda é de responsabilidade única e está perfeitamente bem.

Alexey
fonte
2
Instruções grandes de switch, formatação de saída, definições de hashes / dicionários que devem ser codificados em vez de flexíveis em algum banco de dados, isso geralmente acontece e é perfeitamente adequado. Enquanto a lógica for desviada, você estará bem. Um método grande pode fazer com que você pense "devo dividir isso". A resposta poderia muito bem ser 'não, isso é bom, pois é' (ou sim, esta é uma confusão absoluta)
Martijn
O que significa "SRP"?
Thomthom
3
SRP significa Princípio de Responsabilidade Única e afirma que toda classe (ou método) deve ter apenas uma responsabilidade. Está relacionado à coesão e acoplamento. Se você seguir o SRP, suas classes (ou métodos) terão boa coesão, mas o acoplamento pode ser aumentado porque você acaba com mais classes (ou métodos).
Kristian Duske
+1 para SRP. Ao escrever funções coesas, é possível combiná-las mais facilmente em estilo funcional para obter resultados mais complexos. No final, é melhor que uma função seja composta por três outras funções que foram coladas juntas do que ter uma única função executando três coisas distintas, mesmo que de alguma forma relacionadas.
Mario T. Lanza
Sim, mas o que é uma responsabilidade única. É apenas um conceito criado em sua cabeça. Se você precisa de 400 linhas para uma única responsabilidade, seu conceito de responsabilidade única provavelmente é muito diferente do meu
Xitcod13 25/07/07
9

Depende , sério, realmente não há uma resposta sólida para essa pergunta, porque a linguagem com a qual você trabalha trabalha, as cinco a décima quinta linhas mencionadas nesta resposta podem funcionar para C # ou Java, mas em outras linguagens ela não fornece você tem muito para trabalhar. Da mesma forma, dependendo do domínio em que você está trabalhando, você pode escrever valores de configuração de código em uma grande estrutura de dados. Com algumas estruturas de dados, você pode ter dezenas de elementos que precisa definir, você deve separar as coisas para separar funções apenas porque sua função está demorando muito?

Como outros 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 exceder n linhas e você tornar esse valor muito pequeno, seu código ficará mais difícil de ler à medida que os desenvolvedores tentarem usar truques sofisticados para contornar a regra. Da mesma forma, se você definir um valor muito alto, isso não será um problema e poderá resultar em código incorreto, embora preguiçoso. Sua melhor aposta é apenas realizar revisões de código para garantir que as funções estejam lidando com uma única tarefa e deixar assim.

rjzii
fonte
8

Acho que um problema aqui é que o comprimento 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 de dados recebidos pode ter uma declaração de chave grande e, em seguida, código simples por caso. Eu tenho esse código - gerenciando dados recebidos de um feed. 70 (!) Manipuladores numericamente codificados. Agora, alguém dirá "use constantes" - sim, exceto que a API não as fornece e eu gosto de ficar perto da "fonte" aqui. Métodos? Claro - infelizmente, todos eles lidam com dados das mesmas duas grandes estruturas. Não há benefício em separá-los, exceto talvez com mais métodos (legibilidade). O código não é intrinsecamente complexo - uma opção, dependendo de um campo. Então, todo caso tem um bloco que analisa x elementos de dados e os publica. Sem pesadelo de manutenção. Existe uma condição "if" repetitiva que determina se um campo possui dados (pField = pFields [x], se pField-> IsSet () {blabla}) - o mesmo para todos os campos.

Substitua isso por uma rotina muito menor, contendo loop aninhado e muitas instruções de comutação reais, e um método enorme pode ser mais fácil de manter do que um menor.

Então, desculpe, o LOC não é uma boa medida para começar. Se for o caso, devem ser usados ​​pontos de complexidade / decisão.

TomTom
fonte
11
Os LOC são uma ferramenta excelente para a área em que fornecem uma medida relevante - projetos muito grandes, nos quais podem ser usados ​​para ajudar a fornecer estimativas de quanto tempo um projeto semelhante pode levar para ser concluído. Além disso, as pessoas tendem a se preocupar muito com elas.
rjzii
Direito. Não é como se o LOC não dependesse, por vezes, de quão expressivo eu escrevo o código, de requisitos de formatação etc. O LOC é totalmente inadequado e algo que o MBA sem qualquer experiência usa. Somente. Você é livre para se colocar na lista de pessoas que não entendem por que o LOC é uma medida ruim, mas claramente isso não fará com que você pareça alguém para ouvir.
TomTom
Por favor, reveja o que eu disse novamente, observei que o LOC é uma boa ferramenta apenas para uma área de medição e uso (ou seja, projetos extremamente grandes, onde eles podem ser usados ​​para estimativa). Qualquer coisa menor do que a grande escala e eles usam mais, se não todos, o valor de algo além de sons rápidos nas reuniões para manter as pessoas felizes. É como tentar usar anos-luz para medir o quão justa é a cafeteria do escritório, com certeza você pode fazê-lo, mas a medição é inútil. Mas quando você precisa discutir distâncias entre estrelas, elas funcionam muito bem.
Rjzii
2
+1 em uma função deve ter todo o código necessário para executar a função. Ele deve fazer uma coisa e apenas uma coisa - mas se isso exigir 1000 linhas de código, que assim seja.
James Anderson
Escrevi manipuladores para dados de soquete recebidos e, sim, eles podem exigir um mil ou mais LOC. No entanto, posso contar com uma mão o número de vezes que precisei fazer isso e não posso contar o número de vezes que não era a maneira apropriada de codificar.
7

Vou apenas colocar mais uma citação.

Os programas devem ser escritos para as pessoas lerem e, incidentalmente, para as máquinas executarem

- Harold Abelson

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

Pete
fonte
11
Exceto quando eles contêm um interruptor.
Calmarius
11
ou construir um objeto com base nos resultados de uma consulta de banco de dados que retorna dezenas de campos por linha no conjunto de resultados ...
jwenting
Os resultados do banco de dados são certamente uma exceção aceitável - além de serem geralmente declarações "idiotas" que preenchem alguma instância de uma classe (ou qualquer outra coisa), em vez de lógica do que precisam ser seguidas.
MetalMikester 19/08/14
6

Estou nessa raquete louca, de um jeito ou de outro, desde 1970.

Durante todo esse tempo, com duas exceções que abordarei em breve, NUNCA vi uma "rotina" bem projetada (método, procedimento, função, sub-rotina, qualquer que seja) que PRECISAVA ser mais de uma página impressa ( cerca de 60 linhas). A grande maioria deles era bastante curta, da ordem de 10 a 20 linhas.

No entanto, eu vi MUITO código de "fluxo de consciência", escrito por pessoas que aparentemente nunca ouviram falar em modularização.

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

O outro era a rotina de torpedos de fótons do jogo STARTRK Matuszek-Reynolds-McGehearty-Cohen, escrito no CDC 6600 FORTRAN IV. Ele teve que analisar a linha de comando e simular o vôo de cada torpedo, com perturbações, verificar a interação entre o torpedo e cada tipo de coisa que ele poderia atingir, e, sim, 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.

John R. Strohm
fonte
2
+1 na vibração "saia do meu gramado" recebo desta resposta. Além disso, a partir da experiência pessoal anterior às línguas OOP foram difundidas.
Não é tanto "sair do meu gramado" como uma observação de que eu vi MUITO código de porcaria ao longo dos anos, e parece estar ficando pior.
John R. Strohm
Meu chefe tem o hábito de escrever métodos com várias centenas de linhas, geralmente com vários níveis de ifs aninhados. Ele também usa classes parciais (.NET) para "dividir" uma classe em vários arquivos, para que ele possa afirmar que está mantendo-os curtos. Essas são apenas duas coisas com as quais tenho que lidar. Faz isso há 25 anos e posso confirmar que as coisas estão piorando. E agora é hora de voltar para essa bagunça.
MetalMikester 19/08/2014
5

Se eu encontrar um método longo, posso apostar que esse método não é devidamente testado em unidade ou, na maioria das vezes, não possui teste de unidade. Se você começar a fazer TDD, nunca criará métodos de 100 linhas com 25 responsabilidades diferentes e 5 loops aninhados. Os testes obrigam você a refatorar constantemente sua bagunça e escrever o código limpo do tio Bob.

Sergii Shevchyk
fonte
2

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

  1. O objetivo principal da função é encontrar o valor de retorno. Não há outra razão 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. A chamada de outras funções só deve ser feita 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 funções grandes - uma das duas acontecerá assim que você começar a ter código suficiente para fazer algo significativo. Grandes programas podem ter ambos.
tp1
fonte
11
E os efeitos colaterais - gravar em um arquivo, redefinir o status etc.?
Vorac
2

Os autores entendem o mesmo 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 retorna (e cuja chamada se torna uma única instrução). Essa não é uma distinção comum em todo o SE no mundo real, mas eu a vi nos textos dos usuários.

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

O único ponto que eu acrescentaria é que toda a afirmação "operações longas não é mais propensa a erros do que operações curtas" não é estritamente verdadeira. Além do fato de que mais código equivale a mais espaço potencial de erro, é óbvio que a divisão do código em segmentos tornará os erros mais fáceis de evitar e mais fáceis de localizar. Caso contrário, não haveria razão para dividir o código em pedaços, exceto a repetição. Mas isso talvez seja verdade apenas se os referidos segmentos forem documentados suficientemente bem para que você possa determinar os resultados de uma chamada de operação sem ler ou rastrear o código real (projeto por contrato com base em especificações, em vez de dependência concreta entre áreas do código).

Além disso, se você deseja que operações mais longas funcionem bem, convém adotar convenções de código mais rígidas para apoiá-las. Jogar uma declaraçã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 obviamente condicional, em uma rápida leitura (apenas por um exemplo).

Então, eu pensaria que qual estilo é menos provável de ser um pesadelo cheio de erros dependeria em grande parte das convenções que você adere ao restante do seu código. :)

Trixie Wolf
fonte
1

IMHO, você não precisa usar a barra de rolagem para ler sua função. Assim que você precisar mover a barra de rolagem, levará mais tempo para entender como funciona a função.

Portanto, depende do ambiente de programação usual do trabalho em equipe (resolução da tela, editor, tamanho da fonte, etc ...). Nos anos 80, eram 25 linhas e 80 colunas. Agora, no meu editor, mostro quase 50 linhas. O número de colunas que exibi não foi alterado desde que dividi minha tela em duas para exibir dois arquivos por vez.

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

Jérôme Pouiller
fonte
2
Não eram 24 linhas dessa vez? Estou pensando em terminais 3270 ou 9750, onde o dia 25 era a linha de status. E as emulações de terminal seguiram isso.
ott--
alguns sistemas / editores tinham 40 ou 50 linhas desde o início. Atualmente, 150 linhas não são incomuns e mais de 200 são executáveis, portanto, essa não é realmente uma boa métrica.
Moz
Uso minha tela na orientação retrato, consigo ver 200 linhas de código de uma só vez.
Calmarius
e se eu não usar qualquer quebra de linha para quebrar minhas linhas I pode codificar um método 5000 linha em uma única linha ...
jwenting
1

Acho que a resposta da TomTom chegou perto de como eu me sinto sobre isso.

Mais e mais eu me pego mais na complexidade ciclomática do que nas linhas.

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

Às vezes me pego colocando ifs de uma linha em casos de troca, porque, por algum motivo, esses tendem a ser casos em que a divisão é mais difícil do que ajuda.

Observe que eu não conto a lógica de proteção contra esse limite.

Loren Pechtel
fonte
Foi demonstrado que a complexidade ciclomática, em grandes quantidades de código de produção real, está MUITO fortemente correlacionada com o SLOC bruto, tornando o cálculo da complexidade ciclomática um total desperdício de tempo, energia e ciclos de relógio.
John R. Strohm
@ JohnR.Strohm eu estou falando por método, não no geral. Claro, no cenário geral, ele é altamente correlacionado - a questão é como dividir esse código em métodos. 10 métodos de 100 linhas ou 100 métodos de 10 linhas ainda terão o mesmo SLOC e complexidade gerais, mas o primeiro será muito mais difícil de trabalhar.
Loren Pechtel
eu também. O estudo de correlação analisou MUITOS códigos e MUITAS rotinas. (Foi um dos grandes repositórios públicos.)
John R. Strohm
-3

No OOP, todas as coisas são objetadas 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, 2-3 linhas). Um benefício do método pequeno (unidade pequena, por exemplo, método ou função) são:

  1. melhor legível
  2. manter melhor
  3. bug corrigido melhor
  4. muda melhor
Sam
fonte