Composé de coiffage ET / OU if

13

Comment stylisez-vous les instructions complexes ET / OU if pour une lisibilité maximale? Comment indentez-vous et où placez-vous les sauts de ligne? Ma situation particulière ressemble à ce qui suit. C'est certainement mieux que de tout casser en une seule ligne, mais ça a toujours l'air désordonné.

if (
    (
        x == y
        && a != b
        && p.isGood() 
        && (
            i + u == b
            || q >= a
        )
    )
    || k.isSomething()
    || m > n
) {
    doSomething();
}
JoJo
la source
1
La mise en retrait maladroite et la structure entre parenthèses / accolade sont-elles intentionnelles ou font-elles partie du style?
Ed S.
Marrant. J'ai posé cette même question sur SO il y a une semaine et elle a été fermée. Heureux de voir cette question vivante quelque part!
Eric Belair

Réponses:

6

Créez des variables booléennes pour chaque petite étape:

bool step1 = i + u == b || q >= a;
bool step2 = a != b && p.isGood() && group1;
bool step3 = group2 || k.isSomething() || m > n;
if (step3) { doSomething(); }

Ceci est bien sûr similaire à la réponse de Lacrymologie, sauf avec des noms différents pour chaque étape.

Si vous nommez step1, step2et step3d'une manière qui a un bon sens conceptuel, cela devrait être de loin le plus lisible. p.isGood()et k.isSomething()peut parfois être invoqué dans des situations où il ne serait pas dans votre code d'origine, donc ce ne serait pas une option si ces fonctions sont coûteuses ou si vous exécutez ce code dans une boucle très serrée.

D'un autre côté, vous n'avez pas à vous soucier des performances que la création de nouvelles variables pourrait entraîner; un bon compilateur les optimisera.

Un exemple avec la détection de collision rectangle (que vous n'utiliseriez probablement pas en raison de la performance susmentionnée):

if((a.x + a.width >= b.x || b.x + b.width >= a.x)
 && (a.y + a.height >= b.y || b.y + b.width >= a.y)
)
{ collision(); }

Pourrait devenir:

bool horizMatch = a.x + a.width >= b.x || b.x + b.width >= a.x;
bool vertMatch = a.y + a.height >= b.y || b.y + b.width >= a.y;
if(horizMatch && vertMatch) { collision(); }

De plus, si vous voulez laisser votre code tel quel, je pense que ce serait tout aussi bien. Je pense honnêtement que votre code est assez lisible. Évidemment, je ne sais pas exactement ce que a b x y i u p k m nsont, mais en ce qui concerne la structure, cela me semble bien.

Rei Miyasaka
la source
8

En général, je re-factorise mon code pour le rendre plus modulaire si mes conditions deviennent compliquées.

JohnFx
la source
J'éviterais la réfraction dans cette situation. Il serait idiot de tester ces petites fonctions isolément, et le fait que les définitions se balancent en dehors de la fonction rend le code moins évident.
Rei Miyasaka
Cela dépend de la façon dont vous refactorisez. Je dirais que cela rend votre code beaucoup plus évident d'avoir des noms de fonction significatifs au lieu d'une chaîne de conditions qui ressemble à un manuel d'algèbre jeté dans votre code.
JohnFx
Visuellement cependant, vos fonctions seraient suspendues à l'extérieur, et il ne serait pas immédiatement évident de savoir exactement ce que fait la fonction. C'est momentanément une boîte noire jusqu'à ce que vous faites défiler vers le haut. À moins que vous ne soyez dans un langage qui autorise les fonctions dans les fonctions, je ne pense pas que ce serait très pratique du tout, ni pour le lecteur ni pour l'écrivain, quelle que soit la qualité de votre réfracteur. Et si vous êtes dans un langage qui autorise les fonctions dans les fonctions, il est probable que la syntaxe ne diffère guère de déclarer une liaison ou une variable à la place, par exemple let x = a > bou let f a b = a > b.
Rei Miyasaka
Les variables fonctionneraient tout aussi bien. Je considérerais aussi cette refactorisation.
JohnFx
Ah ok.
Rei Miyasaka
8

Je ferais quelque chose de plus comme ça, à ce niveau de complexité

bool doIt = x == y && a != b && p.isGood();
doIt &= ( i + u == b || q >= a);
doIt |= k.isSomething() || m > n;

if(doIt)
{
    doSomething();
}

c'est moche, mais c'est lisible et je suis presque certain que le compilateur saura comment le refactoriser.

D'un autre côté, si jamais je me vois dans la situation d'écrire une telle déclaration IF, je repense la solution, car je suis CERTAIN qu'il existe un moyen de le faire plus simplement, ou du moins d'abstraire une partie de cette condition (par exemple: peut-être: x == y && a != b && p.isGood()vraiment méchant this->isPolygon()et je peux faire cette méthode;

Lacrymologie
la source
4

Je suis de moins en moins obsédé par l'alignement vertical au fil du temps, mais ma forme générale avec des expressions sur plusieurs lignes est ...

if (   (   (expr1 == expr2)
        || (expr3 == expr4)
        || (expr5 == expr6)
       )
    && (   (expr7 == expr8)
        || (expr9 == expra)
       )
   )
{
  blah;
}

Points clés...

  • Les parens proches s'alignent verticalement avec les parens ouverts, comme avec les accolades.
  • Les sous-expressions qui tiennent dans une ligne sont dans une ligne et sont alignées verticalement à gauche. Là où cela aide à la lisibilité, les opérateurs d'infixe au sein de ces pièces unifilaires sont également alignés verticalement.
  • Les accolades de fermeture créent naturellement des lignes presque vides, aidant à regrouper visuellement les choses.

Parfois, je vais formater +et / *ou d'autres opérateurs comme ça aussi. Un certain nombre d'expressions complexes prennent une forme de somme de produit ou de produit de somme (qui peut faire référence à des «sommes» et «produits» booléens), il est donc probablement assez courant qu'un style cohérent en vaille la peine.

Soyez prudent avec cela, cependant. Il est souvent préférable de refactoriser (déplacer des parties de l'expression dans une fonction, ou de calculer et de stocker des parties intermédiaires dans une variable) plutôt que d'utiliser l'indentation pour essayer de rendre une expression trop complexe plus lisible.

Si vous préférez empiler vos parenthèses sur le côté droit, je ne le déteste pas, mais je suppose que ce n'est pas trop mal. Trop poussé, vous courez le risque qu'une erreur puisse laisser l'indentation dénaturer ce que font les parenthèses.

if (   (   (expr1 == expr2)
        || (expr3 == expr4)
        || (expr5 == expr6))

    && (   (expr7 == expr8)
        || (expr9 == expra)))
{
  blah;
}
Steve314
la source
+1 J'aime votre style. Cela a répondu directement à ma question, mais je pense que Rei Miyasaka a identifié la racine du problème. Si jamais je suis trop paresseux pour faire la méthode de Rei, je vais utiliser votre style.
JoJo
Wow, c'est vraiment sympa.
Rei Miyasaka
1

http://www.codinghorror.com/blog/2006/01/flattening-arrow-code.html

Je suis d'accord avec la réponse de JohnFx ainsi que celle de Lacrymology. Je voudrais créer un tas de fonctions (de préférence statiques) qui atteignent de petits objectifs, puis les développer de manière intelligente.

Alors, que diriez-vous de quelque chose comme ça? Notez que ce n'est pas la solution parfaite, mais cela fonctionne. Il existe des moyens de nettoyer davantage, mais des informations plus spécifiques sont nécessaires. Remarque: ce code devrait fonctionner tout aussi rapidement, car le compilateur est intelligent.

// Currently based on members or global vars
// (which is often a bad idea too)
function doSomethingCondirionally()
{
  if (k.isSomething() || m > n)
  {
    doSomething();
    return;
  }

  // Else ... 
  if (x != y) return;
  if (a == b) return;
  if (!p.isGood()) return;

  // Final, positive check
  if (i + u == b || q >= a)
  {
    doSomething();
  }
}
Emploi
la source
Si avoir un seul point de sortie d'une fonction est quelque chose que vous appréciez (par exemple, si vous êtes dans un langage fonctionnel), ce n'est peut-être pas la meilleure option, ni même une option disponible. Cela dit, oui, c'est ce que je fais souvent.
Rei Miyasaka
Et si c'est un langage interprété comme Javascript?
JoJo
@ Rei Miyasaka, combien j'apprécie cela dépend d'une langue. Bien que j'aime la famille de langues LISP, je n'ai pas eu à en utiliser une au travail. Si je dois re-factoriser la fonction de quelqu'un d'autre sans toucher à aucun autre code (souvent une réalité), alors je ferais quelque chose comme ci-dessus. Si je peux écrire / réécrire cette logique de fond en comble, alors mon approche sera différente, mais je ne peux pas écrire un tel code sans avoir un exemple spécifique de ce que l'auteur essaie de faire ici.
Job
1
@Rei Miyasaka, Cette personne pourrait être un génie ou être pleine de merde. Je ne sais pas tout, mais je serais curieux de savoir la défense de cette personne du point de sortie unique. Il y a une discussion à ce sujet ici et sur SO, et l'impression que j'ai eue était que cette approche a été populaire parmi les universitaires dans les années 80 peut-être, mais n'a plus d'importance et peut en fait nuire à la lisibilité. Bien sûr, si vous faites tout dans le style fonctionnel LINQ, ce problème ne se posera même pas.
Job
2
@Job @Steve Je pense que c'est une directive plus importante dans les langues qui nécessitent une désallocation explicite. Évidemment pas pour chaque fonction, mais c'est probablement une habitude que les programmeurs débutants ont été encouragés à garder afin d'éviter d'oublier de libérer des ressources.
Rei Miyasaka
1

Pour ce que ça vaut, j'ai été surpris de voir que votre exemple ressemble beaucoup aux prédicats compliqués que j'ai écrits. Je suis d'accord avec les autres pour dire qu'un prédicat compliqué n'est pas le meilleur pour la maintenabilité ou la lisibilité, mais il arrive parfois qu'il apparaisse.

Permettez-moi de souligner que vous avez fait cette partie correctement: && a != b NE JAMAIS mettre de connecteur logique à la fin d'une ligne, c'est trop facile de manquer visuellement. Un autre endroit où vous ne devez JAMAIS mettre un opérateur à la fin de la ligne est la concaténation de chaînes, dans les langues avec un tel opérateur.

Faites ceci:

String a = b
   + "something"
   + c
   ;

Ne faites pas ça:

String a = b +
   "something" +
   c;
Bruce Ediger
la source
Avez-vous une logique ou des études soutenant votre affirmation pour les connecteurs logiques, ou est-ce simplement votre préférence déclarée comme un fait? J'ai beaucoup entendu cela à divers endroits et je ne l'ai jamais compris (contrairement aux conditionnels Yoda, qui ont une raison valable [si erronée]).
Caleb Huitt - cjhuitt
@Caleb - Les mathématiques ont été composées de cette façon pendant des siècles. Lors du survol du code, nous nous concentrons sur le côté gauche de chaque ligne. Une ligne commençant par un opérateur est évidemment une continuation de la ligne précédente, et non une nouvelle instruction indentée incorrectement.
kevin cline
J'aime aussi le préfixe des opérateurs mathématiques. Je m'en suis rendu compte trop tard dans ma carrière de programmeur :)
JoJo
0

Si le conditionnel est si compliqué, c'est généralement une indication qu'il doit être divisé en plusieurs parties. Peut-être qu'une clause peut être affectée à une variable intermédiaire. Peut-être qu'une clause peut être transformée en une méthode d'assistance. Je préfère généralement ne pas avoir autant d'and et d'ou sur une même ligne.

Zhehao Mao
la source
0

Vous pouvez diviser le code en plusieurs instructions, ce qui le rend facile à comprendre. Mais un vrai ninja ferait quelque chose comme ça. :-)

if
(
    (
        x == y
    &&
        a != b
    &&
        p.isGood()
    &&
        (
            i + u == b
        ||
            q >= a
        )
    )
||
    k.isSomething()
||
    m > n
)
{
    doSomething();
}
Daniel Lubarov
la source
5
Je suis un fan des espaces blancs, mais cela est excessivement rembourré avec des lignes presque vides à mon goût.
Steve314