Gli effetti collaterali nell'array "ogni" o "alcuni" sono negativi?


9

Mi è sempre stato insegnato che avere effetti collaterali in una ifcondizione è negativo. Ciò che voglio dire è;

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

... al contrario di;

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

... e lo capisco, e i miei colleghi sono felici perché non lo faccio, e tutti andiamo a casa alle 17:00 di venerdì e tutti hanno un buon fine settimana.

Ora, ECMAScript5 ha introdotto metodi come every()e some()to Array, e li trovo molto utili. Sono più puliti di quelli for (;;;), ti offrono un altro scopo e rendono l'elemento accessibile da una variabile.

Tuttavia, durante la convalida dell'input, il più delle volte mi trovo a utilizzare every/ somenella condizione per convalidare l'input, quindi uso every/ di some nuovo nel corpo per convertire l'input in un modello utilizzabile;

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

... quando quello che voglio fare è;

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

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

    return res;
})) {
    return;
}

... che mi dà effetti collaterali in una ifcondizione, che è male.

In confronto, questo è il codice che guarderebbe con un vecchio for (;;;);

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

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

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

Le mie domande sono:

  1. È sicuramente una cattiva pratica?
  2. Sto usando (mis | ab) somee every( dovrei usare a for(;;;)per questo?)
  3. C'è un approccio migliore?

3
Tutti e alcuni oltre a filtro, mappa e riduzione sono query, non hanno effetti collaterali, se lo fanno li stai abusando.
Benjamin Gruenbaum,

@BenjaminGruenbaum: Quindi questo non li rende sdentati più spesso? 9/10, se uso some, voglio fare qualcosa con l'elemento, se uso every, voglio fare qualcosa con tutti quegli elementi ... somee everynon farmi accedere a tali informazioni, quindi neanche io usali, o devo aggiungere effetti collaterali.
Isaac,

No. Quando mi riferisco agli effetti collaterali intendo all'interno della testa del se non il corpo. All'interno del corpo puoi modificarlo come preferisci. Basta non mutare l'oggetto all'interno del callback che si passa ad alcuni / quando.
Benjamin Gruenbaum,

@BenjaminGruenbaum: Ma questo è esattamente il mio punto. Se uso somenella mia ifcondizione per determinare se un determinato elemento nella matrice presenta una certa proprietà, 9/10 devo operare su quell'elemento nel mio ifcorpo; ora, poiché somenon mi dice quale degli elementi esponesse la proprietà (solo "uno ha fatto"), posso usare di some nuovo nel corpo (O (2n)), oppure posso semplicemente eseguire l'operazione all'interno della condizione if ( che è male, perché è un effetto collaterale all'interno della testa).
Isaac,

... lo stesso vale anche per every, ovviamente.
Isaac,

Risposte:


8

Se ho capito bene il vostro punto, ti sembra di essere mis-utilizzando o abusare everye somema è un po 'inevitabile se si desidera modificare gli elementi del tuo array direttamente. Correggimi se sbaglio, ma quello che stai cercando di fare è scoprire se alcuni o tutti gli elementi della sequenza presentano una certa condizione, quindi modificali. Inoltre, il tuo codice sembra applicare qualcosa a tutti gli elementi fino a quando non ne trovi uno che non supera il predicato e non credo sia quello che intendi fare. In ogni modo.

Facciamo il tuo primo esempio (leggermente modificato)

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

Quello che stai facendo qui va in realtà un po 'contro lo spirito dei concetti alcuni / ogni / mappa / riduci / filtro / ecc. Everynon è pensato per influire su tutti gli articoli conformi a qualcosa, ma dovrebbe essere usato solo per dirti se tutti gli articoli di una collezione lo fanno. Se si desidera applicare una funzione a tutti gli elementi per i quali un predicato viene valutato come vero, il modo "buono" per farlo è

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

In alternativa, è possibile utilizzare foreachanziché la mappa per modificare gli elementi sul posto.

La stessa logica si applica some, sostanzialmente:

  • Si utilizza everyper verificare se tutti gli elementi in un array superano alcuni test.
  • Si utilizza someper verificare se almeno un elemento in un array supera un test.
  • Si utilizza mapper restituire un nuovo array contenente 1 elemento (che è il risultato di una funzione di propria scelta) per ogni elemento in un array di input.
  • Si utilizza filterper restituire una matrice di lunghezza 0 < length< initial array lengthelementi, tutti contenuti nell'array originale e tutto il superamento della prova predicato dotazione.
  • Si utilizza foreachse si desidera la mappa ma sul posto
  • Si utilizza reducese si desidera combinare i risultati di un array in un singolo oggetto (che potrebbe essere un array ma non è necessario).

Più li usi (e più scrivi il codice LISP), più ti rendi conto di come sono correlati e di come sia possibile emulare / implementare uno con gli altri. Ciò che è potente con queste query e ciò che è veramente interessante è la loro semantica e come ti spingono davvero verso l'eliminazione di effetti collaterali dannosi nel tuo codice.

EDIT (alla luce dei commenti): supponiamo quindi che tu voglia convalidare che ogni elemento è un oggetto e convertirli in un modello applicativo se sono tutti validi. Un modo per farlo in un unico passaggio sarebbe:

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
}

In questo modo, quando un oggetto non supera la convalida, si continua a iterare attraverso l'intero array, il che sarebbe più lento della semplice convalida every. Tuttavia, la maggior parte delle volte l'array sarà valido (o dovrei sperarlo), quindi nella maggior parte dei casi eseguirai un singolo passaggio sull'array e finirai con un array utilizzabile di oggetti Modello di applicazione. La semantica sarà rispettata, gli effetti collaterali evitati e tutti saranno felici!

Nota che potresti anche scrivere la tua query, simile a foreach, che applicherebbe una funzione a tutti i membri di un array e restituisce vero / falso se tutti superano un test del predicato. Qualcosa di simile a:

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

Sebbene ciò modificherebbe l'array in atto.

Spero che questo aiuti, è stato molto divertente scrivere. Saluti!


Grazie per la tua risposta. Non sto necessariamente cercando di modificare gli elementi in atto di per sé; nel mio codice attuale, sto ricevendo un array di oggetti in formato JSON, quindi per prima cosa convalido l'input if (input.every()), per verificare che ogni elemento sia un oggetto ( typeof el === "object && el !== null) ecc., quindi , se questo viene convalidato, voglio convertire ogni elemento in il rispettivo modello di applicazione (che, ora mi dici map()che potrei usare input.map(function (el) { return new Model(el); });; ma non necessariamente sul posto .
Isaac,

.. ma vedi che anche con map()devo ripetere due volte l'array; una volta per convalidare e un altro per convertire. Tuttavia, utilizzando uno standard for(;;;)ciclo, ho potuto fare questo utilizza uno iterazione, ma non riesco a trovare un modo per applicare every, some, mapo filterin questo scenario, ed eseguire solo un passaggio, senza avere indesiderati effetti collaterali-o altrimenti introducendo Bad- pratica.
Isacco,

@Isaac Va bene, scusa per il ritardo, capisco la tua situazione più chiaramente ora. Modificherò la mia risposta per aggiungere alcune cose.
pwny

Grazie per la magnifica risposta; è stato davvero utile :).
Isaac,

-1

Gli effetti collaterali non sono nella condizione if, sono nel corpo dell'if. Hai solo determinato se eseguire o meno quel corpo nelle condizioni reali. Non c'è niente di sbagliato nel tuo approccio qui.


Ciao, grazie per la tua risposta. Scusa, o o ho frainteso la tua risposta, o hai frainteso il codice ... tutto nel mio frammento di codice è nella ifcondizione, con solo l' returnessere all'interno del ifcorpo del; ovviamente sto parlando dell'esempio di codice preceduto da " ciò che si vuole fare è; ...
Isaac

1
Siamo spiacenti, gli effetti collaterali di @ Issac sono davvero nelle ifcondizioni.
Ross Patterson,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.