Quando uma função é muito longa? [fechadas]

130

35 linhas, 55 linhas, 100 linhas, 300 linhas? Quando você deve começar a separá-lo? Estou perguntando porque tenho uma função com 60 linhas (incluindo comentários) e estava pensando em desmembrá-la.

long_function(){ ... }

para dentro:

small_function_1(){...}
small_function_2(){...}
small_function_3(){...}

As funções não serão usadas fora da função long_, tornando funções menores significa mais chamadas de função etc.

Quando você dividiria uma função em outras menores? Por quê?

  1. Os métodos devem fazer apenas uma coisa lógica (pense na funcionalidade)
  2. Você deve ser capaz de explicar o método em uma única frase
  3. Deve caber na altura do seu monitor
  4. Evite despesas desnecessárias (comentários que apontam para o óbvio ...)
  5. O teste de unidade é mais fácil para pequenas funções lógicas
  6. Verifique se parte da função pode ser reutilizada por outras classes ou métodos
  7. Evite acoplamentos inter-classes excessivos
  8. Evite estruturas de controle profundamente aninhadas

Obrigado a todos pelas respostas , edite a lista e vote na resposta correta; eu vou escolher essa;)

Estou refatorando agora com essas idéias em mente :)

user58163
fonte
Há um erro de digitação na sua pergunta, acho que você quis dizer "Quando uma função é longa demais?".
Tom
1
Você está confundindo a pergunta colocando-a em termos de linhas de código. Os fatores determinantes não são medidos em linhas de código.
dkretz
essa pergunta pode se tornar complicada dependendo do código e do idioma. talvez você possa publicá-lo.
Ray Tayek
Se for cumprido o princípio de responsabilidade única - faça-o. Geralmente, sinto a necessidade de criar um cabeçalho ou para cada 20 linhas de código, o que está me sinalizando para abstraí-lo e nomear esse fragmento como uma função com nome significativo, em vez de criar um cabeçalho de capítulo.
Yevgeniy Afanasyev

Respostas:

75

Não há regras reais e rígidas para isso. Geralmente, gosto dos meus métodos para "fazer uma coisa". Portanto, se estiver capturando dados, fazendo alguma coisa com esses dados e depois gravando-os em disco, eu dividiria a captura e a gravação em métodos separados, para que o meu método "principal" contenha apenas "fazer alguma coisa".

Como "fazer alguma coisa" ainda pode ter algumas linhas, por isso não tenho certeza de que várias linhas sejam a métrica certa para usar :)

Edit: Esta é uma única linha de código que enviei pelo correio na semana passada (para provar um ponto ... não é algo que eu tenha o hábito :)) - eu certamente não iria querer de 50 a 60 desses bandidos no meu método : D

return level4 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3) && (r.Level4 == (int)level4)).ToList() : level3 != null ? GetResources().Where(r => (r.Level2 == (int)level2) && (r.Level3 == (int)level3)).ToList() : level2 != null ? GetResources().Where(r => (r.Level2 == (int)level2)).ToList() : GetAllResourceList();
Steven Robbins
fonte
1
LOL Bem, eu poderia remover todo o espaço em branco no meu método e seria apenas uma linha muito longa e não uma função longa. Fazer uma coisa, que, provavelmente, é a resposta que, graças
@Movaxes Esse trecho de código que eu publiquei é uma única declaração, não apenas muitas linhas em uma linha .. não há ponto e vírgula lá :) Eu poderia ter expandido GetResources () a cada vez para torná-lo ainda mais ruim: P
Steven Robbins
Sim, isso faz sentido. Por que não pegar seu arquivo de origem inteiro e colocá-lo em uma linha? Quero dizer, então você realmente chegar a ser um Web 2.0 "ninja" :)
BobbyShaftoe
Lembro-me de revistas antigas (eu estou falando da BBC Micro) que eles costumavam ter "programas de 10 linhas" que tinham várias declarações em cada linha, até o comprimento máximo que a BBC podia suportar .. eles sempre eram uma dor certa para digitar: D
Steven Robbins
6
Eu gosto do conceito da função fazendo apenas uma coisa, .... mas. Se você tem uma função que faz 10 coisas e move 9 delas para funções separadas, que ainda são chamadas pela função remanescente, não é essa função remanescente ainda fazendo 10 coisas! Eu acho que dividir a função dessa maneira facilita muito o teste.
mtnpaul
214

Aqui está uma lista de bandeiras vermelhas (em nenhuma ordem específica) que podem indicar que uma função é muito longa:

  1. Estruturas de controle profundamente aninhadas : por exemplo, for-loops com 3 níveis de profundidade ou até apenas 2 níveis de profundidade com instruções if aninhadas que possuem condições complexas.

  2. Muitos parâmetros de definição de estado : Por parâmetro de definição de estado , quero dizer um parâmetro de função que garante um caminho de execução específico através da função. Obtenha muitos desses tipos de parâmetros e você terá uma explosão combinatória dos caminhos de execução (isso geralmente acontece em conjunto com o nº 1).

  3. Lógica duplicada em outros métodos : a má reutilização de código é um grande contribuinte para o código processual monolítico. Muitas dessas duplicações lógicas podem ser muito sutis, mas uma vez re-fatoradas, o resultado final pode ser um design muito mais elegante.

  4. Acoplamento inter-classe excessivo : essa falta de encapsulamento adequado resulta em funções preocupadas com características íntimas de outras classes, prolongando-as.

  5. Sobrecarga desnecessária : comentários que apontam classes óbvias e profundamente aninhadas, getters e setters supérfluos para variáveis ​​de classe aninhadas privadas e nomes de funções / variáveis ​​invulgarmente longos podem criar ruído sintático nas funções relacionadas que, em última análise, aumentarão seu tamanho.

  6. Sua enorme tela de nível de desenvolvedor não é grande o suficiente para exibi-la : na verdade, as telas de hoje são grandes o suficiente para que uma função que esteja próxima da sua altura seja provavelmente muito longa. Mas, se for maior , é uma arma de fumo que algo está errado.

  7. Você não pode determinar imediatamente propósito da função : Além disso, uma vez que você realmente fazer determinar a sua finalidade, se você não pode resumir este fim em uma única frase ou acontecer de ter uma tremenda dor de cabeça, esta deve ser uma pista.

Em conclusão, as funções monolíticas podem ter conseqüências de longo alcance e geralmente são um sintoma de grandes deficiências de design. Sempre que encontro um código que é uma alegria absoluta de ler, sua elegância é imediatamente aparente. E adivinhe: as funções geralmente são muito curtas.

Ryan Delucchi
fonte
1
Bela postagem! Muito determinista
Chuck Conway
2
@PedroMorteRolo Exatamente. A API padrão nem sempre é um modelo de elegância. Além disso, grande parte da API Java foi desenvolvida com um conhecimento íntimo do Java Compiler e JVM, portanto, você tem considerações de desempenho que podem explicá-lo. Concordo que seções críticas de código que não podem desperdiçar um único milissegundo podem ter que quebrar algumas dessas regras, mas isso sempre deve ser considerado um caso especial. Passar um tempo extra de desenvolvimento antecipadamente é um investimento inicial que pode evitar uma dívida tecnológica futura (potencialmente incapacitante).
Ryan Delucchi
2
A propósito ... Sou da opinião de que os métodos longos são ruins em heurística também se aplicam às classes. IMHO, as classes longas são ruins, porque tendem a violar o princípio da responsabilidade única. Seria divertido ter compiladores emitindo avisos para ambas as classes de comprimento e métodos ....
Pedro Rolo
3
@PedroMorteRolo Eu definitivamente concordo com este. Além disso, é provável que grandes classes tenham mais estado mutável: o que leva a códigos que são muito difíceis de manter.
Ryan Delucchi
1
Melhor resposta. Outra boa pista é: como são os comentários no código? O número de vezes que eu tropecei através de código de alguém com uma linha como: // fetch Foo's credentials where Bar is "uncomplete". Esse é quase certamente um nome de função e deve ser dissociado. Provavelmente quer ser refatorado para algo como: Foo.fetchCredentialWhereBarUncomplete()
Jay Edwards
28

Acho que há uma grande ressalva no mantra "faça apenas uma coisa" nesta página. Às vezes, fazer uma coisa faz malabarismos com muitas variáveis. Não divida uma função longa em várias funções menores se as funções menores tiverem longas listas de parâmetros. Isso apenas transforma uma única função em um conjunto de funções altamente acopladas, sem nenhum valor individual real.

jmucchiello
fonte
18

Uma função deve fazer apenas uma coisa. Se você estiver fazendo muitas coisas pequenas em uma função, torne cada uma delas uma função e chame essas funções da função longa.

O que você realmente não quer fazer é copiar e colar a cada 10 linhas da sua função longa em funções curtas (como sugere o seu exemplo).

Jeremy Ruten
fonte
Sim fazer um monte de pequenas funções com o copiar e colar padrão não é uma grande idéia, eu concordo uma função deve tentar sempre fazer apenas uma coisa
"fazer uma coisa" pode ou não estar correto, dependendo da granularidade. Se uma função multiplica uma matriz, tudo bem. Se uma função cria um carro virtual - isso é "uma coisa", mas também é uma coisa muito grande. Várias funções podem ser usadas para construir um carro, componente por componente.
precisa saber é o seguinte
16

Concordo que uma função deve fazer apenas uma coisa, mas em que nível é essa coisa.

Se suas 60 linhas estão realizando uma coisa (da perspectiva de seus programas) e as peças que compõem essas 60 linhas não serão usadas por mais nada, então 60 linhas é bom.

Não há nenhum benefício real em desmembrá-lo, a menos que você possa desmembrá-lo em pedaços de concreto que são autônomos. A métrica a ser usada é a funcionalidade e não as linhas de código.

Trabalhei em muitos programas em que os autores levaram a única coisa a um nível extremo e tudo o que acabou fazendo foi fazer parecer que alguém levou uma granada para uma função / método e a explodiu em dezenas de peças não conectadas que são difícil de seguir.

Ao retirar partes dessa função, você também precisa considerar se estará adicionando uma sobrecarga desnecessária e evite transmitir grandes quantidades de dados.

Acredito que o ponto principal é procurar reutilização nessa função longa e retirar essas peças. O que resta é a função, seja ela de 10, 20 ou 60 linhas.

bruceatk
fonte
2
+1 "A métrica para uso é de funcionalidade e não linhas de código"
Cody Piersall
Outra métrica importante é o número de níveis de aninhamento de blocos. Mantenha o mínimo. Quebrar uma função em partes menores geralmente ajuda. Outras coisas também podem ajudar, como retornos múltiplos.
user2367418
10

60 linhas é grande, mas não muito longa para uma função. Se ele se encaixa em uma tela de um editor, você pode ver tudo de uma vez. Realmente depende do que as funções estão fazendo.

Por que eu posso dividir uma função:

  • É muito longe
  • Torna o código mais sustentável, dividindo-o e usando nomes significativos para a nova função
  • A função não é coesa
  • Partes da função são úteis em si mesmas.
  • Quando é difícil criar um nome significativo para a função (provavelmente está fazendo muito)
dajorad
fonte
3
Bons pontos, concordo, também, se você precisar nomear a função DoThisAndThisAndAlsoThis, provavelmente faz muito. Obrigado :)
2
Você está muito fora de ordem com este companheiro. 60 linhas sempre serão demais. Eu diria que se você estiver se aproximando de 10 linhas, provavelmente estará próximo do limite.
willcodejavaforfood
Mas uma outra função ainda está chamando essas funções e é essencialmente a mesma DoThisAndThisAndAlsoThisfunção, mas com um monte de abstração que você ainda tem que nomear alguma forma
Timo Huovinen
6

Minha heurística pessoal é que é muito longo se eu não conseguir ver a coisa toda sem rolar.

Ferruccio
fonte
4
... enquanto definimos o tamanho da fonte para 5?
EricSchaefer
5

Tamanho aproximado do tamanho da tela (então pegue um widescreen dinâmico grande e gire-o) ... :-)

Brincadeira à parte, uma coisa lógica por função.

E o positivo é que o teste de unidade é realmente muito mais fácil de fazer com pequenas funções lógicas que fazem uma coisa. Grandes funções que fazem muitas coisas são mais difíceis de verificar!

/ Johan

Johan
fonte
Bom ponto sobre o teste de unidade :)
5

Regra geral: se uma função contiver blocos de código que fazem algo, que é um pouco separado do restante do código, coloque-o em uma função separada. Exemplo:

function build_address_list_for_zip($zip) {

    $query = "SELECT * FROM ADDRESS WHERE zip = $zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }

    // now create a nice looking list of
    // addresses for the user
    return $html_content;
}

muito melhor:

function fetch_addresses_for_zip($zip) {
    $query = "SELECT * FROM ADDRESS WHERE zip = $zip";
    $results = perform_query($query);
    $addresses = array();
    while ($address = fetch_query_result($results)) {
        $addresses[] = $address;
    }
    return $addresses;
}

function build_address_list_for_zip($zip) {

    $addresses = fetch_addresses_for_zip($zip);

    // now create a nice looking list of
    // addresses for the user
    return $html_content;
}

Essa abordagem tem duas vantagens:

  1. Sempre que você precisar buscar endereços para um determinado código postal, poderá usar a função prontamente disponível.

  2. Quando você precisar ler a função build_address_list_for_zip()novamente, você sabe o que o primeiro bloco de código fará (ele busca endereços para um determinado código postal, pelo menos é o que você pode derivar do nome da função). Se você tivesse deixado o código de consulta em linha, primeiro seria necessário analisá-lo.

[Por outro lado (negarei ter dito isso a você, mesmo sob tortura): se você ler muito sobre otimização de PHP, poderá ter a idéia de manter o número de funções o menor possível, porque a chamada de função é muito, muito caro em PHP. Não sei disso, pois nunca fiz benchmarks. Se for esse o caso, você provavelmente não seguirá nenhuma das respostas da sua pergunta se o aplicativo for muito "sensível ao desempenho" ;-)]

EricSchaefer
fonte
exemplo agradável obrigado :) Eu vou google para benchmarks função em PHP
5

Dê uma olhada na ciclomática de McCabe, na qual ele divide seu código em um gráfico onde: "Cada nó no gráfico corresponde a um bloco de código no programa em que o fluxo é seqüencial e os arcos correspondem às ramificações obtidas no programa. "

Agora imagine que seu código não possui funções / métodos; é apenas uma enorme expansão de código na forma de um gráfico.

Você deseja dividir essa expansão em métodos. Considere que, quando o fizer, haverá um certo número de blocos em cada método. Apenas um bloco de cada método estará visível para todos os outros métodos: o primeiro bloco (presumimos que você poderá pular para um método em apenas um ponto: o primeiro bloco). Todos os outros blocos em cada método serão informações ocultas nesse método, mas cada bloco dentro de um método pode potencialmente pular para qualquer outro bloco dentro desse método.

Para determinar qual o tamanho dos seus métodos em termos de número de blocos por método, uma pergunta que você deve fazer é: quantos métodos eu devo ter para minimizar o número potencial máximo de dependências (MPE) entre todos os blocos?

Essa resposta é dada por uma equação. Se r é o número de métodos que minimiza o MPE do sistema en é o número de blocos no sistema, a equação é: r = sqrt (n)

E pode ser mostrado que isso fornece o número de blocos por método como sendo também sqrt (n).

Ed Kirwan
fonte
4

Lembre-se de que você pode refazer o fatorial apenas por causa do fatorial, potencialmente tornando o código mais ilegível do que era em primeiro lugar.

Um ex-colega meu tinha uma regra bizarra de que uma função / método deve conter apenas 4 linhas de código! Ele tentou se ater a isso com tanta rigidez que seus nomes de métodos frequentemente se tornaram repetitivos e sem sentido, além de as chamadas se tornarem profundamente aninhadas e confusas.

Então, meu próprio mantra se tornou: se você não consegue pensar em um nome de função / método decente para o pedaço de código que está refatorando, não se preocupe.

Saltmeister
fonte
2

A principal razão pela qual costumo interromper uma função é porque pedaços dela também são ingredientes de outra função próxima que estou escrevendo, para que as partes comuns sejam fatoradas. Além disso, se estiver usando muitos campos ou propriedades de alguma outra classe, há uma boa chance de que o pedaço relevante possa ser retirado por atacado e, se possível, movido para a outra classe.

Se você tiver um bloco de código com um comentário na parte superior, considere puxá-lo para uma função, com os nomes das funções e dos argumentos ilustrando sua finalidade e reservando o comentário para a lógica do código.

Tem certeza de que não há peças que seriam úteis em outros lugares? Que tipo de função é essa?

Jeffrey Hantin
fonte
A função cria um arquivo de cache a partir de um modelo, com base no URL, como post_2009_01_01.html do URL / post /
2

Na minha opinião, a resposta é: quando faz muitas coisas. Sua função deve executar apenas as ações que você espera do nome da própria função. Outra coisa a considerar é se você deseja reutilizar algumas partes de suas funções em outras; nesse caso, pode ser útil dividi-lo.

Pierpaolo
fonte
2

Eu costumo dividir funções pela necessidade de colocar comentários descrevendo o próximo bloco de código. O que anteriormente entrou nos comentários agora passa para o novo nome da função. Esta não é uma regra difícil, mas (para mim) uma boa regra de ouro. Eu gosto de falar por código melhor do que aquele que precisa de comentários (como eu aprendi que os comentários geralmente mentem)

Olaf Kock
fonte
Eu gosto de comentar meu código, principalmente não para mim, mas para outros, que elimina muitas perguntas sobre onde a variável $ foi definida, mas também gosto que o código seja auto-explicativo. Os comentários mentem?
sim, porque muitas vezes eles não são mantidos. No momento da redação, eles podem estar corretos, mas depois que uma correção de bug ou novo recurso é introduzido, ninguém força os comentários a serem alterados de acordo com a nova situação. Nomes de métodos tendem a mentira muito menos frequentemente do que comentários IMHO
Olaf Kock
Acabei de encontrar esta resposta: stackoverflow.com/questions/406760/… afirmando que "a maioria dos comentários no código é de fato uma forma perniciosa de duplicação de código". Também - Longa linha de comentários lá.
Olaf Kock
1

Isso é parcialmente uma questão de gosto, mas como eu determino isso é que eu tento manter minhas funções aproximadamente apenas enquanto caber na tela ao mesmo tempo (no máximo). A razão é que é mais fácil entender o que está acontecendo, se você pode ver tudo ao mesmo tempo.

Quando eu codifico, é uma mistura de escrever funções longas e refatorar para extrair bits que poderiam ser reutilizados por outras funções - e escrever pequenas funções que executam tarefas discretas à medida que eu passo.

Não sei se há uma resposta certa ou errada para isso (por exemplo, você pode escolher 67 linhas como o máximo, mas pode haver momentos em que faz sentido adicionar mais algumas).

Andrew Hedges
fonte
Bem, eu também gosto de ver minha função completa na tela :) às vezes isso significa uma fonte Monospace 9 e uma grande resolução em fundo preto, eu concordo que é mais fácil entender dessa maneira.
1

Houve uma pesquisa minuciosa feita sobre esse mesmo tópico; se você deseja o menor número de erros, seu código não deve ser muito longo. Mas também não deve ser muito curto.

Eu discordo que um método deve caber em sua exibição em um, mas se você estiver rolando a página para baixo em mais de uma página, o método será muito longo.

Consulte O tamanho ideal da classe para software orientado a objetos para uma discussão mais aprofundada.

Ian Hickman
fonte
Obrigado pelo link, lendo :)
1

Eu escrevi 500 funções de linha antes, no entanto, essas eram apenas grandes instruções de chave para decodificar e responder a mensagens. Quando o código de uma única mensagem ficou mais complexo que um único if-then-else, eu o extraí.

Em essência, embora a função fosse de 500 linhas, as regiões mantidas independentemente tiveram em média 5 linhas.

Joshua
fonte
1

Normalmente, eu uso uma abordagem orientada a testes para escrever código. Nessa abordagem, o tamanho da função geralmente está relacionado à granularidade de seus testes.

Se o seu teste estiver suficientemente focado, ele o levará a escrever uma pequena função focada para fazer o teste passar.

Isso também funciona na outra direção. As funções precisam ser pequenas o suficiente para testar efetivamente. Portanto, ao trabalhar com código legado, geralmente descubro funções maiores para testar as diferentes partes delas.

Geralmente me pergunto "qual é a responsabilidade dessa função" e se não posso declarar a responsabilidade em uma sentença clara e concisa e depois a traduzo em um pequeno teste focado, me pergunto se a função é muito grande.

lexicalscope
fonte
1

Se tiver mais de três ramificações, geralmente isso significa que uma função ou método deve ser desmembrado, para encapsular a lógica de ramificação em diferentes métodos.

Cada instrução for loop, if, etc não é vista como uma ramificação no método de chamada.

O Cobertura para código Java (e tenho certeza de que existem outras ferramentas para outras linguagens) calcula o número de if, etc. em uma função para cada função e o soma para a "complexidade ciclomática média".

Se uma função / método tiver apenas três ramificações, obterá três nessa métrica, o que é muito bom.

Às vezes, é difícil seguir esta diretriz, nomeadamente para validar a entrada do usuário. No entanto, a inserção de ramificações em diferentes métodos ajuda não apenas ao desenvolvimento e manutenção, mas também ao teste, já que as entradas dos métodos que executam a ramificação podem ser analisadas facilmente para ver quais entradas precisam ser adicionadas aos casos de teste para cobrir as ramificações que não foram cobertos.

Se todas as ramificações estivessem dentro de um único método, as entradas teriam que ser rastreadas desde o início do método, o que dificulta a testabilidade.

Martin54
fonte
0

Eu suspeito que você encontrará muitas respostas sobre isso.

Provavelmente, eu o dividiria com base nas tarefas lógicas que estavam sendo executadas dentro da função. Se você achar que seu conto está se transformando em romance, sugiro que encontre e extraia etapas distintas.

Por exemplo, se você tem uma função que lida com algum tipo de entrada de string e retorna um resultado de string, você pode dividir a função com base na lógica para dividir sua string em partes, a lógica para adicionar caracteres extras e a lógica para colocá-la todos juntos novamente como um resultado formatado.

Em resumo, o que torna seu código limpo e fácil de ler (seja simplesmente garantindo que sua função tenha bons comentários ou desmembrando) é a melhor abordagem.

Phil.Wheeler
fonte
0

supondo que você esteja fazendo uma coisa, o comprimento dependerá:

  • o que você está fazendo
  • qual idioma você está usando
  • com quantos níveis de abstração você precisa lidar no código

60 linhas podem ser muito longas ou podem estar certas. Eu suspeito que pode demorar demais.

Ray Tayek
fonte
Eu estou fazendo alguns cache em PHP, sim, provavelmente, 60 linhas é demais, refatoração ...
0

Uma coisa (e essa coisa deve ser óbvia no nome da função), mas não mais do que uma tela cheia de código, independentemente. E fique à vontade para aumentar o tamanho da fonte. E em caso de dúvida, refatorá-lo em duas ou mais funções.

gkrogers
fonte
0

Estendendo o espírito de um tweet do tio Bob há um tempo, você sabe que uma função está ficando muito longa quando você sente a necessidade de colocar uma linha em branco entre duas linhas de código. A idéia é que, se você precisar de uma linha em branco para separar o código, sua responsabilidade e escopo estarão separados nesse ponto.

Mark Bostleman
fonte
0

Minha idéia é que, se eu tiver que me perguntar se é muito longo, provavelmente é muito longo. Ajuda a criar funções menores nessa área, pois pode ajudar mais tarde no ciclo de vida do aplicativo.

sokket
fonte