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?
fuente
Respuestas:
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 ylargeReportProcessingFunction
no debe depender de ella directamente.Sin embargo, tenga en cuenta que esta es una mala elección de nombres:
Verás,
callAllSection1Functions
es 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 suprocessSection1
lugar o lo que sea realmente significativo en el contexto.fuente
callAllSection1Functions
filtra detalles sobre la implementación. Desde la perspectiva exterior, no importa siprocessSection1
difiere 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 aprocessSection1
, 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.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
Section
clase 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?
fuente
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.
fuente
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.
fuente
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.
fuente
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:
¿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?
fuente
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.
fuente
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.
fuente