web-dev-qa-db-fra.com

Simplifier une déclaration if/else?

J'essaie de simplifier ce qui suit:

function handleDirection(src) {
  if (src === 'left') {
    if (inverse) {
      tracker--;
    } else {
      tracker++;
    }
  } else {
    if (inverse) {
      tracker++;
    } else {
      tracker--;
    }
  }
}

pour réduire le nombre de conditions. La src sera toujours 'left' ou 'right'.

28
Rebecca O'Sullivan

Vous pouvez vérifier avec le résultat du premier contrôle.

Ceci est un contrôle exclusif OR.

// typeof inverse === 'boolean'

function handleDirection(src) {
    if (src === 'left' === inverse) {
        tracker--;
    } else {
        tracker++;
    }
}

La vérification évalue l'expression dans cet ordre (src === 'left') === inverse:

src === 'left' === inverse
---- first ---             returns a boolean value
--------- second --------- take result of former check & compairs it with another boolean
48
Nina Scholz
function handleDirection(src) {
   var movement = 1;
   if(src === 'left')
     movement = -1;

   if(inverse)
     tracker += movement;
   else
     tracker -= movement;
}
18
Alays

Vous pouvez même le faire avec une seule ligne de code:

function getDirectionOffset(src) {
  tracker += (src === 'left' ? 1 : -1) * (inverse ? -1 : 1);
}
11
Leuronics

Cela pourrait être simplifié en une expression ternaire qui renvoie 1 ou -1 en fonction de l'état. Ensuite, vous pouvez simplement ajouter cela à la tracker.

function handleDirection(src) {
  var delta = (src === 'left' && inverse) || (src !== 'left' && !inverse) ? -1 : 1;
  tracker += delta;
}

Cela pourrait ensuite être simplifié davantage en utilisant la logique décrite par @NinaScholz dans sa réponse:

function handleDirection(src) {
  var delta = (src === 'left') === inverse ? -1 : 1;
  tracker += delta;
}
7
Rory McCrossan

En supposant que inverse est un drapeau que vous avez défini une fois, vous n'avez pas besoin de le prendre en compte à chaque fois, vous pouvez calculer son impact une fois et simplement l'utiliser tel quel, ce qui réduira vos branches de code et la logique. Si vous souhaitez le modifier au fur et à mesure, vous devrez peut-être séparer la logique de calcul afin de pouvoir la réutiliser.

Vous pouvez également extraire ensuite la direction du mouvement dans une fonction autonome et votre handleDirection devient très simple: vous calculez la direction que vous voulez aller en fonction de src et de la invert.

let tracker = 0;

//extract logic for the movement offset based on direction
function getDirectionOffset(src) {
  return src === 'left' ? 1 : -1;
}

//have a setter for the invert property
function setInverse(isInverse) {
  movementModifier = isInverse ? -1 : 1
}

//declare the variable dependent on the inverse property
let movementModifier;

//initialise movementModifier variable
setInverse(false);

function handleDirection(src) {
  const offset = getDirectionOffset(src) * movementModifier;
  
  tracker += offset;
}


// usage
setInverse(true);

handleDirection("left");
handleDirection("left");
handleDirection("right");

console.log(tracker);

Cela dit, tout cela suggère que vous ne devriez pas utiliser une fonction, ou l’utiliser différemment. Vous pouvez collecter toutes ces fonctionnalités dans une classe ou faire en sorte que toutes les informations soient transmises autour des fonctions, de sorte que vous n'avez pas de globales. Voici un exemple d'implémentation orientée objet du concept:

class TrackerMover {
  constructor(inverse) {
    this.tracker = 0;
    this.movementModifier = inverse ? 1 : -1
  }
  
  handleDirection(src) {
   const offset = this.getDirectionOffset(src) * this.movementModifier;

    this.tracker += offset;
  }
  
  getDirectionOffset(src) {
    return src === 'left' ? -1 : 1;
  }
  
  getPosition() {
    return this.tracker;
  }
}


//usage
const mover = new TrackerMover(true);

mover.handleDirection("left");
mover.handleDirection("left");
mover.handleDirection("right");

console.log(mover.getPosition())

En passant, une autre alternative consiste à NE PAS calculer le mouvement à chaque fois. Vous savez en fait ce qui se passe à chaque fois. En fait, vous avez une table de vérité dans laquelle vos entrées sont src === left et inverse et les sorties indiquent comment vous modifiez votre suivi.

+--------+------------+--------+
| isLeft | isInverted | Offset |
+--------+------------+--------+
| true   | true       |     -1 |
| true   | false      |      1 |
| false  | true       |      1 |
| false  | false      |     -1 |
+--------+------------+--------+

Donc, vous pouvez simplement mettre cette table dans. 

let tracker = 0;
let invert = false;

const movementLookupTable = {
  "true": { },
  "false": { },
}

//it can be initialised as part of the above expression but this is more readable
movementLookupTable[true ][true ] = -1;
movementLookupTable[true ][false] = 1;
movementLookupTable[false][true ] = 1;
movementLookupTable[false][false] = -1;

function handleDirection(src) {
  const offset = movementLookupTable[src === "left"][invert];

  tracker += offset;
}


// usage
invert = true;

handleDirection("left");
handleDirection("left");
handleDirection("right");

console.log(tracker);

Dans ce cas, cela peut être excessif, mais cette approche peut être utile s’il ya plus de drapeaux (y compris plus de valeurs pour les drapeaux) et/ou d’états finaux. Par exemple, vous souhaitez peut-être introduire quatre directions, mais vous ne modifiez pas la valeur tracker s'il s'agit de up ou down

+-----------+------------+--------+
| direction | isInverted | Offset |
+-----------+------------+--------+
| left      | true       |     -1 |
| left      | false      |      1 |
| right     | true       |      1 |
| right     | false      |     -1 |
| up        | false      |      0 |
| up        | true       |      0 |
| down      | false      |      0 |
| down      | true       |      0 |
+-----------+------------+--------+

Comme vous pouvez le constater, il ne s’agit plus uniquement de booléens, vous pouvez gérer n’importe quelle valeur. En utilisant une table, vous changez également invert pour qu'il ressemble à windDirection. Ainsi, si le mouvement est left et que windDirection est right, le résultat est similaire à ce qu'il est maintenant, mais vous pouvez avoir une direction de left et un vent qui tourne left. déplacer plus loin . Ou vous pouvez déplacer up et la direction du vent est left, donc tracker (à ce stade, les coordonnées X) va réellement être modifié.

+-----------+---------------+---------+
| direction | windDirection | OffsetX |
+-----------+---------------+---------+
| left      | right         |      -1 |
| left      | up            |       1 |
| left      | down          |       1 |
| left      | left          |       2 |
| right     | up            |      -1 |
| right     | down          |      -1 |
| right     | right         |      -2 |
| right     | left          |       1 |
| up        | up            |       0 |
| up        | down          |       0 |
| up        | left          |       1 |
| up        | right         |      -1 |
| down      | up            |       0 |
| down      | down          |       0 |
| down      | left          |       1 |
| down      | right         |      -1 |
+-----------+---------------+---------+

Avec quatre directions et quatre directions de vent à prendre en compte, la logique peut être assez gênante à la fois de lire et de maintenir à l’avenir, alors que si vous n’avez qu’une table de consultation, c’est facile et vous pouvez facilement l’étendre pour gérer les diagonales (supposons ils changent la valeur de 0.5 au lieu de 1) et votre algorithme ne s’intéressera pas vraiment tant que vous récupérerez les valeurs de la table.

4
VLAZ

Vous n'avez besoin d'aucune phrase if. La même opération peut être effectuée en calculant un incrément positif ou négatif en fonction de src et inverse À l’aide d’un opérateur ternaire.

function handleDirection(src) {
    tracker += (src == "left" ? 1 : -1) * (inverse ? -1 : 1);
};

Btw. Par souci d'efficacité, je recommanderais d'utiliser directement des incréments/décréments numériques .__ au lieu de chaînes nécessitant un traitement supplémentaire pour être décodées Vous pouvez utiliser des constantes pour obtenir la même lisibilité:

De même, inverse peut être optimisé en tant que valeur numérique commutant entre 1 (pas inversé) Et -1 (inversé).

const left = 1;
const right = -1;
var direction = 1;

function handleDirection(src) {
    tracker += src * direction;
}

function reverse() { // (Example)
    direction = direction * -1;
}

... même si les mots clés "right" et "left" proviennent d'une sorte d'utilisateur textuel , vous pouvez simplement les traduire à partir d'un dictionnaire:

const steps = {
    left = 1;
    right = -1;
};

function handleDirection(src) {
    tracker += steps[src] * direction;
}
3
bitifet

Vous souhaitez augmenter le suivi si l'un des src == left ou inverse est vrai mais pas l'autre, et le diminuer sinon, comme le fait l'opérateur "XOR" ^:

function handleDirection(src) {
    if (src === 'left' ^ inverse) {
        tracker++;
    } else {
        tracker--;
    }
}

Vous pouvez réduire cela davantage en utilisant une expression ternaire:

function handleDirection(src) {
    tracker += src === 'left' ^ inverse ? 1 : -1;
}

Ou si vous voulez éviter tout type de conditionnal, avec des conversions implicites et des arithmétiques "intelligentes":

function handleDirection(src) {
    tracker += 1 - 2 * (src === 'right' ^ inverse); // either 1-0=1 or 1-2=-1
}
3
Aaron

Cela n’a qu’une condition, et je trouve qu’il se lit de manière plus intuitive que les autres réponses:

function handleDirection(src) {
    if (
        ((src === 'left') && !inverse) ||
        ((src === 'right') && inverse)
    ) {
        tracker++;
    }
    else {
        tracker--;
    }
}
3
Paul S

Je n'aime pas les elses et j'essaie d'éviter les nids si possible. Je pense que cela traduit l'idée de inverse d'une manière plus naturelle:

function handleDirection(src) 
{
    let change = 1;

    if ('right' == src)
        change = -1;

    if (inverse)
        change = -change;

    tracker += change;
}
2
Džuris

Vous pouvez utiliser la syntaxe de court-circuit ou les opérateurs ternaires 

// by using short circuiting
    function handleDirection(src) {
       if (src == 'left') tracker = inverse && tracker-1 || tracker +1
       else  tracker = inverse && tracker+1 || tracker -1
    }
// by using ternary operator
 function handleDirection(src) {
       if (src == 'left') tracker = inverse ? tracker-1 : tracker +1
       else  tracker = inverse ? tracker+1 : tracker -1
    }
2
Komal Bansal

Je sais que cela ne règle pas directement la question par une "simplification" directe, mais je voudrais vous fournir une réponse qui aborde plusieurs problèmes de qualité du code tout en rendant le code plus lisible.

À propos des effets secondaires

Tout d'abord, cette fonction donnée mute des valeurs externes. Ceci introduit le problème de effets secondaires :

  • La fonction modifie un état externe qui peut ne pas être traité dans l'environnement extérieur et peut donc conduire à un comportement indéfini.
  • La fonction elle-même est alors liée à l'état externe, ce qui rend difficile le changement de code et de refactorisation.

Il est également beaucoup plus difficile de tester une telle fonction, car vous devez d'abord "créer l'environnement d'état" pour exécuter le test.

Un premier Tweak facile serait de faire accepter toutes les valeurs externes par paramètre et de renvoyer soit une valeur 1, soit -1 attribuée à quelque chose (dans votre cas, tracker).

Exclusif ou conditionnel avec des chaînes

Deuxièmement, l'utilisation d'un if/else on String avec des valeurs exclusives ou peut conduire à un état non défini dans lequel src pourrait être autre chose que 'right', mais la fonction se comporterait comme si elle était 'right'. Au lieu de cela, il devrait lancer une exception. L'utilisation d'un commutateur est d'une aide précieuse ici.

Application de ces points à la fonction

Si les points ci-dessus sont pris en compte, la fonction globale ressemblerait à ceci:

function handleDirection (src, inverse) {
  switch (src) {
    case 'left':
      return inverse ? -1 :  1
    case 'right':
      return inverse ?  1 : -1
    default:
      throw new Error(`Unknown src: ${src}`)
  }
}

et vous pouvez facilement tester cette fonction:

handleDirection('left' , true)  // -1
handleDirection('left' , false) //  1
handleDirection('right', true)  //  1
handleDirection('right', false) // -1
handleDirection('middle',true)  // Error: Unknown src: middle

Désormais, la fonction est clairement découplée de tracker (pensez à votre temps précieux lors de la refactorisation), mais en outre, son rôle est parfaitement clair.

Remarque

En résumé, je voulais souligner qu’il ne s’agit pas toujours d’écrire le code le plus simple avec le moins de lignes mais un code qui est clair à lire/comprendre et à maintenir. Ce n’est pas aussi court que la plupart des solutions proposées, mais tout le monde devrait immédiatement comprendre ce qu’il fait.

0
Jankapunkt

Actuellement, vous comparez sur des chaînes, ce que je ne conseillerais pas. Si, par exemple, vous utilisez 'Left' au lieu de 'left', la première instruction if échouera. Peut-être qu'un booléen pourrait être utile ici, puisque vous pouvez garantir qu'il n'a que deux états.

Les instructions if peuvent être compressées via opérateurs conditionnels .

Peut-être que quelque chose comme ceci est ce que vous recherchez:

function handleDirection(src) {
  if (src) {
    inverse ? tracker-- : tracker++;
  } else {
    inverse ? tracker++ : tracker--;
  }
}

Voir: https://jsfiddle.net/9zr4f3nv/

0
MagicLegend