Les effets secondaires dans «tous» ou «certains» d'Array sont-ils mauvais?

9

On m'a toujours appris qu'avoir des effets secondaires dans une ifcondition était mauvais. Ce que je veux dire est;

if (conditionThenHandle()) {
    // do effectively nothing
}

... par opposition à;

if (condition()) {
    handle();
}

... et je comprends cela, et mes collègues sont heureux parce que je ne le fais pas, et nous rentrons tous chez nous à 17h00 un vendredi et tout le monde a un joyeux week-end.

Maintenant, ECMAScript5 a introduit des méthodes comme every()et some()à Array, et je les trouve très utiles. Ils sont plus propres que for (;;;)les autres, vous donnent une autre portée et rendent l'élément accessible par une variable.

Cependant, lors de la validation de l'entrée, je me retrouve le plus souvent à utiliser every/ somedans la condition pour valider l'entrée, puis à utiliser every/ à some nouveau dans le corps pour convertir l'entrée en un modèle utilisable;

if (input.every(function (that) {
    return typeof that === "number";
})) {
    input.every(function (that) {
        // Model.findById(that); etc
    }
} else {
    return;
}

... quand ce que je veux faire, c'est;

if (!input.every(function (that) {
    var res = typeof that === "number";

    if (res) {
        // Model.findById(that); etc.
    }

    return res;
})) {
    return;
}

... ce qui me donne des effets secondaires dans un ifétat, ce qui est mauvais.

En comparaison, c'est le code qui ressemblerait à un ancien for (;;;);

for (var i=0;i<input.length;i++) {
    var curr = input[i];

    if (typeof curr === "number") {
        return;
    }

    // Model.findById(curr); etc.
}

Mes questions sont:

  1. Est-ce définitivement une mauvaise pratique?
  2. Suis-je (mis | ab) en utilisant someet every( devrais- je utiliser un for(;;;)pour cela?)
  3. Est-ce qu'il y a une meilleure approche?
Isaac
la source
3
Toutes et certaines ainsi que filtrer, mapper et réduire sont des requêtes, elles n'ont aucun effet secondaire, si vous les abusez.
Benjamin Gruenbaum
@BenjaminGruenbaum: Alors, cela ne les rend-ils pas édentés le plus souvent? 9/10, si j'utilise some, je veux faire quelque chose avec l'élément, si j'utilise every, je veux faire quelque chose avec tous ces éléments ... someet everyne me laisse pas accéder à cette information, donc soit je ne peux pas les utiliser, ou je dois ajouter des effets secondaires.
Isaac
Non. Quand je parle d'effets secondaires, je veux dire à l'intérieur de la tête, sinon du corps. À l'intérieur du corps, vous pouvez le modifier comme vous le souhaitez. Il suffit de ne pas muter l'objet à l'intérieur du rappel que vous passez à certains / quand.
Benjamin Gruenbaum
@BenjaminGruenbaum: Mais c'est exactement mon point. Si je l' utilise somedans mon ifétat pour déterminer si un certain élément du tableau présente une propriété, 9/10 je dois fonctionner sur cet élément dans mon ifcorps; maintenant, comme somene me dites pas que des éléments exposés la propriété (juste « un fait »), je peux soit utiliser à some nouveau dans le corps (O (2n)), ou je peux effectuer l'opération à l' intérieur du si la condition ( ce qui est mauvais, car c'est un effet secondaire dans la tête).
Isaac
... il en va de everymême, bien sûr.
Isaac

Réponses:

8

Si je comprends bien votre point, vous semblez mal utiliser ou abuser every, somemais c'est un peu inévitable si vous voulez changer directement les éléments de vos tableaux. Corrigez-moi si je me trompe, mais ce que vous essayez de faire est de savoir si certains ou tous les éléments de votre séquence présentent une certaine condition, puis modifiez ces éléments. De plus, votre code semble appliquer quelque chose à tous les éléments jusqu'à ce que vous en trouviez un qui ne passe pas le prédicat et je ne pense pas que c'est ce que vous voulez faire. Bref.

Prenons votre premier exemple (légèrement modifié)

if (input.every(function (that) {
    return typeof that === "number";
})) {
    input.every(function (that) {
        that.foo();
    }
} else {
    return;
}

Ce que vous faites ici va en fait un peu à l'encontre de l'esprit des concepts some / every / map / réduire / filtre / etc. Everyn'est pas censé être utilisé pour affecter chaque élément conforme à quelque chose, mais doit uniquement être utilisé pour vous dire si chaque élément d'une collection le fait. Si vous souhaitez appliquer une fonction à tous les éléments pour lesquels un prédicat est évalué comme vrai, la "bonne" façon de le faire est

var filtered = array.filter(function(item) {
    return typeof item === "number";
});

var mapped = filtered.map(function(item) {
    return item.foo(); //provided foo() has no side effects and returns a new object of item's type instead.  See note about foreach below.
});

Alternativement, vous pouvez utiliser foreachau lieu de la carte pour modifier les éléments sur place.

La même logique s'applique some, essentiellement:

  • Vous utilisez everypour tester si tous les éléments d'un tableau réussissent un test.
  • Vous utilisez somepour tester si au moins un élément d'un tableau réussit un test.
  • Vous utilisez mappour renvoyer un nouveau tableau contenant 1 élément (qui est le résultat d'une fonction de votre choix) pour chaque élément d'un tableau d'entrée.
  • Vous utilisez filterpour retourner un tableau de longueur 0 < length< initial array lengthéléments, tous contenus dans le tableau original et tous passer le test sous - jacente fournie.
  • Vous utilisez foreachsi vous voulez une carte mais en place
  • Vous utilisez reducesi vous souhaitez combiner les résultats d'un tableau dans un résultat d'objet unique (qui pourrait être un tableau mais pas nécessairement).

Plus vous les utilisez (et plus vous écrivez de code LISP), plus vous réalisez comment ils sont liés et comment il est même possible d'émuler / implémenter l'un avec les autres. Ce qui est puissant avec ces requêtes et ce qui est vraiment intéressant, c'est leur sémantique, et comment elles vous poussent vraiment à éliminer les effets secondaires nuisibles dans votre code.

EDIT (à la lumière des commentaires): disons que vous voulez valider que chaque élément est un objet et les convertir en modèle d'application s'ils sont tous valides. Une façon de le faire en un seul passage serait:

var dirty = false;
var app_domain_objects = input.map(function(item) {
    if(validate(item)) {
        return new Model(item);
    } else {
        dirty = true; //dirty is captured by the function passed to map, but you know that :)
    }
});
if(dirty) {
    //your validation test failed, do w/e you need to
} else {
    //You can use app_domain_objects
}

De cette façon, lorsqu'un objet ne passe pas la validation, vous continuez à parcourir le tableau entier, ce qui serait plus lent que la simple validation avec every. Cependant, la plupart du temps, votre tableau sera valide (ou je l'espère), donc dans la plupart des cas, vous effectuerez un seul passage sur votre tableau et vous vous retrouverez avec un tableau utilisable d'objets de modèle d'application. La sémantique sera respectée, les effets secondaires évités et tout le monde sera content!

Notez que vous pouvez également écrire votre propre requête, similaire à foreach, qui appliquerait une fonction à tous les membres d'un tableau et renvoie true / false s'ils passent tous un test de prédicat. Quelque chose comme:

function apply_to_every(arr, predicate, func) {
    var passed = true;
    for(var i = 0; i < array.length; ++i) {
        if(predicate(arr[i])) {
            func(arr[i]);
        } else {
            passed = false;
            break;
        }
    }
    return passed;
}

Bien que cela modifierait le tableau en place.

J'espère que cela aide, c'était très amusant à écrire. À votre santé!

pwny
la source
Merci pour votre réponse. Je n'essaie pas nécessairement de modifier les éléments en place en soi; dans mon code actuel, je reçois un tableau d'objets au format JSON, donc je valide d' abord l'entrée if (input.every()), pour vérifier que chaque élément est un objet ( typeof el === "object && el !== null) etc., puis si cela valide, je veux convertir chaque élément en le modèle d'application respectif (que vous mentionnez maintenant que map()je pourrais utiliser input.map(function (el) { return new Model(el); });, mais pas nécessairement en place .
Isaac
.. mais voyez que même avec map()je dois répéter deux fois le tableau; une fois pour valider et une autre pour convertir. Cependant, en utilisant une norme en for(;;;)boucle, je pouvais le faire en utilisant une itération, mais je ne peux pas trouver un moyen d'appliquer every, some, mapou filterdans ce scénario, et d' exécuter seulement une passe, sans avoir effets secondaires indésirables ou l' introduction d' ailleurs mauvais- entraine toi.
Isaac
@Isaac Très bien, désolé pour le retard, je comprends mieux votre situation maintenant. Je vais modifier ma réponse pour ajouter des trucs.
pwny
Merci pour la bonne réponse; ça a été vraiment utile :).
Isaac
-1

Les effets secondaires ne sont pas dans l'état if, ils sont dans le corps de l'if. Vous avez uniquement déterminé si vous souhaitez ou non exécuter ce corps dans l'état réel. Il n'y a rien de mal à votre approche ici.

DeadMG
la source
Salut, merci pour votre réponse. Désolé, mais soit j'ai mal compris votre réponse, soit vous avez mal interprété le code ... tout dans mon extrait de code est dans la ifcondition, avec seulement l' returnêtre à l'intérieur du ifcorps de; de toute évidence, je parle de l'exemple de code précédé de " ce que vous voulez faire est; ...
Isaac
1
Désolé, les effets secondaires de @ Issac sont en effet dans l' ifétat.
Ross Patterson