Funciones que solo llaman a otras funciones. ¿Es esta una buena practica?

13

Actualmente estoy trabajando en un conjunto de informes que tienen muchas secciones diferentes (todas requieren un formato diferente), y estoy tratando de encontrar la mejor manera de estructurar mi código. Informes similares que hemos hecho en el pasado terminan teniendo funciones muy grandes (más de 200 líneas) que hacen toda la manipulación de datos y el formato del informe, de modo que el flujo de trabajo se ve más o menos así:

DataTable reportTable = new DataTable();
void RunReport()
{
    reportTable = DataClass.getReportData();
    largeReportProcessingFunction();
    outputReportToUser();
}

Me gustaría poder dividir estas grandes funciones en trozos más pequeños, pero me temo que terminaré teniendo docenas de funciones no reutilizables, y una función similar de "hacer todo aquí" cuyo único trabajo es llama a todas estas funciones más pequeñas, así:

void largeReportProcessingFunction()
{
    processSection1HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    processSection1SummaryTableData();
    calculateSection1SummaryTableTotalRow();
    formatSection1SummaryTableDisplay();
    processSection1FooterData();
    getSection1FooterSummaryTotals();
    formatSection1FooterDisplay();

    processSection2HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    calculateSection1HeaderAverages();
    ...
}

O, si vamos un paso más allá:

void largeReportProcessingFunction()
{
    callAllSection1Functions();

    callAllSection2Functions();

    callAllSection3Functions();
    ...        
}

¿Es esta realmente una mejor solución? Desde el punto de vista de la organización, supongo que lo es (es decir, todo está mucho más organizado de lo que podría estarlo), pero en cuanto a la legibilidad del código, no estoy seguro (cadenas de funciones potencialmente grandes que solo llaman a otras funciones).

Pensamientos?

Eric C.
fuente
Al final del día ... ¿es más legible? Sí, entonces es una mejor solución.
sylvanaar

Respuestas:

18

Esto se conoce comúnmente como descomposición funcional y, en general, es algo bueno si se hace correctamente.

Además, la implementación de una función debe estar dentro de un único nivel de abstracción. Si lo hace largeReportProcessingFunction, su función es definir qué pasos de procesamiento diferentes se deben tomar en qué orden. La implementación de cada uno de esos pasos se encuentra en una capa de abstracción a continuación y largeReportProcessingFunctionno debe depender de ella directamente.

Sin embargo, tenga en cuenta que esta es una mala elección de nombres:

void largeReportProcessingFunction() {
    callAllSection1Functions();
    callAllSection2Functions();
    callAllSection3Functions();
    ...        
}

Verás, callAllSection1Functionses un nombre que en realidad no proporciona una abstracción, porque en realidad no dice lo que hace, sino cómo lo hace. Debería llamarse en su processSection1lugar o lo que sea realmente significativo en el contexto.

back2dos
fuente
66
Estoy de acuerdo en principio con esta respuesta, excepto que no puedo ver cómo "processSection1" es un nombre significativo. Es prácticamente equivalente a "callAllSection1Functions".
Eric King
2
@EricKing: callAllSection1Functionsfiltra detalles sobre la implementación. Desde la perspectiva exterior, no importa si processSection1difiere su trabajo a "todas las funciones de la sección 1" o si lo hace directamente o si usa polvo de duendecillo para convocar a un unicornio que hará el trabajo real. Todo lo que realmente importa es que, después de una llamada a processSection1, la sección 1 puede considerarse procesada . Estoy de acuerdo en que el procesamiento es un nombre genérico, pero si tiene una alta cohesión (que siempre debe buscar), entonces su contexto suele ser lo suficientemente estrecho como para desambiguarlo.
back2dos
2
Todo lo que quise decir fue que los métodos llamados "processXXX" son casi siempre nombres terribles e inútiles. Todos los métodos 'procesan' cosas, por lo que nombrarlo 'processXXX' es tan útil como nombrarlo 'doSomethingWithXXX' o 'manipulateXXX' y similares. Casi siempre hay un mejor nombre para los métodos llamados 'processXXX'. Desde un punto de vista práctico, es tan útil (inútil) como 'callAllSection1Functions'.
Eric King
4

Dijiste que estás usando C #, así que me pregunto si podrías construir una jerarquía de clases estructurada aprovechando el hecho de que estás usando un lenguaje orientado a objetos. Por ejemplo, si tiene sentido dividir el informe en secciones, tener una Sectionclase y ampliarla para los requisitos específicos del cliente.

Puede tener sentido pensar en ello como si estuviera creando una GUI no interactiva. Piense en los objetos que podría crear como si fueran componentes de GUI diferentes. Pregúntese cómo sería un informe básico. ¿Qué tal uno complejo? ¿Dónde deberían estar los puntos de extensión?

Jeremy Heiler
fuente
3

Depende. Si todas las funciones que está creando son realmente una sola unidad de trabajo, entonces está bien, si son solo las líneas 1-15, 16-30, 31-45 de su función de llamada, eso es malo. Las funciones largas no son malas, si hacen una cosa, si tiene 20 filtros para que su informe tenga una función de validación que verifique que los veinte serán largos. Eso está bien, dividirlo por filtro o grupos de filtros es solo agregar complejidad adicional para ningún beneficio real.

Tener muchas funciones privadas también hace que las pruebas unitarias automatizadas sean más difíciles, por lo que algunas intentarán evitarlas por ese motivo, pero en mi opinión, este es un razonamiento bastante pobre, ya que tiende a crear un diseño más grande y complejo para acomodar las pruebas.

Ryathal
fuente
3
En mi experiencia, las funciones largas son malas.
Bernard
Si su ejemplo estuviera en lenguaje OO, crearía una clase de filtro base y cada uno de los 20 filtros estaría en diferentes clases de derivación. La instrucción de cambio se reemplazaría por una llamada de creación para obtener el filtro correcto. Cada función hace una cosa y todas son pequeñas.
DXM
3

Como está utilizando C #, intente y piense más en los objetos. Parece que todavía está codificando procesalmente al tener variables de clase "globales" de las que dependen las funciones posteriores.

Romper su lógica en funciones separadas es definitivamente mejor que tener toda la lógica en una sola función. Apuesto a que podría ir más allá separando también los datos en los que funcionan las funciones dividiendo esto en varios objetos.

Considere tener una clase ReportBuilder donde pueda pasar los datos del informe. Si puede dividir ReportBuilder en clases separadas, haga esto también.

descifrador
fuente
2
Los procedimientos cumplen una función perfectamente buena.
DeadMG
1

Si.

Si tiene un código de segmentos que necesita ser utilizado en múltiples lugares, función propia. De lo contrario, si tiene que realizar un cambio en la funcionalidad, deberá realizar el cambio en varios lugares. Esto no solo aumenta el trabajo sino que aumenta el riesgo de que aparezcan errores a medida que el código se cambia en un lugar pero no en otro o donde se introduce un pero en un lugar pero no en el otro.

También ayuda a que el método sea más legible si se usan nombres descriptivos del método.

SoylentGray
fuente
1

El hecho de que utilice la numeración para distinguir funciones sugiere que existen problemas subyacentes con la duplicación o una clase que hace demasiado. Desglosar una función grande en muchas funciones más pequeñas puede ser un paso útil para refactorizar la duplicación, pero no debería ser la última. Por ejemplo, crea las dos funciones:

processSection1HeaderData();
processSection2HeaderData();

¿No hay similitudes en el código utilizado para procesar encabezados de sección? ¿Puedes extraer una función común para ambos parametrizando las diferencias?

Garrett Hall
fuente
Esto no es realmente un código de producción, por lo que los nombres de las funciones son solo ejemplos (en producción los nombres de las funciones son más descriptivos). En general, no, no hay similitudes entre las diferentes secciones (aunque cuando hay similitudes, intento reutilizar el código tanto como sea posible).
Eric C.
1

Es útil dividir una función en subfunciones lógicas, incluso si las subfunciones no se reutilizan, la divide en bloques cortos y fáciles de entender. Pero tiene que hacerse por unidades lógicas, no solo n # de líneas. La forma en que debe dividirse dependerá de su código, los temas comunes serían bucles, condiciones, operaciones en el mismo objeto; básicamente cualquier cosa que pueda describir como una tarea discreta.

Entonces, sí, es un buen estilo; esto puede sonar extraño, pero dada su descripción de reutilización limitada, le recomendaría que piense detenidamente sobre INTENTAR la reutilización. Asegúrese de que el uso sea realmente idéntico, y no solo por coincidencia. Intentar manejar todos los casos en el mismo bloque es con frecuencia cómo terminas con la gran función desgarbada en el primer lugar.

jmoreno
fuente
0

Creo que esto es principalmente una preferencia personal. Si sus funciones fueran a ser reutilizadas (¿y está seguro de que no podría hacerlas más genéricas y reutilizables?) Definitivamente diría que las dividiría. También he escuchado que es un buen principio que ninguna función o método debe tener más de 2-3 pantallas de código. Entonces, si su gran función sigue y sigue, podría hacer que su código sea más legible para dividirlo.

No mencionas qué tecnología estás utilizando para desarrollar estos informes. Parece que podrías estar usando C #. ¿Es eso correcto? Si es así, también puede considerar la directiva #region como alternativa si no desea dividir su función en otras más pequeñas.

Joshua Carmody
fuente
Sí, estoy trabajando en C #. Es posible que pueda reutilizar algunas funciones, pero para muchos de estos informes, las diferentes secciones tienen datos y diseños muy diferentes (requisito del cliente).
Eric C.
1
+1 excepto para sugerir regiones. No usaría regiones.
Bernard
@Bernard - ¿Alguna razón en particular? Supongo que el autor de la pregunta solo consideraría usar #regions si ya descartó dividir la función.
Joshua Carmody
2
Las regiones tienden a ocultar el código y pueden ser objeto de abuso (es decir, regiones anidadas). Me gusta ver todo el código a la vez en un archivo de código. Si necesito separar visualmente el código en un archivo, uso una línea de barras diagonales.
Bernard
2
Las regiones suelen ser una señal de que el código necesita una refactorización
codificador el