Sind Nebenwirkungen in Arrays "jeder" oder "einige" schlecht?

9

Mir wurde immer beigebracht, dass Nebenwirkungen in einem ifZustand schlecht sind. Was ich meine ist;

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

... im Gegensatz zu;

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

... und ich verstehe das, und meine Kollegen sind glücklich, weil ich es nicht tue, und wir gehen alle freitags um 17:00 Uhr nach Hause und alle haben ein fröhliches Wochenende.

Jetzt hat ECMAScript5 Methoden wie every()und some()zu eingeführt Array, und ich finde sie sehr nützlich. Sie sind sauberer als for (;;;)die anderen, geben Ihnen einen anderen Bereich und machen das Element durch eine Variable zugänglich.

Bei der Validierung von Eingaben verwende ich jedoch häufig every/ somein der Bedingung, die Eingabe zu validieren, und verwende dann every/ some erneut im Body, um die Eingabe in ein verwendbares Modell umzuwandeln.

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

... wenn ich etwas tun möchte ;

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

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

    return res;
})) {
    return;
}

... was mir Nebenwirkungen in einem ifZustand gibt, der schlecht ist.

Im Vergleich dazu ist dies der Code, mit dem ein alter Code aussehen würde for (;;;).

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

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

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

Meine Fragen sind:

  1. Ist das definitiv eine schlechte Praxis?
  2. Benutze ich (mis | ab) someund every( sollte ich dafür ein verwenden for(;;;)?)
  3. Gibt es einen besseren Ansatz?
Isaac
quelle
3
Alle sowie einige Filter, Zuordnungen und Reduzierungen sind Abfragen. Sie haben keine Nebenwirkungen, wenn Sie sie missbrauchen.
Benjamin Gruenbaum
@BenjaminGruenbaum: Macht sie das nicht öfter zahnlos? 9/10, wenn ich benutze some, möchte ich etwas mit dem Element machen, wenn ich benutze every, möchte ich all diesen Elementen etwas antun ... someund everymich nicht auf diese Informationen zugreifen lassen, also kann ich es auch nicht benutze sie, oder ich muss Nebenwirkungen hinzufügen.
Isaac
Nein. Wenn ich mich auf Nebenwirkungen beziehe, meine ich im Kopf des Körpers, wenn nicht im Körper. Innerhalb des Körpers können Sie es beliebig ändern. Mutieren Sie das Objekt innerhalb des Rückrufs, den Sie an einige / wann übergeben, nur nicht.
Benjamin Gruenbaum
@BenjaminGruenbaum: Aber genau das ist mein Punkt. Wenn ich somein meinem ifZustand , um zu bestimmen , ob ein bestimmtes Element in dem Array eine bestimmte Eigenschaft aufweist, 9/10 Ich muß zum Betrieb auf diesem Elemente in meinem ifKörper; Jetzt, da somemir nicht gesagt wird, welches der Elemente die Eigenschaft aufweist (nur "eines hat es getan"), kann ich es entweder some wieder im Körper verwenden (O (2n)) oder ich kann die Operation einfach innerhalb der if- Bedingung ausführen ( was schlecht ist, weil es eine Nebenwirkung im Kopf ist).
Isaac
... das gilt everynatürlich auch für.
Isaac

Antworten:

8

Wenn ich Ihren Standpunkt richtig verstehe, scheinen Sie ihn zu missbrauchen oder zu missbrauchen, everyund somees ist ein wenig unvermeidlich, wenn Sie die Elemente Ihrer Arrays direkt ändern möchten. Korrigieren Sie mich, wenn ich falsch liege, aber Sie versuchen herauszufinden, ob einige oder jedes Element in Ihrer Sequenz eine bestimmte Bedingung aufweist, und ändern Sie diese Elemente. Außerdem scheint Ihr Code etwas auf alle Elemente anzuwenden, bis Sie eines finden, das das Prädikat nicht erfüllt, und ich glaube nicht, dass Sie dies beabsichtigen. Sowieso.

Nehmen wir Ihr erstes Beispiel (leicht modifiziert)

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

Was Sie hier tun, widerspricht tatsächlich ein wenig dem Geist einiger / jeder / map / redu / filter / etc-Konzepte. Everysoll nicht verwendet werden, um jeden Gegenstand zu beeinflussen, der etwas entspricht, sondern sollte nur verwendet werden, um Ihnen mitzuteilen, ob jeder Gegenstand in einer Sammlung dies tut. Wenn Sie eine Funktion auf alle Elemente anwenden möchten, für die ein Prädikat den Wert true hat, ist dies der "gute" Weg

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.
});

Alternativ können Sie foreachanstelle der Karte die Elemente an Ort und Stelle ändern.

Die gleiche Logik gilt im someGrunde für:

  • Sie everytesten, ob alle Elemente in einem Array einen Test bestehen.
  • Sie sometesten, ob mindestens ein Element in einem Array einen Test besteht.
  • Sie geben mapfür jedes Element in einem Eingabearray ein neues Array zurück, das 1 Element enthält (das Ergebnis einer Funktion Ihrer Wahl).
  • Sie verwenden filterein Array der Länge 0 <zurück length< initial array lengthElemente, die alle in der ursprünglichen Anordnung enthalten und alle mitgelieferten Prädikat Test bestanden.
  • Sie verwenden, foreachwenn Sie Karte aber an Ort und Stelle möchten
  • Sie verwenden reducediese Option, wenn Sie die Ergebnisse eines Arrays in einem einzelnen Objektergebnis kombinieren möchten (das ein Array sein kann, aber nicht muss).

Je häufiger Sie sie verwenden (und je mehr Sie LISP-Code schreiben), desto mehr erkennen Sie, wie sie zusammenhängen und wie es sogar möglich ist, eine mit den anderen zu emulieren / zu implementieren. Was bei diesen Abfragen mächtig und wirklich interessant ist, ist ihre Semantik und wie sie Sie wirklich dazu bringen, schädliche Nebenwirkungen in Ihrem Code zu beseitigen.

BEARBEITEN (im Lichte von Kommentaren): Angenommen, Sie möchten überprüfen, ob jedes Element ein Objekt ist, und sie in ein Anwendungsmodell konvertieren, wenn alle gültig sind. Eine Möglichkeit, dies in einem einzigen Durchgang zu tun, wäre:

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
}

Auf diese Weise durchlaufen Sie immer noch das gesamte Array, wenn ein Objekt die Validierung nicht besteht. Dies wäre langsamer als nur die Validierung mit every. In den meisten Fällen ist Ihr Array jedoch gültig (oder ich sollte es hoffen). In den meisten Fällen führen Sie einen einzelnen Durchlauf über Ihr Array durch und erhalten ein verwendbares Array von Anwendungsmodellobjekten. Die Semantik wird respektiert, Nebenwirkungen vermieden und alle werden glücklich sein!

Beachten Sie, dass Sie ähnlich wie foreach auch eine eigene Abfrage schreiben können, die eine Funktion auf alle Mitglieder eines Arrays anwendet und true / false zurückgibt, wenn alle einen Prädikattest bestehen. So etwas wie:

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;
}

Obwohl dies das Array an Ort und Stelle ändern würde.

Ich hoffe das hilft, es hat sehr viel Spaß gemacht zu schreiben. Prost!

pwny
quelle
Danke für deine Antwort. Ich bin nicht unbedingt versuchen , die Elemente zu ändern , an Ort und Stelle per se; in meinem eigentlichen Code, Ich erhalte ein JSON-Format Array von Objekten, so dass ich zunächst bin Validierung die Eingabe if (input.every()), dass jedes Element zu überprüfen , ist ein Objekt ( typeof el === "object && el !== null) etc, dann , wenn dass validates, ich jedes Element konvertieren will in die jeweiligen Anwendungsmodell (die jetzt erwähnen Sie map()ich verwenden könnte input.map(function (el) { return new Model(el); });, aber nicht unbedingt an Ort und Stelle .
Isaac
.. aber sehen Sie, dass map()ich auch dann zweimal über das Array iterieren muss; einmal zu validieren und ein anderes zu konvertieren. Um jedoch einen Standard mit for(;;;)Schleife, könnte ich tue dies mit einer Iteration, aber ich kann nicht einen Weg finden , gilt every, some, mapoder filterin diesem Szenario und führen nur einen Pass, ohne dass unerwünschte-Nebenwirkungen oder auf andere Weise der Einführung Bad- trainieren.
Isaac
@Isaac Okay, entschuldige die Verzögerung, ich verstehe deine Situation jetzt klarer. Ich werde meine Antwort bearbeiten, um ein paar Sachen hinzuzufügen.
pwny
Danke für die tolle Antwort; es war wirklich hilfreich :).
Isaac
-1

Die Nebenwirkungen sind nicht im if-Zustand, sondern im if-Körper. Sie haben nur festgelegt, ob dieser Körper im tatsächlichen Zustand ausgeführt werden soll oder nicht. An Ihrem Ansatz ist hier nichts auszusetzen.

DeadMG
quelle
Hallo, danke für deine Antwort. Entschuldigung, aber entweder habe ich Ihre Antwort falsch verstanden oder Sie haben den Code falsch interpretiert ... alles in meinem Code-Snippet befindet sich innerhalb des ifZustands, nur das returnWesen befindet sich im ifKörper des Körpers. Offensichtlich spreche ich über das Codebeispiel, dem vorangestellt ist: " Was ich tun möchte, ist: ...
Isaac
1
Entschuldigung, @ Issacs Nebenwirkungen sind in der Tat in dem ifZustand.
Ross Patterson