Existe-t-il un nom pour le modèle (anti) de paramètres de transmission qui ne sera utilisé que sur plusieurs niveaux au plus profond de la chaîne d’appel?

209

J'essayais de trouver des alternatives à l'utilisation de variable globale dans certains codes hérités. Mais cette question ne concerne pas les alternatives techniques, je suis principalement préoccupé par la terminologie .

La solution évidente consiste à transmettre un paramètre à la fonction au lieu d'utiliser un paramètre global. Dans cette ancienne base de code, cela signifierait que je dois changer toutes les fonctions de la longue chaîne d'appels entre le point où la valeur sera éventuellement utilisée et la fonction qui reçoit le paramètre en premier.

higherlevel(newParam)->level1(newParam)->level2(newParam)->level3(newParam)

newParamétait auparavant une variable globale dans mon exemple, mais cela aurait pu être une valeur précédemment codée en dur. Le fait est que maintenant la valeur de newParam est obtenue à higherlevel()et doit "voyager" jusqu’à level3().

Je me demandais s'il y avait un nom pour ce type de situation / modèle où vous devez ajouter un paramètre à de nombreuses fonctions qui "transmettent" simplement la valeur non modifiée.

J'espère que l'utilisation de la terminologie appropriée me permettra de trouver plus de ressources sur les solutions de restructuration et de décrire cette situation à mes collègues.

ecerulm
la source
94
C'est une amélioration par rapport à l'utilisation de variables globales. Il indique clairement de quel état dépend chaque fonction (et constitue un pas en avant vers les fonctions pures). Je l'ai entendu appeler "enfiler" un paramètre, mais je ne sais pas à quel point cette terminologie est courante.
Gardenhead
8
C'est un peu trop large pour avoir une réponse spécifique. A ce niveau, j'appellerais cela simplement du "codage".
Machado
38
Je pense que le "problème" est juste la simplicité. Ceci est essentiellement une injection de dépendance. J'imagine qu'il pourrait exister des mécanismes qui injecteraient automatiquement la dépendance dans la chaîne, si un membre plus profondément imbriqué l'a, sans gonfler les listes de paramètres des fonctions. Peut-être qu'examiner des stratégies d'injection de dépendance avec différents niveaux de sophistication pourrait conduire à la terminologie que vous recherchez, le cas échéant.
null
7
Bien que j'apprécie la discussion sur le fait de savoir si c'est un bon modèle / anti-modèle / concept / solution, ce que je voulais vraiment savoir, c'est s'il y a un nom pour cela.
ecerulm
3
J'ai aussi entendu parler du filetage le plus souvent, mais également de la plomberie , comme abaisser un fil à plomb tout au long de la pile d'appels.
wchargin

Réponses:

202

Les données elles-mêmes sont appelées "données de tramp" . C'est une "odeur de code", indiquant qu'un morceau de code communique avec un autre morceau de code à distance, par le biais d'intermédiaires.

  • Augmente la rigidité du code, en particulier dans la chaîne d'appels. Vous êtes beaucoup plus limité dans la manière dont vous refactorisez une méthode dans la chaîne d'appels.
  • Distribue la connaissance sur les données / méthodes / architecture à des endroits qui s'en moquent le moins du monde. Si vous devez déclarer les données en cours de transmission et si la déclaration nécessite une nouvelle importation, vous avez pollué l'espace de noms.

Le refactoring pour supprimer les variables globales est difficile, et les données de tramp sont une méthode pour le faire, et souvent le moyen le moins coûteux. Cela a des coûts.

BobDalgleish
la source
73
En recherchant "tramp data", j'ai pu trouver le livre "Code Complete" sur mon abonnement Safari. Le livre contient une section intitulée "Raisons d'utiliser des données globales" et l'une des raisons est "L'utilisation de données globales peut éliminer les données parasites". :) Je pense que les "données de tramp" me permettront de trouver plus de littérature sur le traitement des globals. Merci!
ecerulm
9
@ JimmyJames, ces fonctions font des choses bien sûr. Juste pas avec ce nouveau paramètre spécifique qui était auparavant juste un global.
ecerulm
174
En 20 ans de programmation, je n'ai littéralement jamais entendu ce terme auparavant et il n'aurait pas été immédiatement évident de comprendre ce que cela voulait dire. Je ne me plains pas de la réponse, je suggère simplement que le terme n'est pas largement utilisé / connu. Peut-être que c'est juste moi.
Derek Elkins
6
Certaines données globales vont bien. Au lieu de l'appeler "données globales", vous pouvez l'appeler "environnement" - parce que c'est ce que c'est. L’environnement peut inclure, par exemple, un chemin de chaîne pour appdata (sur Windows) ou, dans mon projet actuel, l’ensemble complet de brosses, stylos, polices, etc., utilisés par tous les composants GDI +.
Robinson
7
@ Robinson Pas tout à fait. Par exemple, voulez-vous vraiment que votre code d'écriture d'image touche% AppData% ou préférez-vous qu'il prenne un argument pour savoir où écrire? C'est la différence entre un état global et un argument. "Environnement" peut tout aussi bien être une dépendance injectée, uniquement présente pour ceux qui sont responsables des interactions avec l'environnement. Les brosses GDI +, etc., sont plus raisonnables, mais il s’agit plutôt d’une gestion de ressources dans un environnement qui ne peut pas le faire pour vous - il s’agit plutôt d’une déficience des API sous-jacentes et / ou de votre langage / bibliothèques / runtime.
Luaan
102

Je ne pense pas que cela, en soi, est un anti-modèle. Je pense que le problème est que vous envisagez les fonctions comme une chaîne alors qu'en réalité, vous devriez considérer chacune d'elles comme une boîte noire indépendante ( REMARQUE : les méthodes récursives sont une exception notable à ce conseil.)

Par exemple, supposons que je dois calculer le nombre de jours entre deux dates de calendrier pour créer une fonction:

int daysBetween(Day a, Day b)

Pour ce faire, je crée ensuite une nouvelle fonction:

int daysSinceEpoch(Day day)

Alors ma première fonction devient simplement:

int daysBetween(Day a, Day b)
{
    return daysSinceEpoch(b) - daysSinceEpoch(a);
}

Il n'y a rien d'anti-pattern à ce sujet. Les paramètres de la méthode daysBetween sont transmis à une autre méthode et ne sont jamais autrement référencés dans la méthode, mais ils sont toujours nécessaires pour que cette méthode fasse ce qu'elle doit faire.

Ce que je recommanderais, c'est d'examiner chaque fonction et de commencer par quelques questions:

  • Cette fonction a-t-elle un objectif clair et ciblé ou s'agit-il d'une méthode consistant à "faire des choses"? Habituellement, le nom de la fonction est utile ici et s’il contient des éléments qui ne sont pas décrits par le nom, c’est un drapeau rouge.
  • Y a-t-il trop de paramètres? Parfois, une méthode peut légitimement nécessiter beaucoup de travail, mais avoir autant de paramètres rend son utilisation ou sa compréhension fastidieuse.

Si vous examinez un fouillis de code sans un seul objectif intégré dans une méthode, commencez par le démêler. Cela peut être fastidieux. Commencez par les éléments les plus faciles à extraire et passez à une méthode distincte, puis répétez l'opération jusqu'à obtenir un résultat cohérent.

Si vous avez trop de paramètres, envisagez le refactoring Méthode en objet .

JimmyJames
la source
2
Eh bien, je ne voulais pas être controversé avec les (anti-). Mais je me demande toujours s'il existe un nom pour la "situation" de devoir mettre à jour de nombreuses signatures de fonctions. Je suppose que c'est plus une "odeur de code" qu'un antipattern. Cela me dit qu'il y a quelque chose à corriger dans cet ancien code, si je dois mettre à jour la signature à 6 fonctions pour permettre l'élimination d'un global. Mais je pense que passer des paramètres est généralement acceptable, et j’apprécie les conseils sur la manière de traiter le problème sous-jacent.
ecerulm
4
@ ecerulm Je n'en connais pas, mais je dirai que mon expérience m'a appris que la conversion des globals en paramètres est absolument la bonne façon de commencer à les éliminer. Cela élimine l'état partagé afin que vous puissiez plus loin refactor. J'imagine qu'il y a plus de problèmes avec ce code, mais votre description ne vous permet pas de savoir en quoi ils consistent.
JimmyJames
2
Je suis généralement cette approche aussi, et probablement dans ce cas aussi. Je voulais juste améliorer mon vocabulaire / ma terminologie à ce sujet afin de pouvoir approfondir mes recherches dans ce domaine et de poser de meilleures questions plus ciblées à l’avenir.
ecerulm
3
@ecerulm Je ne pense pas qu'il existe un seul nom pour cela. C'est comme un symptôme qui est commun à beaucoup de maladies et à des affections autres que la maladie, par exemple la «bouche sèche». Si vous étoffez la description de la structure du code, cela pourrait indiquer quelque chose de spécifique.
JimmyJames
@ecerulm Il vous dit qu'il y a quelque chose à réparer - maintenant, il est beaucoup plus évident de corriger quelque chose que s'il s'agissait d'une variable globale.
Immibis
61

BobDalgleish a déjà noté que ce modèle (anti) est appelé " données de tramp ".

D'après mon expérience, la cause la plus fréquente de données de tramp excessives est la présence d'un ensemble de variables d'état liées qui doivent être réellement encapsulées dans un objet ou une structure de données. Parfois, il peut même être nécessaire d'imbriquer un tas d'objets pour organiser correctement les données.

Pour un exemple simple, considérons un jeu qui a un caractère de joueur personnalisable, avec des propriétés telles que playerName, playerEyeColoretc. Bien sûr, le joueur a également une position physique sur la carte du jeu, ainsi que diverses autres propriétés telles que, par exemple, le niveau de santé actuel et maximal, etc.

Lors d'une première itération d'un tel jeu, il peut s'avérer un choix parfaitement raisonnable de transformer toutes ces propriétés en variables globales - après tout, il n'y a qu'un seul joueur et presque tout dans le jeu implique le joueur d'une manière ou d'une autre. Donc, votre état global peut contenir des variables telles que:

playerName = "Bob"
playerEyeColor = GREEN
playerXPosition = -8
playerYPosition = 136
playerHealth = 100
playerMaxHealth = 100

Mais à un moment donné, vous constaterez peut-être que vous devez modifier cette conception, peut-être parce que vous souhaitez ajouter un mode multijoueur au jeu. Dans un premier temps, vous pouvez essayer de rendre toutes ces variables locales et de les transmettre aux fonctions qui en ont besoin. Cependant, vous constaterez peut-être qu'une action particulière de votre jeu peut impliquer une chaîne d'appels de fonctions, par exemple:

mainGameLoop()
 -> processInputEvent()
     -> doPlayerAction()
         -> movePlayer()
             -> checkCollision()
                 -> interactWithNPC()
                     -> interactWithShopkeeper()

... et la interactWithShopkeeper()fonction demande au commerçant d’adresser son nom au joueur, de sorte que vous devez maintenant soudainement transmettre des playerNamedonnées clandestines à travers toutes ces fonctions. Et, bien sûr, si le commerçant pense que les joueurs aux yeux bleus sont naïfs et qu'ils leur demanderont des prix plus élevés, vous devrez passer playerEyeColorpar toute la chaîne de fonctions, etc.

La solution appropriée , dans ce cas, consiste bien entendu à définir un objet de joueur qui encapsule le nom, la couleur des yeux, la position, la santé et toutes autres propriétés du personnage de joueur. De cette façon, il vous suffit de transmettre cet objet unique à toutes les fonctions qui impliquent le joueur.

En outre, plusieurs des fonctions ci-dessus pourraient naturellement être transformées en méthodes de cet objet joueur, ce qui leur donnerait automatiquement accès aux propriétés du joueur. D'une certaine manière, il ne s'agit que d'un simple sucre syntaxique, car l'appel d'une méthode sur un objet transmet de toute façon l'instance d'objet en tant que paramètre masqué à la méthode, mais rend le code plus clair et plus naturel s'il est utilisé correctement.

Bien sûr, un jeu typique aurait un état beaucoup plus "global" que celui du joueur; Par exemple, vous avez presque certainement une sorte de carte sur laquelle se déroule le jeu, ainsi qu'une liste des personnages non-joueurs se déplaçant sur la carte, et peut-être des objets placés dessus, etc. Vous pouvez également passer tous ceux qui se trouvent autour de vous comme des objets tramp, mais cela encombrerait encore les arguments de votre méthode.

Au lieu de cela, la solution consiste à laisser les objets stocker des références à tout autre objet avec lequel ils ont des relations permanentes ou temporaires. Ainsi, par exemple, l’objet joueur (et probablement aussi tout objet NPC) devrait probablement stocker une référence à l’objet "world game", qui aurait une référence au niveau / map en cours, de sorte qu’une méthode telle player.moveTo(x, y)que être explicitement donné la carte en tant que paramètre.

De même, si notre personnage joueur avait, par exemple, un chien qui les suivait, nous regrouperions naturellement toutes les variables d'état décrivant le chien dans un seul objet et donnerions à l'objet joueur une référence au chien (afin que le joueur puisse , appelez le chien par son nom) et vice-versa (pour que le chien sache où se trouve le joueur). Et, bien sûr, nous voudrions probablement que le joueur et les objets chien soient les deux sous-classes d’un objet "acteur" plus générique, afin de pouvoir réutiliser le même code pour déplacer les deux sur la carte, par exemple.

Ps. Même si j'ai utilisé un jeu comme exemple, il existe d'autres types de programmes dans lesquels de tels problèmes se posent également. D'après mon expérience, toutefois, le problème sous-jacent a tendance à être toujours le même: vous avez un ensemble de variables distinctes (qu'elles soient locales ou globales) qui souhaitent réellement être regroupées dans un ou plusieurs objets liés. Que la "donnée tampon" introduite dans vos fonctions se compose de paramètres d'option "globaux" ou de requêtes de base de données en cache ou de vecteurs d'état dans une simulation numérique, la solution consiste invariablement à identifier le contexte naturel auquel les données appartiennent et à en faire un objet. (ou quel que soit l'équivalent le plus proche dans la langue de votre choix).

Ilmari Karonen
la source
1
Cette réponse fournit des solutions à une classe de problèmes pouvant exister. Il peut arriver que des globals soient utilisés, ce qui indiquerait une solution différente. Je prends un problème avec l’idée que faire des méthodes une partie de la classe player équivaut à passer des objets à des méthodes. Cela ignore le polymorphisme qui n'est pas facilement répliqué de cette façon. Par exemple, si je veux créer différents types d’acteurs ayant des règles différentes en matière de mouvements et différents types de propriétés, le simple fait de passer ces objets à une méthode d’implémentation nécessitera beaucoup de logique conditionnelle.
JimmyJames
6
@ JimmyJames: Votre point sur le polymorphisme est bon, et j'ai pensé le faire moi-même, mais je l'ai laissé de côté pour que la réponse ne soit pas plus longue. Ce que j’essayais (peut-être mal) de dire, c’est que, bien qu’il s’agisse du flux de données, il existe peu de différences entre foo.method(bar, baz)et method(foo, bar, baz), il existe d’autres raisons (notamment le polymorphisme, l’encapsulation, la localisation, etc.) pour préférer le premier.
Ilmari Karonen
@IlmariKaronen: c'est aussi l'avantage évident de pouvoir protéger à l'avenir les prototypes de fonctions de modifications / ajouts / suppressions / refactorings à venir dans les objets (par exemple, playerAge). Cela seul est inestimable.
smci
34

Je ne connais pas de nom spécifique pour cela, mais je suppose qu'il est utile de mentionner que le problème que vous décrivez est simplement le problème de trouver le meilleur compromis pour la portée d'un tel paramètre:

  • en tant que variable globale, la portée est trop grande lorsque le programme atteint une certaine taille

  • en tant que paramètre purement local, la portée peut être trop petite, car elle entraîne de nombreuses listes de paramètres répétitives dans les chaînes d'appels

  • en tant que compromis, vous pouvez souvent faire de ce paramètre une variable membre dans une ou plusieurs classes, et c'est ce que j'appellerais simplement la conception de classe appropriée .

Doc Brown
la source
10
+1 pour une conception de classe appropriée. Cela ressemble à un problème classique en attente d'une solution OO.
l0b0
21

Je crois que le modèle que vous décrivez est exactement l' injection de dépendance . Plusieurs intervenants ont fait valoir qu'il s'agissait d'un modèle , et non d'un anti-modèle , et j'aurais tendance à être d'accord.

Je suis également d'accord avec la réponse de @ JimmyJames, dans laquelle il affirme qu'il est judicieux de traiter chaque fonction comme une boîte noire prenant toutes ses entrées en tant que paramètres explicites. Autrement dit, si vous écrivez une fonction qui fait un sandwich au beurre d’arachide et à la gelée, vous pouvez l’écrire ainsi:

Sandwich make_sandwich() {
    PeanutButter pb = get_peanut_butter();
    Jelly j = get_jelly();
    return pb + j;
}
extern PhysicalRefrigerator g_refrigerator;
PeanutButter get_peanut_butter() {
    return g_refrigerator.get("peanut butter");
}
Jelly get_jelly() {
    return g_refrigerator.get("jelly");
}

mais il serait préférable d'appliquer l'injection de dépendance et de l'écrire comme suit:

Sandwich make_sandwich(Refrigerator& r) {
    PeanutButter pb = get_peanut_butter(r);
    Jelly j = get_jelly(r);
    return pb + j;
}
PeanutButter get_peanut_butter(Refrigerator& r) {
    return r.get("peanut butter");
}
Jelly get_jelly(Refrigerator& r) {
    return r.get("jelly");
}

Vous avez maintenant une fonction qui documente clairement toutes ses dépendances dans sa signature, ce qui est excellent pour la lisibilité. Après tout, il est vrai que pour pouvoir make_sandwichaccéder à un Refrigerator; ainsi, l'ancienne signature de fonction était fondamentalement hypocrite en ne prenant pas le réfrigérateur comme partie intégrante de ses entrées.

En prime, si vous respectez la hiérarchie de votre classe, évitez les découpages en tranches, etc., vous pouvez même tester la make_sandwichfonction par unité en transmettant un MockRefrigerator! (Vous devrez peut-être effectuer des tests unitaires de cette manière car votre environnement de test unitaire risque de ne pas avoir accès à aucun PhysicalRefrigerator.

Je comprends que toutes les utilisations de l’injection de dépendance n’exigent pas l’ installation d’un paramètre similaire dans de nombreux niveaux de la pile d’appel. Je ne réponds donc pas exactement à la question que vous avez posée ... mais si vous souhaitez en savoir plus à ce sujet, "injection de dépendance" est certainement un mot clé pour vous.

Quuxplusone
la source
10
C'est très clairement un motif anti . Il n'y a absolument aucun appel pour un réfrigérateur à passer. Maintenant, passer à un ingrédient générique IngredientSource peut fonctionner, mais si vous obteniez le pain dans la corbeille à pain, le thon dans le garde-manger, le fromage dans le réfrigérateur ... en injectant une dépendance sur la source d'ingrédients dans l'opération sans lien ingrédients dans un sandwich, vous avez violé la séparation des préoccupations et fait une odeur de code.
Dewi Morgan
8
@DewiMorgan: Évidemment, vous pourriez refactoriser davantage pour généraliser le Refrigeratordans un IngredientSource, voire même généraliser la notion de "sandwich" template<typename... Fillings> StackedElementConstruction<Fillings...> make_sandwich(ElementSource&); c'est ce qu'on appelle la "programmation générique" et elle est raisonnablement puissante, mais elle est sûrement bien plus mystérieuse que le PO veut vraiment entrer pour le moment. N'hésitez pas à poser une nouvelle question sur le niveau d'abstraction approprié pour les programmes en sandwich. ;)
Quuxplusone
11
Ne vous y trompez pas, l'utilisateur non privilégié ne devrait pas avoir accès à make_sandwich().
dotancohen
2
@Dewi - Lien
Gavin Lock
19
L'erreur la plus grave de votre code est que vous gardez le beurre d'arachide au réfrigérateur.
Malvolio
15

C'est à peu près la définition classique du couplage , un module ayant une dépendance qui en affecte un autre, et qui crée un effet d'entraînement une fois modifié. Les autres commentaires et réponses indiquent correctement qu'il s'agit d'une amélioration par rapport au global, car le couplage est désormais plus explicite et plus facile à voir par le programmeur, au lieu d'être subversif. Cela ne signifie pas que cela ne devrait pas être corrigé. Vous devriez être capable de refactoriser pour supprimer ou réduire le couplage, même si cela peut être douloureux.

Karl Bielefeldt
la source
3
Si level3()besoin est newParam, le couplage est sûr, mais différentes parties du code doivent communiquer les unes avec les autres. Je n'appellerais pas nécessairement un paramètre de fonction mauvais couplage si cette fonction utilise le paramètre. Je pense que l’aspect problématique de la chaîne est le couplage supplémentaire introduit pour level1()et level2()qui n’a aucune utilité, newParamsauf pour le transmettre. Bonne réponse, +1 pour le couplage.
null
6
@null S'ils n'en avaient vraiment pas besoin, ils pourraient créer une valeur au lieu de recevoir de leur appelant.
Random832
3

Bien que cette réponse ne réponde pas directement à votre question, je pense que je m'en voudrais de la laisser passer sans mentionner comment l’améliorer (puisque comme vous le dites, cela peut être un anti-modèle). J'espère que vous et les autres lecteurs pourrez tirer parti de ce commentaire supplémentaire sur la manière d'éviter les "données de tramp" (comme l'a si gentiment nommé Bob Dalgleish pour nous).

Je suis d'accord avec les réponses qui suggèrent de faire quelque chose de plus OO pour éviter ce problème. Cependant, une autre façon de réduire considérablement le passage d'arguments sans passer simplement à " juste passer une classe où vous passiez beaucoup d'arguments! " Est de refactoriser afin que certaines étapes de votre processus se déroulent au niveau supérieur un. Par exemple, voici quelques exemples de code avant :

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   FilterAndReportStuff(stuffs, desiredName);
}

public void FilterAndReportStuff(IEnumerable<Stuff> stuffs, string desiredName) {
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   ReportStuff(stuffs.Filter(filter));
}

public void ReportStuff(IEnumerable<Stuff> stuffs) {
   stuffs.Report();
}

Notez que cela devient encore pire avec le plus de choses à faire ReportStuff. Vous devrez peut-être transmettre l’instance du rapporteur que vous souhaitez utiliser. Et toutes sortes de dépendances qui doivent être transférées fonctionnent d'une fonction imbriquée.

Ma suggestion est de tirer tout cela à un niveau supérieur, où la connaissance des étapes nécessite de vivre dans une seule méthode au lieu d'être répartie sur une chaîne d'appels de méthode. Bien sûr, cela serait plus compliqué dans le code réel, mais cela vous donne une idée:

public void PerformReporting(StuffRepository repo, string desiredName) {
   var stuffs = repo.GetStuff(DateTime.Now());
   var filter = CreateStuffFilter(FilterTypes.Name, desiredName);
   var filteredStuffs = stuffs.Filter(filter)
   filteredStuffs.Report();
}

Notez que la grande différence ici est que vous n'avez pas à passer les dépendances par une longue chaîne. Même si vous nivelez à un niveau, mais à quelques niveaux, si ces niveaux obtiennent également un «nivellement» afin que le processus soit considéré comme une série d'étapes à ce niveau, vous aurez fait une amélioration.

Même si cela reste une procédure et que rien n’a encore été transformé en objet, c’est un bon pas en avant pour décider quel type d’encapsulation vous pouvez obtenir en transformant quelque chose en classe. Les appels de méthode profondément chaînés dans le scénario précédent masquent les détails de ce qui se passe réellement et peuvent rendre le code très difficile à comprendre. Bien que vous puissiez en faire trop et que vous finissiez par faire savoir au code de niveau supérieur ce qu’il ne devrait pas faire, ou à créer une méthode qui fait trop de choses, violant ainsi le principe de responsabilité unique, j’ai généralement constaté qu’aplatir un peu les choses dans la clarté et en faisant des changements progressifs vers un meilleur code.

Notez que pendant que vous faites tout cela, vous devriez envisager la testabilité. Les appels de méthode en chaîne rendent en réalité les tests unitaires plus difficiles, car vous ne disposez pas des bons point d'entrée et de sortie dans l'assemblage pour la tranche que vous souhaitez tester. Notez qu'avec cet aplatissement, puisque vos méthodes ne prennent plus autant de dépendances, elles sont plus faciles à tester et ne nécessitent pas autant de simulacres!

J'ai récemment essayé d'ajouter des tests unitaires à une classe (que je n'ai pas écrite) qui prenait quelque chose comme 17 dépendances, dont il fallait toutes se moquer! Je n'ai pas encore tout réglé, mais j'ai divisé la classe en trois classes, chacune correspondant à l'un des noms distincts qui la concernaient, et j'ai ramené la liste des dépendances à 12 pour la pire et à environ 8 pour la première. meilleur.

La testabilité vous obligera à écrire un meilleur code. Vous devriez écrire des tests unitaires, car vous constaterez que cela vous fait penser à votre code différemment et que vous écrivez un meilleur code dès le départ, quel que soit le nombre de bugs que vous auriez pu avoir avant l'écriture des tests unitaires.

ErikE
la source
2

Vous ne violez pas littéralement la loi de Demeter, mais votre problème est similaire à celui-ci à certains égards. Puisque votre question a pour but de trouver des ressources, je vous suggère de lire à propos de Law of Demeter et de voir dans quelle mesure ces conseils s’appliquent à votre situation.

la nourriture pour chat
la source
1
Un peu faible sur les détails, ce qui explique probablement les votes négatifs. Pourtant, dans l’esprit, cette réponse est parfaitement exacte: OP devrait se renseigner sur la loi de Demeter - c’est le terme qui convient.
Konrad Rudolph Le
4
FWIW, je ne pense pas que la loi de Demeter (ou "moindre privilège") soit pertinente du tout. Le cas de l'OP est le cas où sa fonction ne pourrait pas faire son travail si elle ne disposait pas des données de tramp (parce que le type suivant en a besoin, parce que le type suivant en a besoin, etc.). Les privilèges moindres / Loi de Demeter ne sont pertinents que si le paramètre est vraiment inutilisé , et dans ce cas, le correctif est évident: supprimez le paramètre inutilisé!
Quuxplusone
2
La situation de cette question n'a absolument rien à voir avec la loi de Demeter ... Il existe une similitude superficielle en ce qui concerne la chaîne d'appels de méthodes, mais sinon, c'est très différent.
Eric King
@Quuxplusone Possible, mais dans ce cas, la description est assez déroutante car les appels chaînés n'ont pas vraiment de sens dans ce scénario: ils devraient plutôt être imbriqués .
Konrad Rudolph
1
Le problème est très similaire aux violations de LoD, car la refactorisation habituelle suggérée pour traiter les violations de LoD consiste à introduire des données de tramp. IMHO, c’est un bon point de départ pour réduire le couplage mais ce n’est pas suffisant.
Jørgen Fogh
1

Dans certains cas, il est préférable (en termes d’efficacité, de maintenabilité et de facilité d’implémentation) d’avoir certaines variables globales plutôt que de faire passer tout le temps (par exemple, il faut environ 15 variables qui doivent persister). Il est donc logique de trouver un langage de programmation qui prenne mieux en charge la portée (en tant que variables statiques privées de C ++) afin d’atténuer les dégâts potentiels (de l’espace de noms et de la falsification). Bien sûr, ceci est juste une connaissance commune.

Cependant, l'approche indiquée par le PO est très utile si l'on fait de la programmation fonctionnelle.

Kozner
la source
0

Il n'y a aucun anti-modèle ici, parce que l'appelant ne connaît pas tous ces niveaux ci-dessous et s'en moque.

Quelqu'un appelle HigherLevel (params) et s'attend à ce que HigherLevel fasse son travail. Ce que fait plus haut niveau avec params ne fait pas l'affaire des appelants. higherLevel traite le problème de la meilleure façon possible, dans ce cas en passant les paramètres à level1 (paramètres). C'est absolument ok.

Vous voyez une chaîne d'appels - mais il n'y en a pas. Il y a une fonction au sommet qui fait son travail de la meilleure façon possible. Et il y a d'autres fonctions. Chaque fonction peut être remplacée à tout moment.

gnasher729
la source