¿Existe tal cosa como tener demasiadas funciones / métodos privados?

63

Entiendo la importancia de un código bien documentado. Pero también entiendo la importancia del código autodocumentado . Cuanto más fácil sea leer visualmente una función en particular, más rápido podremos avanzar durante el mantenimiento del software.

Dicho esto, me gusta separar las funciones grandes en otras más pequeñas. Pero lo hago hasta el punto en que una clase puede tener más de cinco de ellos solo para servir un método público. Ahora multiplique cinco métodos privados por cinco públicos, y obtendrá alrededor de veinticinco métodos ocultos que probablemente serán llamados solo una vez por esos públicos.

Claro, ahora es más fácil leer esos métodos públicos, pero no puedo evitar pensar que tener demasiadas funciones es una mala práctica.

[Editar]

La gente me ha preguntado por qué creo que tener demasiadas funciones es una mala práctica.

La respuesta simple: es un presentimiento.

Mi creencia no está, ni por un momento, respaldada por ninguna hora de experiencia en ingeniería de software. Es solo una incertidumbre que me dio un "bloqueo del escritor", pero para un programador.

En el pasado, solo he estado programando proyectos personales. Hace poco me mudé a proyectos basados ​​en equipo. Ahora, quiero asegurarme de que otros puedan leer y comprender mi código.

No estaba seguro de qué mejorará la legibilidad. Por un lado, estaba pensando en separar una función grande en otras más pequeñas con nombres inteligibles. Pero había otro lado de mí que decía que era redundante.

Entonces, estoy pidiendo esto para iluminarme a fin de elegir el camino correcto.

[Editar]

A continuación, incluí dos versiones de cómo podría resolver mi problema. El primero lo resuelve al no separar grandes fragmentos de código. El segundo hace cosas separadas.

Primera versión:

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 versión:

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");
}

En los ejemplos anteriores, este último llama a una función que solo se utilizará una vez durante todo el tiempo de ejecución del programa.

Nota: el código anterior está generalizado, pero es de la misma naturaleza que mi problema.

Ahora, aquí está mi pregunta: ¿cuál? ¿Elijo el primero o el segundo?

Sal
fuente
1
¿"tener demasiadas funciones es una mala práctica"? ¿Por qué? Actualice su pregunta para explicar por qué esto le parece malo.
S.Lott
Recomiendo leer la noción de "cohesión de clase": Bob Martin lo analiza en "Código limpio".
JᴀʏMᴇᴇ
Agregando a otras respuestas, dividir y nombrar, pero tengamos en cuenta que, a medida que una clase crece, sus variables miembro se vuelven más y más como variables globales. Una forma de mejorar el problema (aparte de mejores soluciones como la refactorización ;-)) es separar esos pequeños métodos privados en funciones independientes si eso es posible (ya que no necesitan acceso a gran cantidad de estado). Algunos lenguajes permiten funciones fuera de clase, otros tienen clases estáticas. De esta manera, puede comprender esa función de forma aislada.
Pablo H
Si alguien puede decirme por qué "esta respuesta no es útil", estaría agradecido. Uno siempre puede aprender. :-)
Pablo H
@PabloH La respuesta que proporcionó le da una buena observación y una visión al OP, sin embargo, en realidad no responde a la pregunta que se le hizo. Lo moví al campo de comentarios.
maple_shaft

Respuestas:

36

Ahora multiplique cinco métodos privados por cinco públicos, y obtendrá alrededor de veinticinco métodos ocultos que probablemente serán llamados solo una vez por esos públicos.

Esto es lo que se conoce como encapsulación , que crea una abstracción de control en el nivel superior. Ésto es una cosa buena.

Esto significa que cualquiera que lea el código, cuando llegan a la startTheEngine()método en su código, puede ignorar todos los detalles de nivel inferior, tales como openIgnitionModule(), turnDistributorMotor(), sendSparksToSparkPlugs(), injectFuelIntoCylinders(), activateStarterSolenoid(), y todos los otros complejos, pequeños, las funciones que se deben ejecutar con el fin de facilitar la función mucho más grande y más abstracta de startTheEngine().

A menos que el problema con el que esté lidiando en su código se relacione directamente con uno de esos componentes, los mantenedores de código pueden seguir adelante, ignorando la funcionalidad encapsulada y de espacio aislado.

Esto también tiene la ventaja adicional de hacer que su código sea más fácil de probar. . Por ejemplo, puedo escribir un caso de turnAlternatorMotor(int revolutionRate)prueba y probar su funcionalidad completamente independiente de los otros sistemas. Si hay un problema con esa función y la salida no es lo que esperaba, entonces sé cuál es el problema.

El código que no se divide en componentes es mucho más difícil de probar. De repente, sus mantenedores solo están mirando la abstracción en lugar de poder profundizar en componentes medibles.

Mi consejo es seguir haciendo lo que está haciendo, ya que su código se escalará, será fácil de mantener y podrá usarse y actualizarse en los próximos años.

jmort253
fuente
3
¿Entonces cree que es bueno 'encapsular' hacer que la lógica esté disponible para toda una clase que solo se llama en un lugar?
Steven Jeuris
99
@ Steven Jeuris: Mientras esas funciones se hagan privadas, no veo ningún problema con eso; de hecho, me parece que, como se indica en esta respuesta, es más fácil de leer. Es mucho más fácil entender una función pública de alto nivel que solo llama a una serie de funciones privadas de bajo nivel (que, por supuesto, han sido nombradas apropiadamente). Claro, todo ese código podría haberse puesto en la función de alto nivel en sí, pero luego tomaría más tiempo comprender el código, ya que hay que mirar mucho más en el mismo lugar.
gablin
2
@gablin: aún se puede acceder a las funciones privadas desde toda la clase. En este artículo (aparentemente controvertido) analizo cómo se pierde la 'legibilidad global' cuando se tienen demasiadas funciones. En realidad, un principio común además de que las funciones deberían hacer solo una cosa, es que no debería tener demasiadas. Solo obtienes 'legibilidad local' al dividir solo por brevedad, lo que también se puede lograr con comentarios simples y 'párrafos de código'. Ok, el código debe autodocumentarse, ¡pero los comentarios no son obsoletos!
Steven Jeuris
1
@ Steven - ¿Qué es más fácil de entender? myString.replaceAll(pattern, originalData);¿o pegar todo el método replaceAll en mi código, incluida la matriz de caracteres de respaldo que representa el objeto de cadena subyacente cada vez que necesito usar la funcionalidad de una cadena?
jmort253
77
@ Steven Jeuris: Tienes un punto. Si una clase tiene demasiadas funciones (públicas o privadas), entonces tal vez toda la clase está tratando de hacer demasiado. En tales casos, puede ser mejor dividirlo en varias clases más pequeñas, del mismo modo que dividiría una función demasiado grande en varias funciones más pequeñas.
gablin
22

Si y no. El principio fundamental es: un método debe hacer una cosa y solo una cosa

Es correcto "dividir" su método en métodos más pequeños. Por otra parte, esos métodos deberían ser lo suficientemente generales como para no solo servir ese método "grande" sino ser reutilizables en otros lugares. Sin embargo, en algunos casos, un método seguirá una estructura de árbol simple, como un Método Initialize que llama InitializePlugins, InitializeInterface, etc.

Si tiene un método / clase realmente grande, generalmente es una señal de que está haciendo demasiado y necesita refactorizar para dividir el "blob". Los perphaps ocultan cierta complejidad en otra clase bajo una abstracción, y usan inyección de dependencia

Homde
fuente
1
¡Es bueno leer que no todos siguen la regla de 'una cosa' a ciegas! ; p ¡Buena respuesta!
Steven Jeuris
16
A menudo divido un método grande en métodos más pequeños, aunque solo servirán para el método más grande. La razón para dividirlo es porque se vuelve más fácil de manejar y comprender, en lugar de tener una gran cantidad de código (que puede o no ser autoevaluado y bien comentado).
gablin
1
Eso también está bien en algunos casos en los que realmente no puedes evitar un método grande. Por ejemplo, si tiene un método Initialize, puede hacer que llame a InitializeSystems, InitializePlugins, etc. Siempre debe comprobarse a sí mismo que la refactorización no es necesaria
Homde
1
La clave de cada método (o función) es que debe tener una interfaz conceptual clara, es "contrato". Si no puede precisar eso, será un problema y tendrá la factorización equivocada. (Documentar explícitamente el contrato puede ser algo bueno, o usar una herramienta para imponerlo, al estilo Eiffel, pero no es tan importante como tenerlo allí en primer lugar.)
Donal Fellows
3
@gablin: otra ventaja a tener en cuenta: cuando rompa las cosas en funciones más pequeñas, no creo "que sólo sirven otra función", piensan "que sólo sirven otra función por ahora ". Ha habido varias ocasiones en las que re-factorizar métodos grandes me ha ahorrado mucho tiempo al escribir otros métodos en el futuro porque las funciones más pequeñas eran más genéricas y reutilizables.
GSto
17

Creo que, en general, es mejor errar del lado de demasiadas funciones en lugar de muy pocas. Las dos excepciones a esa regla que he visto en la práctica son para los principios DRY y YAGNI . Si tiene muchas funciones casi idénticas, debe combinarlas y usar parámetros para evitar repetirse. También puede tener demasiadas funciones si las creó "por si acaso" y no se están utilizando. No veo absolutamente nada de malo en tener una función que solo se usa una vez si agrega legibilidad y facilidad de mantenimiento.

Karl Bielefeldt
fuente
10

La gran respuesta de jmort253 me inspiró a seguir con una respuesta de "sí, pero" ...

Tener muchos métodos privados pequeños no es algo malo, pero presta atención a esa sensación molesta que te hace publicar una pregunta aquí :). Si llega a un punto en el que escribe pruebas que se centran solo en métodos privados (ya sea llamándolos directamente o configurando un escenario tal que se llame de una manera determinada para que pueda probar el resultado) deberías dar un paso atrás.

Lo que puede tener es una nueva clase con su propia interfaz pública más enfocada que intenta escapar. En ese punto, es posible que desee pensar en extraer una clase para encapsular esa parte de la funcionalidad de sus clases existentes que actualmente vive en un método privado. Ahora su clase original puede usar su nueva clase como una dependencia, y puede escribir pruebas más enfocadas directamente en esa clase.

Pete Hodgson
fuente
Creo que esta es una gran respuesta y es una dirección en la que seguramente podría ir el código. Las mejores prácticas dicen utilizar el modificador de acceso más restrictivo que le brinda la funcionalidad que necesita. Si los métodos no se usan fuera de la clase, privado tiene más sentido. Si tiene más sentido hacer públicos los métodos o trasladarlos a otra clase para que puedan usarse en otro lugar, entonces, por supuesto, eso también se puede hacer. +1
jmort253
8

Casi todos los proyectos de OO a los que me he unido sufrieron mucho por las clases que eran demasiado grandes y los métodos que eran demasiado largos.

Cuando siento la necesidad de un comentario que explique la siguiente sección de código, entonces extraigo esa sección en un método separado. A la larga, esto acorta el código. Esos pequeños métodos se reutilizan más de lo que cabría esperar inicialmente. Empiezas a ver oportunidades para reunir un montón de ellos en una clase separada. Pronto estará pensando en su problema a un nivel superior y todo el esfuerzo será mucho más rápido. Las nuevas características son más fáciles de escribir, porque puedes construir sobre esas pequeñas clases que creaste a partir de esos pequeños métodos.

Kevin Cline
fuente
7

Una clase con 5 métodos públicos y 25 métodos privados no me parece tan grande. Solo asegúrese de que sus clases tengan responsabilidades bien definidas y no se preocupe demasiado por la cantidad de métodos.

Dicho esto, esos métodos privados deberían centrarse en un aspecto específico de toda la tarea, pero no de manera "doSomethingPart1", "doSomethingPart2". No ganas nada si esos métodos privados son solo piezas de una larga historia dividida arbitrariamente, cada una sin sentido fuera de todo el contexto.

usuario281377
fuente
+1 para "No ganas nada si esos métodos privados son solo piezas de una larga historia dividida arbitrariamente, cada una sin sentido fuera de todo el contexto".
Mahdi
4

Extracto del Código Limpio de R. Martin (p. 176):

Incluso conceptos tan fundamentales como la eliminación de la duplicación, la expresividad del código y el SRP pueden llevarse demasiado lejos. En un esfuerzo por hacer que nuestras clases y métodos sean pequeños, podríamos crear demasiadas clases y métodos pequeños. Entonces, esta regla [minimizar el número de clases y métodos] sugiere que también mantengamos bajas nuestras funciones y recuentos de clases. Los recuentos de clase y método altos a veces son el resultado de un dogmatismo sin sentido.

usuario2418306
fuente
3

Algunas opciones:

  1. Esta bien. Simplemente nombra los métodos privados, así como también los nombres de tus métodos públicos. Y nombra bien tus métodos públicos.

  2. Resuma algunos de estos métodos auxiliares en clases auxiliares o módulos auxiliares. Y asegúrese de que estas clases / módulos auxiliares estén bien nombrados y sean abstracciones sólidas en sí mismas.

Yfeldblum
fuente
1

No creo que haya un problema de "demasiados métodos privados" si se siente así, probablemente sea un síntoma de una arquitectura de código deficiente.

Así es como lo pienso ... Si la arquitectura está bien diseñada, generalmente es obvio dónde debería estar el código que hace X. Es aceptable si hay un par de excepciones a esto, pero estas deben ser realmente excepcionales, de lo contrario, refactorice la arquitectura para manejarlas como estándar.

Si el problema es que al abrir su clase, está lleno de métodos privados que están dividiendo fragmentos más grandes en fragmentos de código más pequeños, entonces quizás este sea un síntoma de falta de arquitectura. En lugar de clases y arquitectura, esta área de código se ha implementado de manera procesal dentro de una clase.

Encontrar un equilibrio aquí depende del proyecto y sus requisitos. El argumento contrario a esto es el dicho de que un programador junior puede activar la solución en 50 líneas de código que toma un arquitecto 50 clases.

Michael Shaw
fuente
0

¿Existe tal cosa como tener demasiadas funciones / métodos privados?

Si.

En Python, el concepto de "privado" (como lo usan C ++, Java y C #) realmente no existe.

Hay una convención de nomenclatura "privada", pero eso es todo.

Privado puede conducir a un código difícil de probar. También puede romper el "Principio de Abierto-Cerrado" al hacer que el código simplemente se cierre.

En consecuencia, para las personas que han usado Python, las funciones privadas no tienen valor. Simplemente haga todo público y termine con esto.

S.Lott
fuente
1
¿Cómo puede romper el principio abierto cerrado? El código está abierto para la extensión y cerrado para la modificación, independientemente de si los métodos públicos se han dividido en métodos privados más pequeños.
byxor