Apakah efek samping dalam "setiap" Array, atau "beberapa" buruk?

9

Saya selalu diajari bahwa memiliki efek samping dalam suatu ifkondisi adalah buruk. Yang saya maksud;

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

... sebagai lawan;

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

... dan saya mengerti itu, dan kolega saya senang karena saya tidak melakukannya, dan kami semua pulang pukul 17:00 pada hari Jumat dan semua orang memiliki akhir pekan yang meriah.

Sekarang, ECMAScript5 memperkenalkan metode seperti every()dan some()untuk Array, dan saya menemukan mereka sangat berguna. Mereka lebih bersih daripada for (;;;), memberi Anda ruang lingkup lain, dan membuat elemen dapat diakses oleh variabel.

Namun ketika memvalidasi input, saya lebih sering menemukan diri saya menggunakan every/ somedalam kondisi untuk memvalidasi input, kemudian menggunakan every/ some lagi dalam tubuh untuk mengubah input menjadi model yang dapat digunakan;

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

... ketika apa yang ingin saya lakukan adalah;

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

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

    return res;
})) {
    return;
}

... yang memberi saya efek samping dalam if, kondisi, yang buruk.

Sebagai perbandingan, ini adalah kode yang akan terlihat dengan yang lama for (;;;);

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

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

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

Pertanyaan saya adalah:

  1. Apakah ini benar-benar praktik yang buruk?
  2. Apakah saya (mis | ab) menggunakan somedan every( haruskah saya menggunakan for(;;;)ini?)
  3. Apakah ada pendekatan yang lebih baik?
Ishak
sumber
3
Setiap dan beberapa serta menyaring, memetakan, dan mengurangi permintaan, mereka tidak memiliki efek samping, jika mereka Anda menyalahgunakan mereka.
Benjamin Gruenbaum
@BenjaminGruenbaum: Jadi bukankah itu membuat mereka ompong lebih sering daripada tidak? 9/10, jika saya menggunakan some, saya ingin melakukan sesuatu dengan elemen, jika saya menggunakan every, saya ingin melakukan sesuatu untuk semua elemen itu ... somedan everyjangan biarkan saya mengakses informasi itu, jadi saya tidak bisa gunakan mereka, atau saya harus menambahkan efek samping.
Isaac
Tidak. Ketika saya merujuk efek samping yang saya maksud di dalam kepala jika bukan tubuh. Di dalam tubuh Anda dapat memodifikasinya sesuka Anda. Hanya saja, jangan bermutasi objek di dalam callback Anda melewati beberapa / kapan.
Benjamin Gruenbaum
@BenjaminGruenbaum: Tapi itu maksud saya. Jika saya menggunakan somedi saya ifkondisi untuk menentukan apakah elemen tertentu dalam array pameran properti tertentu, 9/10 saya perlu untuk beroperasi pada elemen di saya iftubuh; sekarang, karena sometidak memberitahu saya yang dari unsur-unsur dipamerkan properti (hanya "satu melakukan"), saya bisa menggunakan some lagi dalam tubuh (O (2n)), atau saya bisa saja melakukan operasi di dalam jika kondisi ( yang buruk, karena efek sampingnya di kepala).
Isaac
... hal yang sama berlaku everyjuga, tentu saja.
Isaac

Jawaban:

8

Jika saya memahami maksud Anda dengan benar, Anda tampaknya salah menggunakan atau menyalahgunakan every, sometetapi itu sedikit tidak dapat dihindari jika Anda ingin mengubah elemen array Anda secara langsung. Koreksi saya jika saya salah, tetapi yang Anda coba lakukan adalah mencari tahu apakah beberapa atau setiap elemen dalam urutan Anda menunjukkan kondisi tertentu kemudian memodifikasi elemen-elemen itu. Juga, kode Anda tampaknya menerapkan sesuatu untuk semua item sampai Anda menemukan satu yang tidak lulus predikat dan saya tidak berpikir itu yang Anda maksudkan. Bagaimanapun.

Mari kita ambil contoh pertama Anda (sedikit dimodifikasi)

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

Apa yang Anda lakukan di sini sebenarnya sedikit bertentangan dengan semangat beberapa / setiap / peta / kurangi / filter / konsep dll. Everytidak dimaksudkan untuk digunakan untuk memengaruhi setiap item yang sesuai dengan sesuatu, melainkan seharusnya hanya digunakan untuk memberi tahu Anda jika setiap item dalam koleksi melakukan hal yang sama. Jika Anda ingin menerapkan fungsi ke semua item yang predikatnya bernilai true, cara "baik" untuk melakukannya adalah

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

Atau, Anda bisa menggunakan foreachalih-alih peta untuk memodifikasi item di tempat.

Logika yang sama berlaku untuk some, pada dasarnya:

  • Anda gunakan everyuntuk menguji apakah semua elemen dalam array lulus beberapa tes.
  • Anda gunakan someuntuk menguji apakah setidaknya satu elemen dalam array melewati beberapa tes.
  • Anda menggunakan mapuntuk mengembalikan array baru yang mengandung 1 elemen (yang merupakan hasil dari fungsi pilihan Anda) untuk setiap elemen dalam array input.
  • Anda menggunakan filteruntuk mengembalikan array panjang 0 < length< initial array lengthelemen, semua yang terkandung dalam array asli dan semua lulus tes predikat disediakan.
  • Anda menggunakan foreachjika Anda ingin peta tetapi di tempat
  • Anda menggunakan reducejika Anda ingin menggabungkan hasil array dalam hasil objek tunggal (yang bisa berupa array tetapi tidak harus).

Semakin banyak Anda menggunakannya (dan semakin banyak Anda menulis kode LISP), semakin Anda menyadari bagaimana mereka terkait dan bagaimana bahkan mungkin untuk meniru / mengimplementasikan satu dengan yang lain. Yang kuat dengan pertanyaan ini dan yang benar-benar menarik adalah semantiknya, dan bagaimana mereka benar-benar mendorong Anda untuk menghilangkan efek samping berbahaya dalam kode Anda.

EDIT (berdasarkan komentar): Jadi katakanlah Anda ingin memvalidasi bahwa setiap elemen adalah objek dan mengubahnya menjadi Model Aplikasi jika semuanya valid. Salah satu cara untuk melakukan ini dalam sekali jalan adalah:

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
}

Dengan cara ini, ketika suatu objek tidak lulus validasi, Anda masih terus mengulangi seluruh array, yang akan lebih lambat daripada hanya memvalidasi dengan every. Namun, sebagian besar waktu array Anda akan valid (atau saya harap begitu), jadi dalam kebanyakan kasus Anda akan melakukan satu melewati array Anda dan berakhir dengan array yang dapat digunakan dari objek Model Aplikasi. Semantik akan dihormati, efek samping dihindari dan semua orang akan senang!

Perhatikan bahwa Anda juga bisa menulis kueri Anda sendiri, mirip dengan foreach, yang akan menerapkan fungsi untuk semua anggota array dan mengembalikan true / false jika mereka semua lulus tes predikat. Sesuatu seperti:

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

Meskipun itu akan mengubah array di tempatnya.

Saya harap ini membantu, sangat menyenangkan untuk menulis. Bersulang!

pwny
sumber
Terima kasih atas jawaban anda. Saya tidak perlu mencoba untuk memodifikasi elemen di tempat per-se; dalam kode aktual saya, saya menerima array objek berformat JSON, jadi saya pertama-tama memvalidasi input if (input.every()), untuk memeriksa bahwa setiap elemen adalah objek ( typeof el === "object && el !== null) dll, maka jika itu valid, saya ingin mengkonversi setiap elemen menjadi Model Aplikasi masing-masing (yang, sekarang Anda sebutkan map()saya bisa menggunakan input.map(function (el) { return new Model(el); });; tetapi tidak harus di tempat .
Isaac
.. tetapi lihat bahwa bahkan dengan map()saya harus mengulangi array dua kali; satu kali untuk memvalidasi, dan satu lagi untuk dikonversi. Namun, dengan menggunakan standar for(;;;)lingkaran, aku bisa melakukan ini dengan menggunakan satu iterasi, tapi aku tidak bisa menemukan cara untuk menerapkan every, some, mapatau filterdalam skenario ini, dan melakukan hanya satu lulus, tanpa tak diinginkan-efek samping atau sebaliknya memperkenalkan buruk- praktek.
Isaac
@ Isaac Baiklah, maaf atas keterlambatannya, saya mengerti situasi Anda lebih jelas sekarang. Saya akan mengedit jawaban saya untuk menambahkan beberapa hal.
pwny
Terima kasih atas jawaban yang bagus; sudah sangat membantu :).
Isaac
-1

Efek samping tidak dalam kondisi if, mereka berada di dalam tubuh if. Anda hanya menentukan apakah akan mengeksekusi tubuh itu dalam kondisi aktual atau tidak. Tidak ada yang salah dengan pendekatan Anda di sini.

DeadMG
sumber
Hai, terima kasih atas jawaban Anda. Maaf, tapi entah saya salah paham jawaban Anda, atau Anda salah mengartikan kode ... semua yang ada dalam cuplikan kode saya berada dalam ifkondisi, dengan hanya returnberada di dalam iftubuh; jelas saya sedang berbicara tentang contoh kode yang didahului oleh " apa yang ingin dilakukan adalah; ...
Isaac
1
Maaf, efek samping @ Issac memang dalam ifkondisi.
Ross Patterson