En general, uso métodos privados para encapsular la funcionalidad que se reutiliza en varios lugares de la clase. Pero a veces tengo un gran método público que podría dividirse en pasos más pequeños, cada uno en su propio método privado. Esto acortaría el método público, pero me preocupa que obligar a cualquiera que lea el método a saltar a diferentes métodos privados dañará la legibilidad.
¿Hay consenso sobre esto? ¿Es mejor tener métodos públicos largos o dividirlos en piezas más pequeñas, incluso si cada pieza no es reutilizable?
coding-style
readability
Jordak
fuente
fuente
Respuestas:
No, este no es un mal estilo. De hecho es un muy buen estilo.
Las funciones privadas no necesitan existir simplemente debido a la reutilización. Esa es ciertamente una buena razón para crearlos, pero hay otra: descomposición.
Considere una función que hace demasiado. Tiene cien líneas de largo e imposible de razonar.
Si divide esta función en partes más pequeñas, todavía "hace" tanto trabajo como antes, pero en partes más pequeñas. Llama a otras funciones que deberían tener nombres descriptivos. La función principal se lee casi como un libro: hacer A, luego hacer B, luego hacer C, etc. Las funciones que llama solo pueden llamarse en un lugar, pero ahora son más pequeñas. Cualquier función particular está necesariamente aislada de las otras funciones: tienen diferentes ámbitos.
Cuando descompone un problema grande en problemas más pequeños, incluso si esos problemas (funciones) más pequeños solo se usan / resuelven una vez, obtiene varios beneficios:
Legibilidad. Nadie puede leer una función monolítica y entender lo que hace completamente. Puedes seguir mintiéndote a ti mismo o dividirlo en trozos pequeños que tengan sentido.
Localidad de referencia. Ahora se vuelve imposible declarar y usar una variable, luego hacer que se quede y volver a usar 100 líneas más tarde. Estas funciones tienen diferentes ámbitos.
Pruebas. Si bien solo es necesario evaluar unitariamente a los miembros públicos de una clase, también puede ser conveniente evaluar a ciertos miembros privados. Si hay una sección crítica de una función larga que podría beneficiarse de la prueba, es imposible probarla de forma independiente sin extraerla a una función separada.
Modularidad. Ahora que tiene funciones privadas, puede encontrar una o más de ellas que podrían extraerse en una clase separada, ya sea que se use solo aquí o sea reutilizable. Para el punto anterior, es probable que esta clase separada también sea más fácil de probar, ya que necesitará una interfaz pública.
La idea de dividir el código grande en piezas más pequeñas que sean más fáciles de entender y probar es un punto clave del libro Clean Code del tío Bob . Al momento de escribir esta respuesta, el libro tenía nueve años, pero hoy es tan relevante como lo era en aquel entonces.
fuente
¡Probablemente sea una gran idea!
No estoy de acuerdo con dividir largas secuencias lineales de acción en funciones separadas simplemente para reducir la longitud promedio de la función en su base de código:
Ahora ha agregado líneas de fuente y ha reducido considerablemente la legibilidad total. Especialmente si ahora está pasando muchos parámetros entre cada función para realizar un seguimiento del estado. ¡Ay!
Sin embargo , si ha logrado extraer una o más líneas en una función pura que cumple un único propósito claro ( incluso si se llama solo una vez ), entonces ha mejorado la legibilidad:
Es probable que esto no sea fácil en situaciones del mundo real, pero las piezas de funcionalidad pura a menudo se pueden descifrar si lo piensas lo suficiente.
Sabrás que estás en el camino correcto cuando tienes funciones con nombres verbales agradables y cuando la función principal los llama y todo se lee prácticamente como un párrafo de prosa.
Luego, cuando regrese semanas más tarde para agregar más funcionalidades y descubra que realmente puede reutilizar una de esas funciones, entonces , ¡alegría gozosa! ¡Qué maravilloso deleite radiante!
fuente
foo = compose(reglorbulate, deglorbFramb, getFrambulation);
La respuesta es que depende mucho de la situación. @Snowman cubre los aspectos positivos de la ruptura de una gran función pública, pero es importante recordar que también puede haber efectos negativos, ya que le preocupa con razón.
Muchas funciones privadas con efectos secundarios pueden hacer que el código sea difícil de leer y bastante frágil. Esto es especialmente cierto cuando estas funciones privadas dependen de los efectos secundarios entre sí. Evite tener funciones estrechamente acopladas.
Las abstracciones tienen fugas . Si bien es bueno fingir cómo se realiza una operación o cómo se almacenan los datos, no importa, hay casos en los que lo hace, y es importante identificarlos.
La semántica y el contexto importan. Si no logra capturar claramente lo que hace una función en su nombre, nuevamente puede reducir la legibilidad y aumentar la fragilidad de la función pública. Esto es especialmente cierto cuando comienza a crear funciones privadas con un gran número de parámetros de entrada y salida. Y mientras que desde la función pública, ves qué funciones privadas llama, desde la función privada, no ves qué funciones públicas lo llaman. Esto puede conducir a una "corrección de errores" en una función privada que rompe la función pública.
El código muy descompuesto siempre es más claro para el autor que para otros. Esto no significa que todavía no pueda ser claro para los demás, pero es fácil decir que tiene mucho sentido que
bar()
deba llamarse antesfoo()
en el momento de la escritura.Reutilización peligrosa. Su función pública probablemente restringió qué entrada fue posible para cada función privada. Si estos supuestos de entrada no se capturan correctamente (documentar TODOS los supuestos es difícil), alguien puede reutilizar incorrectamente una de sus funciones privadas e introducir errores en la base de código.
Descomponga las funciones en función de su cohesión interna y acoplamiento, no longitud absoluta.
fuente
MainMethod
(lo suficientemente fácil de obtener, generalmente se incluyen símbolos y debe tener un archivo de desreferencia de símbolos) que es de 2k líneas (los números de línea nunca se incluyen en informes de errores) y es un NRE que puede suceder en 1k de esas líneas, bueno, estaré condenado si estoy investigando eso. Pero si me dicen que sucedióSomeSubMethodA
y son 8 líneas y la excepción fue una NRE, entonces probablemente encontraré y arreglaré eso lo suficientemente fácil.En mi humilde opinión, el valor de extraer bloques de código simplemente como un medio para romper la complejidad, a menudo estaría relacionado con la diferencia de complejidad entre:
Una descripción completa y precisa en lenguaje humano de lo que hace el código, incluyendo cómo maneja los casos de esquina , y
El código en sí.
Si el código es mucho más complicado que la descripción, reemplazar el código en línea con una llamada de función puede facilitar la comprensión del código circundante. Por otro lado, algunos conceptos se pueden expresar de manera más legible en un lenguaje de computadora que en un lenguaje humano. Consideraría
w=x+y+z;
, por ejemplo, que es más legible quew=addThreeNumbersAssumingSumOfFirstTwoDoesntOverflow(x,y,z);
.A medida que se divide una función grande, habrá cada vez menos diferencia entre la complejidad de las subfunciones y sus descripciones, y la ventaja de subdivisiones adicionales disminuirá. Si las cosas se dividen hasta el punto de que las descripciones serían más complicadas que el código, las divisiones adicionales empeorarán el código.
fuente
int average3(int n1, int n2, int n3)
. Separar la función "promedio" significaría que alguien que quisiera ver si era adecuado para los requisitos tendría que buscar en dos lugares.Es un desafío de equilibrio.
Más fino es mejor
El método privado proporciona efectivamente un nombre para el código contenido y, a veces, una firma significativa (a menos que la mitad de sus parámetros sean datos de vaciado completamente ad-hoc con interdependencias indocumentadas y poco claras).
Dar nombres a las construcciones de código es generalmente bueno, siempre que los nombres sugieran un contrato significativo para la persona que llama, y el contrato del método privado corresponde exactamente a lo que sugiere el nombre.
Al obligarse a pensar en contratos significativos para porciones más pequeñas del código, el desarrollador inicial puede detectar algunos errores y evitarlos sin dolor incluso antes de cualquier prueba de desarrollo. Esto solo funciona si el desarrollador se esfuerza por nombrar conciso (un sonido simple pero preciso) y está dispuesto a adaptar los límites del método privado para que el nombre conciso sea incluso posible.
El mantenimiento posterior también es útil, ya que los nombres adicionales ayudan a que el código se auto documente .
Hasta que se pone muy bien
¿Es posible exagerar dando nombres a pequeños fragmentos de código y terminar con demasiados métodos privados demasiado pequeños? Seguro. Uno o más de los siguientes síntomas indican que los métodos son demasiado pequeños para ser útiles:
XxxInternal
oDoXxx
, especialmente si no hay un esquema unificado para introducirlas.LogDiskSpaceConsumptionUnlessNoUpdateNeeded
fuente
Al contrario de lo que han dicho los demás, diría que un método público largo es un olor de diseño que es no se rectifica mediante la descomposición en métodos privados.
Si este es el caso, entonces argumentaría que cada paso debe ser su propio ciudadano de primera clase con una única responsabilidad cada uno. En un paradigma orientado a objetos, sugeriría crear una interfaz y una implementación para cada paso de tal manera que cada uno tenga una responsabilidad única, fácilmente identificable y se pueda nombrar de manera que la responsabilidad sea clara. Esto le permite probar el método público grande (anteriormente), así como cada paso individual independientemente uno del otro. Todos deberían estar documentados también.
¿Por qué no descomponerse en métodos privados? Aquí hay algunas razones:
Esta es una discusión que tuve recientemente con uno de mis colegas. Argumenta que tener el comportamiento completo de un módulo en el mismo archivo / método mejora la legibilidad. Estoy de acuerdo en que el código es más fácil de seguir cuando están todos juntos, pero es menos fácil razonar sobre el código a medida que crece la complejidad. A medida que un sistema crece, se vuelve intratable razonar sobre el módulo completo como un todo. Cuando descompone la lógica compleja en varias clases, cada una con una sola responsabilidad, resulta mucho más fácil razonar sobre cada parte.
fuente