La modification d'un paramètre entrant est-elle un antipattern? [fermé]

60

Je programme en Java et je fabrique toujours des convertisseurs comme ceci:

public OtherObject MyObject2OtherObject(MyObject mo){
    ... Do the conversion
    return otherObject;
}

Sur le nouveau lieu de travail, la tendance est la suivante:

public void MyObject2OtherObject(MyObject mo, OtherObject oo){
    ... Do the conversion
}

Pour moi, cela sent un peu mauvais, car je me suis habitué à ne pas changer les paramètres entrants. Cette modification de paramètre entrante est-elle un antipattern ou est-ce correct? At-il de sérieux inconvénients?

CsBalazsHongrie
la source
4
La bonne réponse à cela sera spécifique à la langue, car ceux où les paramètres de passage par valeur les transforment effectivement en variables locales.
Blrfl
1
Le second motif est parfois utilisé comme mesure d'efficacité pour réutiliser des objets. En supposant qu'il s'agisse d' ooun objet transmis à la méthode, et non d'un pointeur défini sur un nouvel objet. est-ce le cas ici? Si c'est en Java, c'est probablement le cas, si son C ++ n'est peut
Richard Tingle
3
Cela ressemble à une optimisation prématurée, aye. Je vous recommande d'utiliser le premier formulaire en général, mais si vous rencontrez un goulet d'étranglement, vous pouvez utiliser le second formulaire avec un commentaire pour le justifier . Un commentaire vous empêcherait, ainsi que d'autres personnes, de consulter ce code et de faire en sorte que la même question réapparaisse.
Vif
2
Le second modèle est utile lorsque la fonction renvoie également une valeur, par exemple, succès / échec. Mais il n'y a rien de mal à cela. Cela dépend vraiment de l'endroit où vous voulez créer l'objet.
GrandmasterB
12
Je ne nommerais pas les fonctions implémentant ces deux modèles différents de la même manière.
Casey

Réponses:

70

Ce n'est pas un anti-modèle, c'est une mauvaise pratique.

La différence entre un anti-modèle et une simple mauvaise pratique est la suivante: définition anti-modèle .

Selon le nouveau code de Oncle Bob, le nouveau style de travail que vous montrez est une mauvaise pratique , vestigiale ou pré-OOP.

Les arguments sont naturellement interprétés comme des entrées dans une fonction.

Tout ce qui vous oblige à vérifier la signature de la fonction équivaut à une double-prise. C'est une rupture cognitive et devrait être évitée. Dans les jours précédents la programmation orientée objet, il était parfois nécessaire de disposer d'arguments de sortie. Cependant, une grande partie du besoin d'arguments de sortie disparaît dans les langages OO

Tulains Córdova
la source
7
Je ne vois pas pourquoi la définition que vous avez liée empêche que cela soit considéré comme un anti-modèle.
Samuel Edwin Ward
7
Je suppose que c’est parce que "nous économisons le CPU / la mémoire en ne créant pas de nouvel objet, nous devrions plutôt recycler celui que nous avons et le passer comme argument" est si manifestement faux pour tout le monde ayant une expérience sérieuse de POO que cela pourrait ne constitue jamais un motif - il n'y a donc aucune raison de considérer cela comme un anti-motif au sens de la définition ...
vaxquis
1
Qu'en est-il du cas lorsqu'un paramètre d'entrée correspond à une vaste collection d'objets à modifier?
Steve Chambers
Bien que je préfère aussi l’option 1, qu’en OtherObjectest-il une interface? Seul l'appelant (espérons-le) sait quel type de béton il veut.
user949300
@SteveChambers Je pense que, dans ce cas, la conception de la classe ou de la méthode est mauvaise et doit être repensée pour l'éviter.
piotr.wittchen le
16

Citant le célèbre livre de Robert C. Martin "Clean Code":

Les arguments de sortie doivent être évités

Les fonctions doivent avoir un petit nombre d'arguments

Le second modèle enfreint les deux règles, en particulier l'argument "argument de sortie". En ce sens, il est pire que le premier schéma.

Claasz
la source
1
Je dirais que cela viole le premier, beaucoup d’arguments font référence à un code compliqué, mais je pense que deux arguments sont tout à fait acceptables. Je pense que cette règle sur le code propre signifie que si vous avez besoin de 5 ou 6 données entrantes, vous voulez en faire trop avec une seule méthode. Vous devez donc refactoriser pour augmenter la lisibilité du code et la portée de la POO.
CsBalazsHungary
2
Quoi qu'il en soit, je soupçonne qu'il ne s'agit pas d'une loi stricte, mais d'une suggestion forte. Je sais que cela ne fonctionnera pas avec les types primitifs. S'il s'agit d'un schéma répandu dans un projet, un débutant obtiendrait des idées pour passer un int et s'attendrait à ce qu'il soit modifié comme Objets.
CsBalazsHungary
27
Quel que soit le degré de pureté du code propre, je ne pense pas que ce soit une réponse valable sans expliquer pourquoi il faut éviter de le faire. Ce ne sont pas des commandements qui descendent sur une tablette de pierre, la compréhension est importante. Cette réponse pourrait être améliorée en fournissant un résumé du raisonnement dans le livre et une référence de chapitre
Daenyth
3
Bon sang, si un gars l'a écrit dans un livre, ce doit être un bon conseil dans toutes les situations imaginables. Pas besoin de penser.
Casey
2
@ emodendroket Mais mec, c'est le gars!
Pierre Arlaud le
15

Le deuxième idiome peut être plus rapide, car l'appelant peut réutiliser une variable sur une longue boucle, au lieu de chaque itération créant une nouvelle instance.

Je ne l'emploierais généralement pas, mais par exemple. la programmation de jeu a sa place. Par exemple, le nombre d'opérations Vector3f de JavaMonkey permet de transmettre des instances qui doivent être modifiées et renvoyées comme résultat.

utilisateur470365
la source
12
Eh bien, je suis d’accord pour dire si c’est le goulot d’étranglement, mais là où j’ai travaillé, les goulots d’étranglement sont généralement des algorithmes peu efficaces, sans clonage ni création d’objets. Je connais moins le développement de jeux.
CsBalazsHungary
4
@CsBalazsHungary Je crois que les problèmes lorsque la création d'objet est une préoccupation ont tendance à être liés aux allocations de mémoire et au garbage collection, ce qui constituerait probablement le prochain niveau de goulets d'étranglement suivant la complexité algorithmique (en particulier dans un environnement à mémoire limitée, par exemple les smartphones et autres). .
JAB
JMonkey est également ce que j’ai souvent vu car il est souvent essentiel à la performance. Je ne l'ai jamais entendu appeler "JavaMonkey" cependant
Richard Tingle
3
@JAB: Le CPG de la machine virtuelle Java est spécialement adapté au cas des objets éphémères. Il est facile de collecter de grandes quantités d’objets de très courte durée de vie et, dans de nombreux cas, toute votre génération éphémère peut être collectée en un seul mouvement de pointeur.
Phoshi
2
en fait, si on doit créer un objet , ce ne sera pas performant; si un bloc de code doit être fortement optimisé pour la vitesse, vous devez utiliser des primitives et des tableaux natifs au lieu; à cause de cela, j'estime que l'énoncé de cette réponse ne tient pas.
vaxquis
10

Je ne pense pas que ce sont deux morceaux de code équivalents. Dans le premier cas, vous devez créer le fichier otherObject. Vous pouvez modifier l'instance existante dans la seconde. Les deux ont ses utilisations. L'odeur de code serait préférer l'un à l'autre.

Euphorique
la source
Je sais qu'ils ne sont pas équivalents. Vous avez raison, nous persistons dans ces choses, donc cela ferait une différence. Vous dites donc qu’aucun d’eux n’est anti-modèle, c’est juste la question du cas d’utilisation?
CsBalazsHungary
@CsBalazsHungary Je dirais oui. A en juger par le petit morceau de code que vous avez fourni.
Euphoric
Je suppose que cela devient un problème plus grave si vous avez un paramètre primitif entrant, qui bien sûr ne sera pas mis à jour, donc comme @claasz a écrit: il devrait être évité sauf si c'est nécessaire.
CsBalazsHungary
1
@CsBalazsHungary En cas d'argument primitif, la fonction ne fonctionnerait même pas. Donc, vous avez un problème pire que de changer les arguments.
Euphoric
Je dirais qu'un junior tomberait dans le piège d'essayer de transformer un type primitif en paramètre de sortie. Donc, je dirais que le premier convertisseur devrait être préféré si possible.
CsBalazsHungary
7

Cela dépend vraiment de la langue.

En Java, la seconde forme peut être un anti-motif, mais certains langages traitent le passage de paramètre différemment. En Ada et VHDL par exemple, au lieu de passer par valeur ou par référence, les paramètres peuvent avoir des modes in, outou in out.

C'est une erreur de modifier un inparamètre ou de lire un outparamètre, mais toute modification apportée à un in outparamètre est renvoyée à l'appelant.

Donc, les deux formes en Ada (ce sont aussi des VHDL légales) sont

function MyObject2OtherObject(mo : in MyObject) return OtherObject is
begin
    ... Do the conversion
    return otherObject;
end MyObject2OtherObject;

et

procedure MyObject2OtherObject(mo : in MyObject; oo : out OtherObject) is
begin
    ... Do the conversion
    oo := ... the conversion result;
end MyObject2OtherObject;

Les deux ont leurs utilisations; une procédure peut renvoyer plusieurs valeurs dans plusieurs Outparamètres, tandis qu'une fonction ne peut renvoyer qu'un seul résultat. Étant donné que l'objectif du deuxième paramètre est clairement indiqué dans la déclaration de procédure, aucune objection ne peut être opposée à ce formulaire. J'ai tendance à préférer la fonction pour la lisibilité. mais il y aura des cas où la procédure est meilleure, par exemple, lorsque l'appelant a déjà créé l'objet.

Brian Drummond
la source
3

Cela semble effectivement malodorant, et sans plus de contexte, il est impossible de le dire avec certitude. Il pourrait y avoir deux raisons pour cela, bien qu'il existe des alternatives pour les deux.

Tout d'abord, il s'agit d'une manière concise d'implémenter une conversion partielle ou de laisser le résultat avec des valeurs par défaut en cas d'échec de la conversion. C'est-à-dire que vous pourriez avoir ceci:

public void ConvertFoo(Foo from, Foo to) {
    if (can't convert) {
        return;
    }
    ...
}

Foo a;
Foo b = DefaultFoo();
ConvertFoo(a, b);
// If conversion fails, b is unchanged

Bien entendu, cela est généralement traité à l'aide d'exceptions. Cependant, même si des exceptions doivent être évitées pour une raison quelconque, il existe un meilleur moyen de procéder: le modèle TryParse étant une option.

Une autre raison est que cela pourrait être uniquement pour des raisons de cohérence. Par exemple, cela fait partie d'une API publique dans laquelle cette méthode est utilisée pour toutes les fonctions de conversion pour une raison quelconque (telle que d'autres fonctions de conversion ayant plusieurs sorties).

Java n'a pas l'habitude de gérer plusieurs sorties - il ne peut pas avoir de paramètres de sortie comme certaines langues, ni avoir plusieurs valeurs de retour comme d'autres - mais vous pouvez quand même utiliser des objets de retour.

La raison de la cohérence est plutôt boiteuse mais malheureusement, elle est peut-être la plus courante.

  • Peut-être que les flics de style sur votre lieu de travail (ou votre base de code) proviennent d'un contexte autre que Java et ont été réticents à changer.
  • Votre code peut avoir été un port d'une langue où ce style est plus idiomatique.
  • Votre entreprise peut avoir besoin de maintenir la cohérence des API dans différentes langues. Il s’agissait du style avec le plus petit dénominateur commun (c'est idiot, mais cela arrive même pour Google ).
  • Ou peut-être que le style avait plus de sens dans un passé lointain et était transformé dans sa forme actuelle (par exemple, il pourrait s'agir du modèle TryParse mais un prédécesseur bien intentionné a supprimé la valeur de retour après avoir découvert que personne ne l'avait cochée du tout).
congusbongus
la source
2

L’avantage du second modèle est qu’il oblige l’appelant à assumer la propriété et la responsabilité de l’objet créé. Il n'est pas question de savoir si la méthode a créé ou non l'objet ou l'a obtenu à partir d'un pool réutilisable. L'appelant sait qu'il est responsable de la durée de vie et de la disposition du nouvel objet.

Les inconvénients de cette méthode sont les suivants:

  1. Ne peut pas être utilisé en tant que méthode d'usine, l'appelant doit connaître le sous-type exact dont OtherObjectil a besoin et le construire à l'avance.
  2. Ne peut pas être utilisé avec des objets nécessitant des paramètres dans leur constructeur si ces paramètres proviennent de MyObject. OtherObjectdoit être constructible sans savoir MyObject.

La réponse est basée sur mon expérience en c #, j'espère que la logique se traduira en Java.

Rotem
la source
Je pense qu'avec la responsabilité que vous mentionnez, cela crée également un cycle de vie "plus difficile à voir" des objets. Dans un système de méthode complexe, je préférerais une modification directe d'un objet plutôt que de commencer à parcourir toutes les méthodes que nous passons.
CsBalazsHungary
C'était ce que je pensais. Un type primitif d' injection
Lyndon White
Un autre avantage est si OtherObject est abstrait ou une interface. La fonction n'a aucune idée du type à créer, mais l'appelant peut le faire.
user949300
2

Compte tenu de la sémantique vague - qui myObjectToOtherObjectpourrait également signifier que vous déplacez certaines données du premier objet vers le deuxième ou que vous les convertissez entièrement à nouveau, la deuxième méthode semble plus appropriée.

Cependant, si le nom de la méthode était Convert(ce qui devrait être le cas si nous examinons la partie "... faites la conversion"), je dirais que la deuxième méthode n'aurait aucun sens. Vous ne convertissez pas une valeur en une autre valeur existante, IMO.

guillaume31
la source