Existe algo como ter muitas funções / métodos privados?

63

Entendo a importância do código bem documentado. Mas também entendo a importância do código de auto-documentação . Quanto mais fácil é ler visualmente uma função específica, mais rápido podemos seguir em frente durante a manutenção do software.

Com isso dito, eu gosto de separar grandes funções em outras menores. Mas faço isso a um ponto em que uma classe pode ter mais de cinco delas apenas para servir a um método público. Agora multiplique cinco métodos privados por cinco públicos, e você terá cerca de vinte e cinco métodos ocultos que provavelmente serão chamados apenas uma vez por esses públicos.

Claro, agora é mais fácil ler esses métodos públicos, mas não posso deixar de pensar que ter muitas funções é uma prática ruim.

[Editar]

As pessoas me perguntam por que acho que ter muitas funções é uma prática ruim.

A resposta simples: é um pressentimento.

Minha crença não é, nem um pouco, apoiada por horas de experiência em engenharia de software. É apenas uma incerteza que me deu um "bloqueio de escritor", mas para um programador.

No passado, eu apenas programava projetos pessoais. Recentemente, mudei para projetos baseados em equipes. Agora, quero garantir que outras pessoas possam ler e entender meu código.

Eu não tinha certeza do que melhoraria a legibilidade. Por um lado, eu estava pensando em separar uma grande função em outras menores com nomes inteligíveis. Mas havia um outro lado de mim dizendo que era apenas redundante.

Então, estou pedindo que isso me ilumine para escolher o caminho correto.

[Editar]

Abaixo, incluí duas versões de como eu poderia resolver o meu problema. O primeiro resolve isso não separando grandes partes do código. O segundo faz coisas separadas.

Primeira versão:

public static int Main()
{
    // Displays the menu.
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

Segunda versão:

public static int Main()
{
    DisplayMenu();
}

private static void DisplayMenu()
{
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

Nos exemplos acima, o último chama uma função que será usada apenas uma vez durante todo o tempo de execução do programa.

Nota: o código acima é generalizado, mas é da mesma natureza que o meu problema.

Agora, aqui está a minha pergunta: qual? Eu escolho o primeiro ou o segundo?

Sal
fonte
1
"ter muitas funções é uma má prática."? Por quê? Atualize sua pergunta para explicar por que isso parece ruim para você.
31511 S.Lott
Eu recomendo ler a noção de 'coesão de classe' - Bob Martin discute isso em 'Código Limpo'.
JᴀʏMᴇᴇ
Adicionando outras respostas, divisão e nome, mas todos devemos ter em mente que, à medida que a classe cresce, suas variáveis-membro se tornam cada vez mais semelhantes às variáveis ​​globais. Uma maneira de melhorar o problema (além de soluções melhores, como refatoração ;-)) é separar esses pequenos métodos privados em funções independentes, se isso for possível (na medida em que eles não precisam de acesso a muitos estados). Alguns idiomas permitem funções fora da classe, outros têm classes estáticas. Dessa forma, você pode entender essa função isoladamente.
Pablo H
Se alguém puder me dizer por que "esta resposta não é útil", ficaria grato. Sempre se pode aprender. :-)
Pablo H
@PabloH A resposta que você forneceu fornece uma boa observação e insights ao OP, no entanto, na verdade, não responde à pergunta que foi feita. Eu mudei para o campo de comentários.
maple_shaft

Respostas:

36

Agora multiplique cinco métodos privados por cinco públicos, e você terá cerca de vinte e cinco métodos ocultos que provavelmente serão chamados apenas uma vez por esses públicos.

Isso é conhecido como Encapsulamento , que cria uma Abstração de Controle no nível superior. Isto é uma coisa boa.

Isto significa que quem lê o seu código, quando chegar ao startTheEngine()método em seu código, pode ignorar todos os detalhes de nível inferior, como openIgnitionModule(), turnDistributorMotor(), sendSparksToSparkPlugs(), injectFuelIntoCylinders(), activateStarterSolenoid(), e todos os outros complexos, pequenos, funções que devem ser executados, a fim de facilitar a função muito maior e mais abstrata de startTheEngine().

A menos que o problema com o qual você esteja lidando no código lide diretamente com um desses componentes, os mantenedores de código podem seguir em frente, ignorando a funcionalidade encapsulada na caixa de areia.

Isso também tem a vantagem de tornar seu código mais fácil de testar. . Por exemplo, eu posso escrever um caso de turnAlternatorMotor(int revolutionRate)teste e testar sua funcionalidade completamente independente dos outros sistemas. Se houver um problema com essa função e a saída não for o que eu espero, então eu sei qual é o problema.

Código que não é dividido em componentes é muito mais difícil de testar. De repente, seus mantenedores estão apenas olhando para a abstração em vez de poderem se aprofundar em componentes mensuráveis.

Meu conselho é continuar fazendo o que você está fazendo, pois seu código será dimensionado, será fácil de manter e poderá ser usado e atualizado nos próximos anos.

jmort253
fonte
3
Então você acredita que é bom 'encapsulamento' disponibilizar a lógica para uma classe inteira que é chamada apenas em um só lugar?
Steven Jeuris
9
@ Steven Jeuris: Contanto que essas funções sejam tornadas privadas, não vejo problema algum; de fato, acho que, como afirmado nesta resposta, é mais fácil de ler. É muito mais fácil entender uma função pública de alto nível que apenas chama uma série de funções privadas de baixo nível (que foram nomeadas apropriadamente, é claro). Claro, todo esse código poderia ter sido colocado na função de alto nível em si, mas levaria mais tempo para entender o código, pois é necessário analisar muito mais no mesmo local.
gablin
2
@ gablin: funções privadas ainda são acessíveis a partir de toda a classe. No presente artigo (aparentemente controverso) discuto como 'legibilidade global' é perdida quando ter muito funções. Na verdade, um princípio comum além das funções deve fazer apenas uma coisa: você não deve ter muitas. Você só obtém 'legibilidade local' dividindo apenas por falta de tempo, o que também pode ser alcançado com comentários simples e 'parágrafos de código'. Ok, o código deve ser auto-documentado, mas os comentários não são obsoletos!
Steven Jeuris
1
@Steven - O que é mais fácil de entender? myString.replaceAll(pattern, originalData);ou eu colando o método replaceAll inteiro no meu código, incluindo a matriz de caracteres de backup que representa o objeto de string subjacente toda vez que preciso usar a funcionalidade de um String?
jmort253
7
@ Steven Jeuris: Você tem razão. Se uma classe tem muitas funções (pública ou privada), talvez a classe inteira esteja tentando fazer demais. Nesses casos, pode ser melhor dividi-lo em várias classes menores, assim como você dividiria uma função muito grande em várias funções menores.
gablin
22

Sim e não. O princípio fundamental é: um método deve fazer uma coisa e apenas uma coisa

É correto "dividir" seu método em métodos menores. Por outro lado, esses métodos devem ser idealmente gerais o suficiente, não apenas para servir esse método "grande", mas também para serem reutilizáveis ​​em outros lugares. Em alguns casos, um método segue uma estrutura de árvore simples, como um método Initialize que chama InitializePlugins, InitializeInterface etc.

Se você tem um método / classe realmente grande, geralmente é um sinal de que está fazendo muito e é necessário refatorar para interromper o "blob". Talvez oculte alguma complexidade em outra classe sob uma abstração e use injeção de dependência

Homde
fonte
1
É bom ler que nem todos seguem cegamente a regra da "única coisa"! ; p Boa resposta!
Steven Jeuris
16
Costumo dividir um método grande em métodos menores, mesmo que eles sirvam apenas o método maior. A razão para dividir isso é porque fica mais fácil lidar e entender, em vez de apenas ter um enorme blob de código (que pode ou não ser comentado por si próprio e bem).
gablin
1
Tudo bem também em alguns casos em que você realmente não pode evitar um método grande. Por exemplo, se você tem um método Initialize, você pode chamar InitializeSystems, InitializePlugins etc. etc. Deve-se sempre verificar se não há necessidade de refatoração.
Homde
1
O principal de todo método (ou função) é que ele deve ter uma interface conceitual clara, é "contrato". Se você não pode definir isso, será um problema e você errou na fatoração. (Explicitamente documentando o contrato pode ser uma coisa boa - ou usar uma ferramenta para aplicá-la, Eiffel de estilo - mas não é tão importante quanto tê-lo lá em primeiro lugar.)
Donal Fellows
3
@ gablin: outro benefício a considerar: quando eu dividir as coisas em funções menores, não pense "elas servem apenas uma outra função", pense "elas servem apenas uma outra função por enquanto ". Houve várias ocasiões em que a redefinição de métodos grandes me salvou muito tempo ao escrever outros métodos no futuro, porque as funções menores eram mais genéricas e reutilizáveis.
GSto
17

Eu acho que, em geral, é melhor errar do lado de muitas funções, em vez de poucas. As duas exceções a essa regra que eu vi na prática são para os princípios DRY e YAGNI . Se você tiver muitas funções quase idênticas, combine-as e use parâmetros para evitar se repetir. Você também pode ter muitas funções se você as criou "apenas por precaução" e elas não estão sendo usadas. Não vejo absolutamente nada de errado em ter uma função que é usada apenas uma vez se adicionar legibilidade e facilidade de manutenção.

Karl Bielefeldt
fonte
10

A grande resposta de jmort253 me inspirou a seguir com uma resposta "sim, mas" ...

Ter muitos métodos privados pequenos não é algo ruim, mas preste atenção ao sentimento de insatisfação que está fazendo você postar uma pergunta aqui :). Se você chegar a um ponto em que está escrevendo testes focados apenas em métodos privados (chamando-os diretamente ou configurando um cenário em que um seja chamado de uma certa maneira, apenas para que você possa testar o resultado), então você deve dar um passo atrás.

O que você pode ter é uma nova classe com sua própria interface pública mais focada tentando escapar. Nesse ponto, você pode querer pensar em extrair uma classe para encapsular a parte de sua funcionalidade de classes existente que atualmente vive no método privado. Agora sua classe original pode usar sua nova classe como uma dependência e você pode escrever testes mais focados diretamente nessa classe.

Pete Hodgson
fonte
Eu acho que essa é uma ótima resposta e é uma direção que o código certamente poderia seguir. As práticas recomendadas dizem usar o modificador de acesso mais restritivo que fornece a funcionalidade necessária. Se os métodos não forem usados ​​fora da classe, o privado fará mais sentido. Se faz mais sentido tornar os métodos públicos ou movê-los para outra classe para que possam ser usados ​​em outros lugares, então, por todos os meios, isso também pode ser feito. 1
jmort253
8

Quase todos os projetos de OO em que participei sofreram muito com classes muito grandes e métodos muito longos.

Quando sinto a necessidade de um comentário explicando a próxima seção do código, extraio essa seção em um método separado. A longo prazo, isso torna o código mais curto. Esses pequenos métodos são reutilizados mais do que você esperaria inicialmente. Você começa a ver oportunidades de reunir vários deles em uma classe separada. Em breve, você estará pensando em seu problema em um nível superior e todo o esforço será muito mais rápido. Novos recursos são mais fáceis de escrever, porque você pode desenvolver essas pequenas classes criadas a partir desses pequenos métodos.

Kevin Cline
fonte
7

Uma classe com 5 métodos públicos e 25 métodos privados não parece tão grande para mim. Apenas certifique-se de que suas aulas tenham responsabilidades bem definidas e não se preocupe muito com o número de métodos.

Dito isso, esses métodos particulares devem se concentrar em um aspecto específico de toda a tarefa, mas não da maneira "doSomethingPart1", "doSomethingPart2". Você não ganha nada se esses métodos particulares são apenas partes de uma longa história arbitrariamente dividida, cada uma sendo sem sentido fora de todo o contexto.

user281377
fonte
+1 em "Você não ganha nada se esses métodos particulares são apenas partes de uma longa história arbitrariamente dividida, cada uma sem sentido fora de todo o contexto".
Mahdi
4

Trecho do Código Limpo de R. Martin (p. 176):

Mesmo conceitos tão fundamentais quanto a eliminação da duplicação, a expressividade do código e o SRP podem ser levados longe demais. Em um esforço para tornar nossas classes e métodos pequenos, podemos criar muitas classes e métodos minúsculos. Portanto, essa regra [minimizar o número de classes e métodos] sugere que também mantemos nossa função e a contagem de classes baixa. Contagens elevadas de classe e método são algumas vezes o resultado de um dogmatismo sem sentido.

user2418306
fonte
3

Algumas opções:

  1. Isso é bom. Apenas nomeie os métodos privados, assim como seus métodos públicos. E nomeie seus métodos públicos também.

  2. Abstraia alguns desses métodos auxiliares em classes auxiliares ou módulos auxiliares. E certifique-se de que essas classes / módulos auxiliares tenham um bom nome e sejam abstrações sólidas por si só.

yfeldblum
fonte
1

Eu não acho que exista um problema de "muitos métodos privados" se parecer que isso é provavelmente um sintoma da arquitetura de código ruim.

Aqui está como eu penso sobre isso ... Se a arquitetura é bem projetada, geralmente é óbvio onde o código que faz X deve estar. É aceitável se houver algumas exceções a isso - mas elas precisam ser realmente excepcionais, caso contrário refatorem a arquitetura para lidar com elas como padrão.

Se o problema é que, ao abrir sua classe, ele está cheio de métodos particulares que estão dividindo pedaços maiores em pedaços menores de código, talvez isso seja um sintoma de falta de arquitetura. Em vez de classes e arquitetura, essa área de código foi implementada de maneira processual dentro de uma classe.

Encontrar um equilíbrio aqui depende do projeto e de seus requisitos. O contra-argumento para isso é o ditado de que um programador júnior pode interromper a solução em 50 linhas de código que leva um arquiteto a 50 classes.

Michael Shaw
fonte
0

Existe algo como ter muitas funções / métodos privados?

Sim.

No Python, o conceito de "privado" (usado pelo C ++, Java e C #) realmente não existe.

Existe uma convenção de nomenclatura "meio que privada", mas é isso.

Privado pode levar a códigos difíceis de testar. Ele também pode quebrar o "Princípio Aberto-Fechado", tornando o código simplesmente fechado.

Consequentemente, para pessoas que usaram Python, funções privadas não têm valor. Apenas torne tudo público e termine com isso.

S.Lott
fonte
1
Como ele pode quebrar o princípio aberto aberto? O código está aberto para extensão e fechado para modificação, independentemente de os métodos públicos terem sido divididos em métodos particulares menores.
byxor