Opérateur ternaire considéré comme nuisible? [fermé]


79

Par exemple, préféreriez-vous ce one-liner

int median(int a, int b, int c) {
    return (a<b) ? (b<c) ? b : (a<c) ? c : a : (a<c) ? a : (b<c) ? c : b;
}

ou une solution if / else impliquant plusieurs déclarations de retour?

Quand est-ce ?:approprié et quand ne l'est-il pas? Devrait-il être enseigné ou caché aux débutants?


221
Cette utilisation particulière en est :)
karmajunkie

6
Qui a codé cela et à quoi ressemble leur version d'une médiane à quatre chiffres? Ou cinq?
Mason Wheeler

3
Le nom le plus correct est "opérateur conditionnel". Il s’agit tout simplement de l’opérateur ternaire le plus utilisé.
Alan Pearce

1
Cela a été demandé il y a plus de deux ans sur stackoverflow. Allons-nous tout demander à nouveau ici maintenant? stackoverflow.com/questions/160218/to-ternary-or-not-to-ternary
webbiedave

3
Je suis surpris pourquoi de telles questions ne cessent de se poser. La réponse est toujours "tout ce qui fonctionne et est lisible". - le dernier étant d'égale importance.
Apoorv Khurasia

Réponses:


234

L'opérateur ternaire est-il mauvais?

Non c'est une bénédiction.

Quand est-il approprié?

Quand c'est quelque chose d'aussi simple, vous ne voulez pas gaspiller beaucoup de lignes.

et quand est-ce pas?

Lorsque la lisibilité et la clarté du code en souffrent et qu'un risque d'erreur d'erreur dû à une attention insuffisante augmente, par exemple avec de nombreux opérateurs chaînés, comme dans votre exemple.


Le test décisif est lorsque vous commencez à douter que votre code est facilement lisible et maintenable à long terme. Alors ne le fais pas.


23
+1 pour Lorsque la lisibilité et la clarté du code en souffrent. Avec de nombreux opérateurs chaînés, comme dans votre exemple. L'exemple prend plus de temps à comprendre que son équivalent si / else.
Etrange

23
+1 bravo pour la bonne explication! Les développeurs n'ont pas tendance à se rendre compte que certaines choses sont des jugements, ils veulent que tout soit en noir et blanc. Cela me rend fou. J'ai rencontré beaucoup de gens de l'opinion "X is evil, ne l'utilisons jamais". Je préfère "X is great si vous l'utilisez pour ce à quoi il est bon".
Rétablir Monica

54
C'est aussi mal s'il est utilisé: myVar = (someExpression)? vrai faux; Aaaaarrgh!
Adamk

20
@adamk: Essayez ceci pour le mal:myVar = someExpression ? false : true;
Dean Harding

8
Que diriez-vous de (someExpression ? var1 : var2)++:-)
fredoverflow

50

Je pense que l'opérateur ternaire non imbriqué (c'est-à-dire, une déclaration où il n'est utilisé qu'une fois) convient, mais si vous imbriquer plus d'un, il devient un peu difficile à lire.


3
Cela pourrait être considéré comme une simplification excessive, mais c'est une directive très facile à suivre et cela fonctionne dans la plupart des cas.
Alan Pearce

2
C'est en fait ma règle de base: vous ne devriez jamais les imbriquer, sinon remplacez-les par ceux de / sinon, ce sera plus clair ainsi.
Piovezan

Si vous les utilisiez et que vous les imbriquiez, utilisez des parenthèses et un espace pour le rendre lisible. If-else peut être rendu aussi moche. Résistez à l'envie de montrer votre intelligence en écrivant des choses que les compilateurs peuvent lire, mais que les humains ne peuvent pas lire. Un jour, vous serez l'humain qui ne peut pas.
candied_orange

24

Quand est: approprié

  • Quand cela rend votre code plus concis et lisible.

et quand est-ce pas?

  • Quand cela rend votre code illisible.
  • Si vous le faites simplement pour faire plaisir à un outil de refactoring comme ReSharper et non à la personne qui doit gérer le code

Si vous avez des appels de logique ou de fonction à l'intérieur de l'expression ternaire, vous rendez la tâche horrible.


Celui-ci mérite beaucoup d'upvotes!
Piovezan

22

Une différence que (je pense) personne n'a signalée est que si-else ne peut pas renvoyer de valeur, alors que l'opérateur ternaire le peut.

Venant de F #, j'aime parfois utiliser l'opérateur ternaire pour imiter la correspondance de motif.

match val with
| A -> 1
| B -> 3
| _ -> 0

contre

return val == A ? 1 : 
       val == B ? 3 : 
       0;

C'est plutôt cool. Jamais pensé à ça.
Rei Miyasaka

+1 @Benjol: J'allais souligner la même chose (qu'en F #, tout est une expression, y compris if / elif / else). J'utilise des ternaires comme dans votre exemple également, mais pas encore découverts :). Une autre chose que je fais, en Javascript, peut-être au-dessus, est la suivante: var res = function() {switch(input) {case 1: return "1"; case 2: return "2"; ...}}()émuler des commutateurs en tant qu'expressions.
Stephen Swensen

@Stephen, je vais utiliser les mots expressionet statementmais je suis toujours inquiet , je vais les chercher dans le mauvais sens et faire un fou de moi - même :)
Benjol

@Benjol: Je sais ce que tu veux dire!
Stephen Swensen

6
Également utile en C et C ++ pour initialiser des constvariables qui ne pourront pas être modifiées ultérieurement.
David Thornley

13

Un exemple (IMHO) d'une utilisation valide:

printf("Success in %d %s\n", nr_of_tries, (nr_of_tries == 1 ? "try" : "tries"));

Cela donne un code plus lisible que d'avoir deux instructions d'impression distinctes. Les exemples imbriqués dépendent: (compréhensible? Oui: non)


11
Gardez simplement à l'esprit que si vous faites cela, vous faites l'enfer pour quiconque doit localiser votre application. Bien sûr, si cela ne pose pas de problème, continuez.
Anon.

1
C'est ma règle de base: 1 niveau de nidification (dans certaines circonstances).
Oliver Weiler

7

Absolument pas mal. En fait, c'est pur , et si-alors-sinon, ce n'est pas le cas.

Dans les langages fonctionnels comme Haskell, F #, ML, etc., ce sont les déclarations if-then-else qui sont considérées comme mauvaises.

La raison en est que toute "action" comme une instruction impérative if-then-else exige que vous sépariez une déclaration de variable de sa définition et introduise l' état dans votre fonction.

Par exemple, dans le code suivant:

const var x = n % 3 == 1
    ? Parity.Even
    : Parity.Odd;

contre.

Parity x;
if (n % 3 == 1)
    x = Parity.Even;
else
    x = Parity.Odd;

Le premier a deux avantages en plus d’être plus court:

  1. x est une constante, et offre donc beaucoup moins de chances d’introduire des bogues, et peut potentiellement être optimisée de façon que la seconde ne puisse jamais l'être.
  2. Le type est précisé par l'expression, ainsi le compilateur peut facilement en déduire qu'il xdoit être de type Parity.

De manière confuse, dans les langages fonctionnels, l’opérateur ternaire est souvent appelé if-then-else. En Haskell, vous pourriez dire x = if n mod 3 == 1 then Odd else Even.


Eh oui, c'est ce que @Benjol a également souligné. Voir mon commentaire à sa réponse pour une manière amusante d'émuler des instructions switch comme des expressions en Javascript.
Stephen Swensen

7

Cette expression particulière me fait mal aux yeux; Je nommerais n'importe quel développeur de mon équipe qui l'utilisait parce que c'était impossible à maintenir.

Les opérateurs ternaires ne sont pas mauvais s'ils sont bien utilisés. Ils ne doivent même pas être des lignes simples; Un long format qui est bien formaté peut être très clair et facile à comprendre:

return
      ( 'a' == $s ) ? 1
    : ( 'b' == $s ) ? 2
    : ( 'c' == $s ) ? 3
    :                 4;

J'aime mieux que l'équivalent de la chaîne if / then / else:

if ( 'a' == $s ) {
    $retval = 1;
}
elsif ( 'b' == $s ) {
    $retval = 2;
}
elsif ( 'c' == $s ) {
    $retval = 3;
}
else {
    $retval = 4;
}

return $retval;

Je vais reformater ceux-ci en:

if    ( 'a' == $s ) { $retval = 1; }
elsif ( 'b' == $s ) { $retval = 2; }
elsif ( 'c' == $s ) { $retval = 3; }
else                { $retval = 4; }

return $retval;

si les conditions et les tâches permettent un alignement facile. Je préfère tout de même la version ternaire car elle est plus courte et moins bruyante en ce qui concerne les conditions et les affectations.


Pourquoi ne puis-je pas insérer de sauts de ligne dans les commentaires? Arrgghh!
Christopher Mahan

3

ReSharper dans VS.NET suggère parfois de remplacer if...elsepar l' ?:opérateur.

Il semble que ReSharper ne suggère que si les conditions / blocs sont inférieurs à un certain niveau de complexité, sinon cela reste if...else.


4
Une autre grande fonctionnalité que j'aime beaucoup chez ReSharper
Anonyme Tapez

2

Cela peut être reformaté pour être aussi beau que la combinaison if / else:

int median(int a, int b, int c)
{
    return
        (a<b)
        ?
            (b<c)
            ? b
            :
                (a<c)
                ? c
                : a
        :
            (a<c)
            ? a
            :
                (b<c)
                ? c
                : b;
}

Mais le problème est que je ne suis pas vraiment sûr d'avoir obtenu le bon retrait pour représenter ce qui se passera réellement. :-)


3
+1 Je pense que c'est beaucoup mieux qu'une if-elsestructure équivalente . La clé est la mise en forme.
Orbling

13
Si je voyais cela dans une base de code, je réfléchirais sérieusement à la recherche d'un nouvel emploi.
Nick Larsen

+1 Pas pour l'indentation réelle, mais c'est la meilleure solution pour cet exemple.
Mark Hurd

2
Seulement une autre mise en forme automatique accidentelle effacerait toute cette empreinte, et le temps d'une autre série de restructurations - extrêmement productive :)
nawfal

2

Loin d'être méchant, l'opérateur ternaire est une aubaine.

  • Cela est particulièrement utile lorsque vous souhaitez prendre une décision dans une expression imbriquée . L'exemple classique est un appel de fonction:

    printf("I see %d evil construct%s in this program\n", n, n == 1 ? "" : "s");
    
  • Dans votre exemple particulier, le ternaire est presque gratuit, puisqu'il s'agit de l'expression de premier niveau sous a return. Vous pouvez lever la condition au niveau de l'instruction sans dupliquer autre chose que le returnmot clé.

NB Rien ne rendra jamais cet algorithme particulier pour la médiane facile à lire.


Il est difficile d'être si lisible que vous ne pourrez pas l'améliorer. printf("I see %d evil construct%s in this program\n", n, "s" unless (n == 1) "s");
Pacerier

2
  1. Les arguments "diaboliques" mis à part, j'ai constaté dans mon expérience une forte corrélation entre l'utilisation d'un opérateur ternaire par un programmeur et la probabilité que l'ensemble de sa base de code soit difficile à lire, à suivre et à maintenir (si ce n'est carrément non documenté). Si un programmeur est plus préoccupé par la sauvegarde de quelques lignes de 1-2 caractères que par une personne capable de comprendre son code, toute confusion mineure dans la compréhension d'une déclaration ternaire est généralement la partie visible de l'iceberg.

  2. Les opérateurs ternaires attirent des nombres magiques comme s ** t attire les mouches.

Si je recherchais une bibliothèque Open Source pour résoudre un problème particulier et que je voyais un code tel que les opérateurs ternaires de l'affiche originale dans un candidat pour cette bibliothèque, des sonneries d'avertissement sonnaient dans ma tête et je commençais à envisager de passer à autre chose. à un autre projet à emprunter.


2

Voici un exemple de quand il est mauvais:

oldValue = newValue >= 0 ? newValue : oldValue;

C'est déroutant et inutile. Le compilateur pourrait optimiser la deuxième expression (oldValue = oldValue), mais pourquoi le codeur l'a-t-il fait en premier lieu?

Un autre doozy:

thingie = otherThingie != null ? otherThingie : null;

Certaines personnes ne sont tout simplement pas faites pour être des codeurs ...

Greg dit que la déclaration équivalente si est 'bruyante'. C'est si vous écrivez bruyamment. Mais ce même si peut être écrit comme:

if ('a' == $s) return 1;
if ('b' == $s) return 2;
if ('c' == $s) return 3;
return 4;

Ce qui n'est pas plus bruyant que le ternaire. Je me demande si les raccourcis ternaires; toutes les expressions sont-elles évaluées?


Votre deuxième exemple me rappelle if (x != 0) x = 0;...
fredoverflow

2

Mal? Regardez, ils sont juste différents.

ifest une déclaration. (test ? a : b)est une expression. Ils ne sont pas la même chose.

Les expressions existent pour exprimer des valeurs. Des instructions existent pour effectuer des actions. Les expressions peuvent apparaître à l'intérieur des instructions, mais pas l'inverse. Vous pouvez donc utiliser des expressions ternaires dans d'autres expressions, comme pour les termes d'une sommation ou pour les arguments d'une méthode, etc. Vous n'êtes pas obligé , mais vous pouvez si vous le souhaitez . Il n'y a rien de mal à ça. Certaines personnes pourraient dire que c'est mal, mais c'est leur opinion.

L'une des valeurs d'une expression ternaire est qu'elle vous oblige à gérer les cas vrais et faux. ifles déclarations ne le font pas.

Si la lisibilité vous préoccupe, vous pouvez les formater de manière lisible.

D'une certaine manière, le "mal" s'est glissé dans le vocabulaire de la programmation. J'aimerais savoir qui l'a laissé tomber pour la première fois. (En fait, j'ai un suspect - il est au MIT.) Je préférerais que nous ayons des raisons objectives de juger de la valeur dans ce domaine, pas seulement le goût et l'appellation de personnes.


1
Puis-je savoir qui est le suspect? Juste pour améliorer un peu ma connaissance du terrain.
mlvljr

1
@mlvljr: Eh bien, je peux me tromper, alors mieux vaut ne pas le faire.
Mike Dunlavey

1

Il a une place. J'ai travaillé dans de nombreuses entreprises où les niveaux de compétences des développeurs vont de terribles à magiciens. Etant donné que le code doit être maintenu, et que je ne serai pas là pour toujours, j'essaie d'écrire des choses de telle sorte qu'elles semblent appartenir à cet endroit (sans regarder les commentaires avec mes initiales, il est extrêmement rare que vous puissiez regardez le code sur lequel j'ai travaillé pour voir où j'ai apporté des modifications), et qu'une personne moins habile que moi peut le conserver.

Bien que l'opérateur ternaire ait l'air cool et nitziffic, mon expérience est que cette ligne de code va être à peu près impossible à maintenir. Chez mon employeur actuel, nous vendons des produits depuis près de 20 ans. Je n'utiliserais cet exemple nulle part.


1

Je ne pense pas que l'opérateur ternaire soit diabolique.

Voici un piège qui m'a laissé perplexe, cependant. J'étais un programmeur C pour beaucoup (plus de 10 ans) et à la fin des années 1990, je suis passé à la programmation d'applications Web. En tant que programmeur Web, j'ai rapidement rencontré PHP qui possède également un opérateur ternaire. J'ai eu un bogue dans un programme PHP que j'ai finalement tracé pour une ligne de code avec un opérateur ternaire imbriqué. Il s’avère que l’opérateur ternaire PHP s’associe de gauche à droite mais que l’opérateur ternaire (auquel j’étais habitué) s’associe de droite à gauche.


1

Tout ce qui rend votre code plus laid est diabolique.

Si vous utilisez ternary pour rendre votre code plus propre, utilisez-le. Parfois, comme php, il est bon de faire des substitutions en ligne, par exemple

"Hello ".($Male?"Mr":"Ms")." $Name

Cela économise quelques lignes et c'est assez clair, mais votre exemple nécessite au moins une meilleure mise en forme pour être clair, et ternary n'est pas vraiment bon pour le multi-ligne, alors vous pouvez aussi bien utiliser if / else.


1

Puis-je le dire? Je n'arrive pas à trouver cette application particulière de l'opération ternaire maléfique :

  1. l'opération qu'il effectue est assez triviale, et une fois que vous y êtes allé, il est presque impossible que des bugs apparaissent;
  2. ce qu'il fait est clairement indiqué dans le nom de la fonction;
  3. prendre> 1 ligne pour quelque chose d'aussi évident et qui ne serait clairement pas amélioré à l'avenir (à moins qu'un algorithme de la médiane magique ait vécu non détecté jusqu'à maintenant).

S'il vous plaît ayez pitié, ma réputation est déjà assez pitoyable.


1

Plus grosse victoire: montrer qu'il n'y a qu'un seul objectif d'action.

if ( $is_whatever )
    $foo = 'A';
else
    $foo = 'B';

Vous pouvez suivre deux chemins de code et le lecteur doit lire attentivement pour savoir quelles sont les deux variables définies. Dans ce cas, ce n’est qu’une variable, mais le lecteur a encore beaucoup à lire pour le comprendre. Après tout, ça aurait pu être ça:

if ( $is_whatever )
    $foo = 'A';
else
    $bar = 'B';

Avec l'opérateur ternaire, il est clair qu'une seule variable est définie.

$foo = $is_whatever ? 'A' : 'B';

Au niveau le plus bas, c'est le principe DRY (Don't Repeat Yourself) le plus fondamental. Si vous ne pouvez spécifier $fooqu'une seule fois, faites-le.


0

Si ... alors ... le reste a tendance à mettre l'accent sur la condition et, partant, à mettre l'accent sur les opérations effectuées de manière conditionnelle.

l'opérateur ternaire est l'opposé, il a tendance à masquer la condition et est donc utile lorsque l'opération effectuée est plus importante que la condition elle-même.

Il existe un problème technique mineur, dans certaines langues, selon lequel ils ne sont pas tout à fait interchangeables, l'un étant une instruction et l'autre une expression, par exemple, initialisation conditionnelle de consts en C ++.


0

Quand est-ce approprié et quand ne l'est-il pas?

Je pense que lorsque vous développez pour un groupe homogène de personnes, il n’ya pas de problème, mais lorsque vous devez traiter avec des personnes qui gèrent des niveaux différents, ce type de lecteurs ne fait qu’introduire un niveau supplémentaire de complexité dans le code. Ainsi, ma politique en la matière est la suivante: code clair et ne pas expliquer plutôt que coder court et expliquer 123123 fois.

Devrait-il être enseigné ou caché aux débutants?

Je ne devrais pas apprendre aux débutants, mais plutôt les préférer comprendre quand un besoin s'en fait sentir, alors il ne sera utilisé que lorsque cela sera nécessaire et pas à chaque fois que vous aurez besoin d'un if.


0

OMI, l'opérateur lui-même n'est pas mauvais, mais la syntaxe utilisée pour cela en C (et C ++) est excessivement succincte. OMI, Algol 60 a fait mieux, donc quelque chose comme ça:

A = x == y ? B : C;

ressemblerait plus à ceci (mais en s’en tenant à la syntaxe de type C en général):

A = if (x==y) B else C;

Même avec cela, une imbrication trop profonde peut entraîner des problèmes de lisibilité, mais au moins A) quiconque a déjà programmé peut en comprendre une simple, et B) les personnes qui comprennent qu'elle peut gérer une imbrication beaucoup plus profonde assez facilement. OTOH, je voudrais également noter que dans LISP (par exemple), a condest en gros une déclaration ternaire - pas un ensemble d’énoncés, mais une expression unique qui donne une valeur (mais là encore, la plupart de LISP est comme ça. .)


Pourquoi ne pas simplement faire cela pour la lisibilité? A = (x==y) ? B : C
Jeremy Heiler

@ Jeremy: alors que certaines personnes trouvent les parens utiles, même au mieux, ils ne les aident pas beaucoup . Nidifiez plus qu'un couple en profondeur et vous avez toujours besoin d'une empreinte minutieuse (au minimum) pour garder les choses en ordre. Sans doute la même chose se produira finalement dans Algol, mais je ne dis jamais un problème se pose en elle comme je le fais souvent en C ...
Coffin Jerry

J'ai juste supposé que tout le monde était d'accord pour dire que confondre un opérateur ternaire est une mauvaise chose. Je parlais spécifiquement des exemples que vous avez fournis. En particulier, comment le premier peut ressembler davantage au second dans la plupart des langues.
Jeremy Heiler

0

Un magasin qui écrit régulièrement des méthodes de 600 à 1 200 lignes ne devrait pas me dire qu'un ternaire est "difficile à comprendre". Un magasin qui autorise régulièrement cinq conditions pour évaluer une branche de code ne doit pas me dire que les conditions résumées concrètement dans un ternaire sont "difficiles à lire".


0

Quand est-il approprié: et quand ne l'est-il pas?

  • Si vous n'obtenez pas un gain de performance, ne l'utilisez pas; cela affecte la lisibilité de votre code.
  • Utilisez-le une fois et ne l'imbriquez pas.
  • C'est plus difficile à déboguer.

Devrait-il être enseigné ou caché aux débutants?

Peu importe, mais cela ne devrait pas être caché intentionnellement car ce n'est pas trop complexe pour un «débutant» à apprendre.


-2

Dans votre exemple:

def median(a, b, c):
    if a < b < c: return b
    if a < c < b: return c
    if b < a < c: return a
    if b < c < a: return c
    if c < a < b: return a
    if c < b < a: return b

est très simple à lire et évident. La variable entre <<est la valeur de retour.

Mise à jour

idem, mais moins de lignes de code. Encore simple je pense.

def median(a, b, c):
    if b<a<c or c<a<b: return a
    if a<b<c or c<b<a: return b
    if a<c<b or b<c<a: return c

Cela nécessite 12 comparaisons dans le pire des cas ...
fredoverflow

1
Peut-être, mais c'est lisible.
Christopher Mahan

-2

C'est aussi nécessaire pour const

const int nLegs  = isChicken ? 2: 4 ;

Étrange. Je pense que c'est C ++ ou quelque chose. Je pensais que const avait toujours été une constante de compilation (comme en C #)
nawfal

@nawfal - si vous ne le savez pas, Poulet jusqu'à la fin
Martin Beckett

oui c'est quoi. Je pense que constoui dans certaines langues. En C # constdoit toujours être la valeur compilée au moment de la compilation. ce qui signifie const int nLegs = isChicken ? 2: 4 ;ne fonctionnera pas, mais le const int nLegs = true ? 2: 4 ;fera
nawfal
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.