Il existe un fichier source dans un projet assez important avec plusieurs fonctions extrêmement sensibles à la performance (appelées millions de fois par seconde). En fait, le responsable précédent a décidé d'écrire 12 copies d'une fonction différente très légèrement, afin de sauvegarder le temps qui serait consacré à la vérification des conditionnels dans une seule fonction.
Malheureusement, cela signifie que le code est un pita pour maintenir. Je voudrais supprimer tout le code dupliqué et écrire un seul modèle. Cependant, la langue, Java, ne prend pas en charge les modèles et je ne suis pas sûr que les génériques conviennent à cela.
Mon plan actuel consiste à écrire à la place un fichier qui génère les 12 copies de la fonction (un modèle de modèle unique à usage unique, pratiquement). Je ferais bien sûr une explication copieuse pour la raison pour laquelle le fichier doit être généré par programme.
Mon inquiétude est que cela conduirait à la confusion de main-d'œuvre future et à introduire peut-être des bugs désagréables s'ils oublient de régénérer le fichier après la modification ou (même pire) si elles modifient plutôt le fichier généré par programmation. Malheureusement, le cas échéant de réécrire le tout en C++, je ne vois aucun moyen de résoudre ce problème.
Les avantages de cette approche l'emportent-ils sur les inconvénients? Devrais-je plutôt:
P.s. En raison de la mauvaise conception du projet, le profilage de la fonction est plutôt difficile ... Cependant, l'ancien responsable m'a convaincu que le succès de la performance est inacceptable. Je suppose par cela, il signifie plus de 5%, bien que cela soit une supposition complète de ma part.
Peut-être que je devrais élaborer un peu. Les 12 copies font une tâche très similaire, mais ont des différences mineures. Les différences sont dans divers endroits de la fonction, malheureusement, de nombreuses déclarations conditionnelles sont nombreuses. Il existe efficacement 6 "modes" de fonctionnement et 2 "paradigmes" d'opération (mots composés par moi-même). Pour utiliser la fonction, on spécifie le "mode" et "paradigme" de fonctionnement. Ceci n'est jamais dynamique; Chaque morceau de code utilise exactement un mode et un paradigme. Tous les 12 paires de mode-paradigme sont utilisés quelque part dans l'application. Les fonctions sont judicieusement nommées Func1 à Func12, avec un chiffre même représentant le deuxième paradigme et les nombres impairs représentant le premier paradigme.
Je suis conscient que cela concerne le pire design que la main-d'œuvre est l'objectif. Mais il semble être "assez rapide", et ce code n'a pas besoin de modifications pendant un moment ... il convient également de noter que la fonction d'origine n'a pas été supprimée (bien que ce soit du code mort autant que je puisse dire) , alors le refactoring serait simple.
C'est une très mauvaise situation, vous devez refroidir ce dès que possible - c'est la dette technique dans son pire - vous ne savez même pas à quel point le code vraiment Seulement spécifique que c'est important.
Quant aux solutions ASAP:
[.____] Quelque chose qui peut être fait est d'ajouter une étape de compilation personnalisée. Si vous utilisez Maven qui est en fait assez simple à faire, d'autres systèmes de construction automatisés sont susceptibles de faire face à cela aussi. Écrivez un fichier avec A Différent Extension que .java et ajoutez une étape personnalisée qui recherche votre source pour des fichiers comme ça et régénère le réel .java. Vous voudrez peut-être aussi ajouter un énorme Disclaimer sur le fichier généré automatiquement expliquant de ne pas la modifier.
Avantages vs à l'aide d'un fichier une fois généré: vos développeurs n'obtiendront pas leurs modifications au travail .java. S'ils exécutent effectivement le code sur leur machine avant de commettre, ils constateront que leurs changements n'ont aucun effet (HAH). Et puis peut-être qu'ils vont lire la responsabilité. Vous avez absolument raison de ne pas faire confiance à vos coéquipiers et à votre futur moi en vous souvenant que ce fichier particulier doit être modifié d'une manière différente. Il permet également des tests automatiques tels que WELL, car Junit compilez votre programme avant d'exécuter des tests (et régénérez le fichier aussi).
ÉDITER
À en juger par les commentaires La réponse est sortie comme si c'était un moyen de faire ce travail indéfiniment et peut-être d'accord pour déployer dans d'autres parties critiques de votre projet.
Mettez simplement: ce n'est pas le cas.
Le fardeau supplémentaire de la création de votre propre mini-langage, d'écrire un générateur de code pour cela et de le maintenir, de ne pas mentionner l'enseignement aux futurs Mainteners est Hellish à long terme. Ce qui précède ne permet que A plus sûr moyen de gérer le problème pendant que vous travaillez sur une solution à long terme. Ce que cela prendra est au-dessus de moi.
Le maintien a-t-il atteint de vrai, ou cela vous dérange-t-il juste? Si cela ne vous dérange que, laissez-le seul.
Le problème de performance est-il réel ou le développeur précédent seulement pense C'était? Les problèmes de performance, plus souvent que non, ne sont pas là où ils sont considérés comme étant, même lorsqu'un profileur est utilisé. (J'utilise cette technique pour les trouver de manière fiable.)
Il est donc possible que vous ayez une solution laide à un non-problème, mais cela pourrait également être une solution laide à un problème réel. En cas de doute, laissez-le seul.
Vraiment sûr que de manière réelle, la production, les conditions (consommation de mémoire réaliste, qui déclenche de manière réaliste, etc.), toutes ces méthodes distinctes font vraiment une différence de performance (par opposition à une seule méthode). Cela prendra quelques jours de votre temps, mais peut vous faire économiser des semaines en simplifiant le code que vous travaillez désormais.
De plus, si vous do Découvrez que vous avez besoin de toutes les fonctions, vous voudrez peut-être utiliser Javassist pour générer le code de manière programmatique. Comme d'autres ont souligné, vous pouvez l'automatiser avec Maven .
Pourquoi ne pas inclure une sorte de préprocesseur de modèle\génération de code pour ce système? Une étape de construction supplémentaire personnalisée qui exécute et émet un supplément Java Fichiers source avant de compiler le reste du code. Voici comment inclure les clients Web de WSDL et XSD fonctionne souvent.
Vous aurez bien sûr la maintenance de ce préprocesseur\code générateur, mais vous n'aurez pas la maintenance du code central dupliqué à vous inquiéter.
Depuis la compilation Time Java code est émis, il n'y a pas de pénalité de performance à payer pour un code supplémentaire. Mais vous obtenez une simplification de maintenance en ayant le modèle au lieu de tout le code dupliqué.
Les génériques Java ne donnent aucun avantage de performance dû à l'effacement de type dans la langue, le casting simple est utilisé à sa place.
De ma compréhension des modèles C++, ils sont compilés dans plusieurs fonctions par chaque invocation de modèle. Vous ferez la liquidation avec un code temporel de compilation double dupliqué pour chaque type que vous stockez dans un std :: vecteur par exemple.
No sain Java méthode peut être suffisamment long pour avoir 12 variantes ... et la JITC déteste des méthodes longues - il refuse simplement de les optimiser correctement. J'ai vu un facteur de vitesse de deux par simplement diviser une méthode en deux plus courtes. Peut-être que c'est la voie à suivre.
OTOH Avoir plusieurs copies peut avoir un sens même s'il y avait des idées identiques. Comme chacun d'entre eux s'habitue à différents endroits, ils sont optimisés pour différents cas (le JITC les profile et place ensuite les rares cas sur un chemin exceptionnel.
Je dirais que la génération de code n'est pas grave, en supposant qu'il y a une bonne raison. Les frais généraux sont plutôt faibles, et correctement nommés fichiers conduisent immédiatement à leur source. Certains il y a longtemps comme j'ai généré le code source, j'ai mis // DO NOT EDIT
Sur toutes les lignes ... Je suppose que ça fait suffisamment d'économie.
Il n'y a absolument rien de mal avec le "problème" que vous avez mentionné. De ce que je sais, c'est le type exact de conception et d'approche utilisée par DB Server pour avoir de bonnes performances.
Ils ont de nombreuses méthodes spéciales pour s'assurer qu'ils peuvent maximiser les performances de toutes sortes d'opérations: joindre, sélectionner, agréger, etc ... lorsque certaines conditions s'appliquent.
En bref, la génération de code comme celle que vous pensez est une mauvaise idée. Peut-être devriez-vous regarder ce diagramme pour voir comment une DB résout un problème similaire à celui de la vôtre:
Vous voudrez peut-être vérifier si vous pouvez toujours utiliser des abstractions raisonnables et utilisez E.G. Le modèle de méthode de modèle Pour écrire un code compréhensible pour la fonctionnalité courante et déplacer les différences entre les méthodes dans les "opérations primitives" (selon la description du modèle) dans 12 sous-classes. Cela améliorera puissamment la maintenabilité et la testabilité, et il pourrait en réalité avoir la même performance que le code actuel, car la JVM peut aligner la méthode appelle les opérations primitives après un certain temps. Bien sûr, vous devez vérifier cela dans un test de performance.
Vous pouvez résoudre le problème des méthodes spécialisées en utilisant la langue Scala.
Scala peut des méthodes intégrées, ceci (en combinaison avec une utilisation facile de la fonction d'ordre supérieur) permet d'éviter une duplication de code gratuitement - cela ressemble au principal problème mentionné dans votre réponse.
Mais aussi, Scala a des macros syntaxiques, ce qui permet de faire beaucoup de choses avec le code à la compilation de manière de manière sûre.
Et le problème courant des types primitifs de boxe lorsqu'il est utilisé dans les génériques, il est possible de résoudre également dans Scala, il peut également faire une spécialisation générique pour les primitives pour éviter la boxe automatiquement à l'aide de @specialized
Annotation - Cette chose est intégrée à la langue elle-même. Ainsi, fondamentalement, vous écrirez une méthode générique à Scala, et il fonctionnera avec la vitesse des méthodes spécialisées. Et si vous avez besoin d'arithmétiques génériques rapides, il est facilement faisable en utilisant le "motif" de la typlasse pour injecter les opérations et les valeurs pour différents types numériques.
En outre, Scala est génial de nombreuses autres manières. Et vous n'avez pas à réécrire tout code à Scala, car Scala <-> Java L'interopérabilité est excellente. Assurez-vous simplement d'utiliser SBT (Scala Build Tool) pour la construction du projet.