Il existe un fichier source dans un projet assez volumineux avec plusieurs fonctions extrêmement sensibles aux performances (appelées des millions de fois par seconde). En fait, le responsable précédent a décidé d'écrire 12 copies d'une fonction chacune différant très légèrement, afin de gagner du temps qui serait consacré à la vérification des conditions dans une seule fonction.
Malheureusement, cela signifie que le code est un PITA à maintenir. Je voudrais supprimer tout le code en double et écrire un seul modèle. Cependant, le langage 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 est d'écrire à la place un fichier qui génère les 12 copies de la fonction (un expanseur de modèle à usage unique, pratiquement). Je fournirais bien sûr de nombreuses explications pour lesquelles le fichier doit être généré par programme.
Ma préoccupation est que cela conduirait à la confusion des futurs responsables, et introduirait peut-être des bugs désagréables s'ils oublient de régénérer le fichier après l'avoir modifié, ou (pire encore) s'ils modifient à la place le fichier généré par programme. Malheureusement, à moins 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? Dois-je plutôt:
- Profitez des performances et utilisez une seule fonction maintenable.
- Ajoutez des explications sur les raisons pour lesquelles la fonction doit être dupliquée 12 fois et supportez gracieusement la charge de maintenance.
- Essayez d'utiliser des génériques comme modèles (ils ne fonctionnent probablement pas de cette façon).
- Criez à l'ancien mainteneur pour avoir rendu le code si dépendant des performances d'une seule fonction.
- Autre méthode pour maintenir les performances et la maintenabilité?
PS En raison de la mauvaise conception du projet, le profilage de la fonction est plutôt délicat ... cependant, l'ancien mainteneur m'a convaincu que le niveau de performance est inacceptable. Je suppose qu'il entend par là plus de 5%, bien que ce soit une supposition complète de ma part.
Je devrais peut-être élaborer un peu. Les 12 copies font une tâche très similaire, mais ont des différences infimes. Les différences se trouvent à divers endroits de la fonction, donc malheureusement, il existe de très nombreuses instructions conditionnelles. Il y a effectivement 6 "modes" de fonctionnement, et 2 "paradigmes" de fonctionnement (mots inventés par moi-même). Pour utiliser la fonction, on précise le "mode" et le "paradigme" de fonctionnement. Ce n'est jamais dynamique; chaque morceau de code utilise exactement un mode et un paradigme. Les 12 paires mode-paradigme sont utilisées quelque part dans l'application. Les fonctions sont bien nommées func1 à func12, les nombres pairs représentant le deuxième paradigme et les nombres impairs représentant le premier paradigme.
Je suis conscient que c'est à peu près la pire conception jamais réalisée si la maintenabilité est l'objectif. Mais il semble être "assez rapide", et ce code n'a pas eu besoin de modifications depuis un moment ... Il convient également de noter que la fonction d'origine n'a pas été supprimée (bien qu'il s'agisse d'un code mort pour autant que je sache) , le refactoring serait donc simple.
la source
Makefile
" (ou du système que vous utilisez) et supprimez-le juste après la compilation . De cette façon, ils n'ont tout simplement pas la possibilité de modifier le mauvais fichier source.Réponses:
C'est une très mauvaise situation, vous devez refactoriser ce DÈS QUE POSSIBLE - c'est une dette technique dans le pire - vous ne savez même pas à quel point le code est vraiment important - spéculez seulement qu'il est important.
Quant aux solutions DÈS QUE POSSIBLE:
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 également. Écrivez un fichier avec une extension différente de .java et ajoutez une étape personnalisée qui recherche des fichiers comme celui-ci dans votre source et régénère le .java réel. Vous pouvez également ajouter un énorme avertissement sur le fichier généré automatiquement expliquant de ne pas le modifier.
Avantages par rapport à l'utilisation d'un fichier généré une fois: vos développeurs ne verront pas leurs modifications au travail .java fonctionner. S'ils exécutent réellement le code sur leur machine avant de valider, ils constateront que leurs modifications n'ont aucun effet (hah). Et puis peut-être qu'ils liront l'avertissement. Vous avez absolument raison de ne pas faire confiance à vos coéquipiers et à votre avenir en vous rappelant que ce fichier particulier doit être changé d'une manière différente. Il permet également des tests automatiques comme wel, car JUnit compilera votre programme avant d'exécuter des tests (et régénérera également le fichier)
MODIFIER
À en juger par les commentaires, la réponse est apparue comme si c'était un moyen de faire fonctionner indéfiniment et peut-être OK de déployer sur d'autres parties critiques de la performance de votre projet.
Autrement dit: ce n'est pas le cas.
La charge supplémentaire de créer votre propre mini-langage, d'écrire un générateur de code pour celui-ci et de le maintenir, sans parler de l'enseigner aux futurs responsables, est infernale à long terme. Ce qui précède ne permet qu'une manière plus sûre de gérer le problème pendant que vous travaillez sur une solution à long terme . Ce que cela prendra est au-dessus de moi.
la source
ant clean
ou quel que soit votre équivalent. Pas besoin de mettre un avis de non-responsabilité dans un fichier qui n'est même pas là!L'entretien est-il réel ou vous dérange-t-il simplement? Si cela vous dérange, laissez-le tranquille.
Le problème de performances est-il réel ou le développeur précédent pensait- il seulement que c'était le cas? Les problèmes de performances, le plus souvent, ne sont pas là où ils sont censés être, 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 tranquille.
la source
Assurez-vous vraiment que dans des conditions réelles de production (consommation de mémoire réaliste, qui déclenche le garbage collection 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 gagner des semaines en simplifiant le code avec lequel vous travaillez à partir de maintenant.
En outre, si vous faites découvrir que vous avez besoin de toutes les fonctions, vous pouvez utiliser Javassist pour générer le code programatically. Comme d'autres l'ont souligné, vous pouvez l'automatiser avec Maven .
la source
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 des fichiers source java supplémentaires avant de compiler le reste du code. C'est ainsi que l'inclusion de clients Web hors des fichiers wsdl et xsd fonctionne souvent.
Vous aurez bien sûr la maintenance de ce générateur de préprocesseur \ code, mais vous n'aurez pas à vous soucier de la maintenance du code de base dupliqué.
Étant donné que le code Java au moment de la compilation est émis, il n'y a pas de pénalité de performance à payer pour le code supplémentaire. Mais vous gagnez en simplification de la maintenance en ayant le modèle au lieu de tout le code dupliqué.
Les génériques Java n'apportent aucun avantage en termes de performances en raison de l'effacement des types dans le langage, une simple conversion est utilisée à sa place.
D'après ma compréhension des modèles C ++, ils sont compilés en plusieurs fonctions pour chaque invocation de modèle. Vous vous retrouverez avec un code temporel à mi-compilation en double pour chaque type que vous stockez dans un vecteur std :: par exemple.
la source
Aucune méthode Java sensée ne peut être assez longue pour avoir 12 variantes ... et JITC déteste les méthodes longues - il refuse simplement de les optimiser correctement. J'ai vu un facteur d'accélération de deux en divisant simplement une méthode en deux plus courtes. C'est peut-être la voie à suivre.
OTOH ayant plusieurs copies peut avoir du sens même si elles étaient identiques. Au fur et à mesure que chacun d'eux est utilisé à différents endroits, ils sont optimisés pour différents cas (le JITC les profile puis place les cas rares sur un chemin exceptionnel.
Je dirais que générer du code n'est pas un gros problème, en supposant qu'il y a une bonne raison. La surcharge est plutôt faible et les fichiers correctement nommés mènent immédiatement à leur source. Il y a longtemps que j'ai généré du code source, j'ai mis
// DO NOT EDIT
sur chaque ligne ... Je suppose que c'est assez d'économiser.la source
Il n'y a absolument rien de mal avec le «problème» que vous avez mentionné. D'après ce que je sais, c'est le type exact de conception et d'approche utilisé par le serveur DB pour avoir de bonnes performances.
Ils ont beaucoup de méthodes spéciales pour s'assurer qu'ils peuvent maximiser les performances pour 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. Vous devriez peut-être regarder ce diagramme pour voir comment une base de données résout un problème similaire au vôtre:
la source
Vous voudrez peut-être vérifier si vous pouvez toujours utiliser des abstractions sensées et utiliser par exemple le modèle de méthode de modèle pour écrire du code compréhensible pour la fonctionnalité commune 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 considérablement la maintenabilité et la testabilité, et il pourrait en fait avoir les mêmes performances que le code actuel, car la JVM peut incorporer les appels de méthode pour les opérations primitives après un certain temps. Bien sûr, vous devez vérifier cela dans un test de performance.
la source
Vous pouvez résoudre le problème des méthodes spécialisées en utilisant le langage Scala.
Scala peut intégrer des méthodes, cela (en combinaison avec une utilisation facile des fonctions d'ordre supérieur) permet d'éviter la 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 du code au moment de la compilation de manière sécurisée.
Et le problème commun des types de primitives de boxe lorsqu'ils sont utilisés dans des génériques est également possible à résoudre dans Scala: il peut faire une spécialisation générique pour les primitives pour éviter la boxe automatiquement en utilisant l'
@specialized
annotation - cette chose est intégrée dans le langage lui-même. Donc, en gros, vous écrirez une méthode générique dans Scala, et elle fonctionnera à la vitesse des méthodes spécialisées. Et si vous avez également besoin d'arithmétiques génériques rapides, cela est facilement réalisable en utilisant le "modèle" Typeclass pour injecter les opérations et les valeurs pour différents types numériques.En outre, Scala est génial à bien d'autres égards. Et vous n'avez pas à réécrire tout le code dans Scala, car l'interopérabilité Java Scala <-> est excellente. Assurez-vous simplement d'utiliser SBT (scala build tool) pour construire le projet.
la source
Si la fonction en question est volumineuse, transformer les bits "mode / paradigme" en une interface, puis passer un objet qui implémente cette interface en tant que paramètre à la fonction peut fonctionner. Le GoF appelle cela le modèle de «stratégie», iirc. Si la fonction est petite, les frais généraux accrus peuvent être importants ... quelqu'un a-t-il déjà mentionné le profilage? ... davantage de personnes devraient mentionner le profilage.
la source
Quel âge a le programme? Le matériel le plus récent supprimerait probablement le goulot d'étranglement et vous pourriez passer à une version plus maintenable. Mais il doit y avoir une raison pour la maintenance, donc à moins que votre tâche ne soit d'améliorer la base de code, laissez-la telle qu'elle fonctionne.
la source