Je travaille actuellement sur un ensemble de rapports qui ont de nombreuses sections différentes (toutes nécessitant une mise en forme différente), et j'essaie de trouver la meilleure façon de structurer mon code. Des rapports similaires que nous avons réalisés dans le passé finissent par avoir de très grandes fonctions (200+ lignes) qui effectuent toutes les manipulations et le formatage des données pour le rapport, de sorte que le flux de travail ressemble à ceci:
DataTable reportTable = new DataTable();
void RunReport()
{
reportTable = DataClass.getReportData();
largeReportProcessingFunction();
outputReportToUser();
}
J'aimerais pouvoir diviser ces grandes fonctions en morceaux plus petits, mais je crains de ne finir par avoir des dizaines de fonctions non réutilisables, et une fonction similaire "tout faire ici" dont le seul travail consiste à appeler toutes ces petites fonctions, comme ceci:
void largeReportProcessingFunction()
{
processSection1HeaderData();
calculateSection1HeaderAverages();
formatSection1HeaderDisplay();
processSection1SummaryTableData();
calculateSection1SummaryTableTotalRow();
formatSection1SummaryTableDisplay();
processSection1FooterData();
getSection1FooterSummaryTotals();
formatSection1FooterDisplay();
processSection2HeaderData();
calculateSection1HeaderAverages();
formatSection1HeaderDisplay();
calculateSection1HeaderAverages();
...
}
Ou, si nous allons plus loin:
void largeReportProcessingFunction()
{
callAllSection1Functions();
callAllSection2Functions();
callAllSection3Functions();
...
}
Est-ce vraiment une meilleure solution? D'un point de vue organisationnel, je suppose que c'est le cas (c'est-à-dire que tout est beaucoup plus organisé qu'il ne le serait autrement), mais en ce qui concerne la lisibilité du code, je ne suis pas sûr (potentiellement de grandes chaînes de fonctions qui n'appellent que d'autres fonctions).
Pensées?
la source
Réponses:
Ceci est communément appelé décomposition fonctionnelle et est généralement une bonne chose à faire, s'il est fait correctement.
De plus, l'implémentation d'une fonction doit se faire dans un seul niveau d'abstraction. Si vous le faites
largeReportProcessingFunction
, son rôle est de définir quelles différentes étapes de traitement doivent être prises dans quel ordre. La mise en œuvre de chacune de ces étapes se fait sur une couche d'abstraction ci-dessous etlargeReportProcessingFunction
ne devrait pas en dépendre directement.Veuillez noter que c'est cependant un mauvais choix de nom:
Vous voyez,
callAllSection1Functions
c'est un nom qui ne fournit pas réellement une abstraction, car il ne dit pas réellement ce qu'il fait, mais plutôt comment il le fait. Il doit être appelé à laprocessSection1
place ou tout ce qui est réellement significatif dans le contexte.la source
callAllSection1Functions
détails sur la mise en œuvre. Du point de vue extérieur, peu importe queprocessSection1
son travail soit reporté à "toutes les fonctions de la section 1" ou qu'il le fasse directement ou qu'il utilise de la poudre de lutin pour invoquer une licorne qui fera le travail réel. Tout ce qui compte vraiment, c'est qu'après un appel àprocessSection1
, la section 1 puisse être considérée comme traitée . Je conviens que le traitement est un nom générique, mais si vous avez une cohésion élevée (que vous devriez toujours rechercher), votre contexte est généralement suffisamment étroit pour le lever.Vous avez dit que vous utilisez C #, alors je me demande si vous pourriez construire une hiérarchie de classe structurée en tirant parti du fait que vous utilisez un langage orienté objet? Par exemple, s'il est judicieux de diviser le rapport en sections, créez une
Section
classe et étendez-la pour les besoins spécifiques du client.Il peut être judicieux d'y penser comme si vous créiez une interface graphique non interactive. Pensez aux objets que vous pourriez créer comme s'il s'agissait de différents composants de l'interface graphique. Demandez-vous à quoi ressemblerait un rapport de base? Que diriez-vous d'un complexe? Où devraient être les points d'extension?
la source
Ça dépend. Si toutes les fonctions que vous créez sont vraiment une seule unité de travail, c'est bien, si ce ne sont que les lignes 1-15, 16-30, 31-45 de leur fonction d'appel qui est mauvaise. Les fonctions longues ne sont pas mauvaises, si elles font une chose, si vous avez 20 filtres pour votre rapport ayant une fonction de validation qui vérifie les vingt va être longue. C'est très bien, le séparer par filtre ou groupes de filtres ajoute simplement de la complexité supplémentaire sans réel avantage.
Le fait d'avoir beaucoup de fonctions privées rend également les tests unitaires automatisés plus difficiles, donc certains essaieront de l'éviter pour cette raison, mais c'est un raisonnement plutôt médiocre à mon avis car cela tend à créer une conception plus grande et plus complexe pour accueillir les tests.
la source
Puisque vous utilisez C #, essayez de penser davantage aux objets. Il semble que vous codiez toujours de manière procédurale en ayant des variables de classe "globales" dont dépendent les fonctions suivantes.
Décomposer votre logique en fonctions séparées est certainement mieux que d'avoir toute la logique dans une seule fonction. Je parierais que vous pourriez aller plus loin en séparant également les données sur lesquelles les fonctions fonctionnent en les répartissant en plusieurs objets.
Envisagez d'avoir une classe ReportBuilder où vous pouvez transmettre les données du rapport. Si vous pouvez diviser ReportBuilder en classes distinctes, faites-le également.
la source
Oui.
Si vous avez un segment de code qui doit être utilisé à plusieurs endroits, sa propre fonction. Sinon, si vous devez modifier la fonctionnalité, vous devrez effectuer la modification à plusieurs endroits. Cela augmente non seulement le travail, mais augmente également le risque de voir apparaître des bogues lorsque le code est modifié à un endroit mais pas à un autre ou là où un mais est introduit à un endroit mais pas à l'autre.
Il aide également à rendre la méthode plus lisible si des noms de méthode descriptifs sont utilisés.
la source
Le fait que vous utilisez la numérotation pour distinguer les fonctions suggère qu'il existe des problèmes sous-jacents avec la duplication ou une classe en faisant trop. La division d'une grande fonction en plusieurs fonctions plus petites peut être une étape utile pour refactoriser la duplication, mais ce ne devrait pas être la dernière. Par exemple, vous créez les deux fonctions:
N'y a-t-il pas des similitudes dans le code utilisé pour traiter les en-têtes de section? Pouvez-vous extraire une fonction commune pour les deux en paramétrant les différences?
la source
La décomposition d'une fonction en sous-fonctions logiques est utile, même si les sous-fonctions ne sont pas réutilisées - elle la décompose en blocs courts et faciles à comprendre. Mais cela doit être fait par des unités logiques, pas seulement n # de lignes. La façon dont vous devez le diviser dépendra de votre code, les thèmes courants seraient les boucles, les conditions, les opérations sur le même objet; essentiellement tout ce que vous pouvez décrire comme une tâche discrète.
Donc, oui, c'est un bon style - cela peut sembler étrange, mais étant donné que vous décrivez une réutilisation limitée, je vous recommande de réfléchir attentivement à la réutilisation ATTEMPTING. Assurez-vous que l'utilisation est vraiment identique, et pas seulement la même coïncidence. Tenter de gérer tous les cas dans le même bloc est souvent la façon dont vous vous retrouvez avec la grande fonction disgracieuse en premier lieu.
la source
Je pense que c'est surtout une préférence personnelle. Si vos fonctions devaient être réutilisées (et êtes-vous sûr que vous ne pourriez pas les rendre plus génériques et réutilisables?) Je dirais certainement de les diviser. J'ai également entendu dire que c'est un bon principe qu'aucune fonction ou méthode ne devrait contenir plus de 2 à 3 écrans de code. Donc, si votre grande fonction continue encore et encore, cela pourrait rendre votre code plus lisible pour le diviser.
Vous ne mentionnez pas la technologie que vous utilisez pour développer ces rapports. Il semble que vous utilisiez C #. Est-ce exact? Si c'est le cas, vous pouvez également considérer la directive #region comme une alternative si vous ne voulez pas diviser votre fonction en plus petites.
la source