Haruskah saya mengikuti jalur normal atau gagal lebih awal?

73

Dari Kode Lengkap buku ini muncul kutipan berikut:

"Letakkan case normal setelah ifdaripada daripada else"

Yang berarti bahwa pengecualian / penyimpangan dari jalur standar harus dimasukkan ke dalam elsecase.

Tetapi The Pragmatic Programmer mengajarkan kita untuk "crash early" (hlm. 120).

Aturan mana yang harus saya ikuti?

jao
sumber
15
@gnat bukan duplikat
BЈовић
16
keduanya tidak saling eksklusif, tidak ada ambiguitas.
jwenting
6
Saya tidak begitu yakin kutipan kode-lengkap adalah nasihat yang luar biasa. Mungkin ini merupakan upaya untuk meningkatkan keterbacaan, tetapi tentu saja ada situasi di mana Anda ingin kasus yang tidak biasa menjadi yang pertama secara tekstual. Perhatikan bahwa ini bukan jawaban; jawaban yang ada sudah menjelaskan kebingungan pertanyaan Anda.
Eamon Nerbonne
14
Kecelakaan awal, menabrak keras! Jika salah satu ifcabang Anda kembali, gunakan dulu. Dan hindari elseuntuk sisa kode, Anda sudah kembali jika pra-kondisi gagal. Kode lebih mudah dibaca, lebih sedikit indentasi ...
CodeAngry
5
Apakah hanya saya yang berpikir ini tidak ada hubungannya dengan keterbacaan, tetapi sebaliknya mungkin hanya upaya yang salah untuk mengoptimalkan prediksi cabang statis?
Mehrdad

Jawaban:

189

"Crash early" bukan tentang baris kode mana yang datang lebih awal secara tekstual. Ini memberitahu Anda untuk mendeteksi kesalahan pada langkah paling awal yang mungkin dari pemrosesan , sehingga Anda tidak secara tidak sengaja membuat keputusan dan perhitungan berdasarkan keadaan yang sudah salah.

Dalam if/ elsekonstruksi, hanya satu dari blok yang dieksekusi, jadi tidak ada yang bisa dikatakan merupakan langkah "awal" atau "nanti". Oleh karena itu cara memesannya adalah masalah keterbacaan, dan "gagal lebih awal" tidak masuk ke dalam keputusan.

Kilian Foth
sumber
2
Poin umum Anda bagus tetapi saya melihat satu masalah. Jika Anda memiliki (if / else if / else) maka ekspresi dievaluasi dalam "else if" dievaluasi setelah ekspresi dalam pernyataan "jika". Bit penting, seperti yang Anda tunjukkan, adalah keterbacaan vs unit pemrosesan konseptual. Hanya satu blok kode yang dieksekusi tetapi beberapa kondisi dapat dievaluasi.
Encaitar
7
@Encaitar, tingkat granularitas itu jauh lebih kecil dari apa yang umumnya dimaksudkan ketika frasa "gagal awal" digunakan.
riwalk
2
@Encaitar Itu adalah seni pemrograman. Saat dihadapkan dengan beberapa pemeriksaan, idenya adalah mencoba yang paling mungkin benar terlebih dahulu. Namun, informasi itu mungkin tidak diketahui pada waktu desain tetapi pada tahap optimisasi, tetapi waspadai optimasi prematur.
BPugh
Komentar yang adil. Ini adalah jawaban yang bagus dan saya hanya ingin mencoba membuatnya lebih baik untuk referensi di masa mendatang
Encaitar
Bahasa scripting seperti JavaScript, Python, perl, PHP, bash, dll. Adalah pengecualian karena mereka ditafsirkan secara linear. Dalam if/elsekonstruksi kecil mungkin tidak masalah. Tetapi yang dipanggil dalam satu lingkaran atau dengan banyak pernyataan di setiap blok bisa berjalan lebih cepat dengan kondisi paling umum terlebih dahulu.
DocSalvager
116

Jika elsepernyataan Anda hanya berisi kode kegagalan, maka kemungkinan besar tidak seharusnya ada di sana.

Alih-alih melakukan ini:

if file.exists() :
  if validate(file) :
    # do stuff with file...
  else :
    throw foodAtMummy
else :
  throw toysOutOfPram

melakukan hal ini

if not file.exists() :
  throw toysOutOfPram

if not validate(file) :
  throw foodAtMummy

# do stuff with file...

Anda tidak ingin membuat sarang kode Anda secara mendalam hanya untuk memasukkan pengecekan kesalahan.

Dan, seperti yang telah dinyatakan semua orang, kedua nasihat itu tidak saling bertentangan. Salah satunya adalah tentang urutan eksekusi , yang lain adalah tentang urutan kode .

Jack Aidley
sumber
4
Patut diperjelas bahwa saran untuk menempatkan aliran normal di blok setelah ifdan aliran luar biasa di blok setelah elsetidak berlaku jika Anda tidak memiliki else! Pernyataan penjaga seperti ini adalah bentuk yang disukai untuk menangani kondisi kesalahan di sebagian besar gaya pengkodean.
Jules
+1 ini adalah poin yang bagus dan benar-benar memberikan jawaban atas pertanyaan sebenarnya tentang cara memesan barang dengan kondisi kesalahan.
ashes999
Jelas jauh lebih jelas dan mudah dirawat. Inilah yang saya suka lakukan.
John
27

Anda harus mengikuti keduanya.

"Crash early" / fail advice awal berarti Anda harus menguji input Anda untuk kemungkinan kesalahan sesegera mungkin.
Misalnya, jika metode Anda menerima ukuran atau jumlah yang seharusnya positif (> 0), maka saran awal gagal berarti Anda menguji kondisi itu tepat di awal metode Anda daripada menunggu algoritma menghasilkan omong kosong hasil.

Saran untuk menempatkan kasus normal terlebih dahulu berarti bahwa jika Anda menguji suatu kondisi, maka jalan yang paling mungkin harus didahulukan. Ini membantu dalam kinerja (karena prediksi cabang prosesor akan benar lebih sering) dan mudah dibaca, karena Anda tidak harus melewati blok kode ketika mencoba mencari tahu apa fungsi yang dilakukan dalam kasus normal.
Saran ini tidak benar-benar berlaku ketika Anda menguji prasyarat dan segera menebus (dengan menggunakan menegaskan atau if (!precondition) throwmembangun), karena tidak ada kesalahan penanganan untuk melewati saat membaca kode.

Bart van Ingen Schenau
sumber
1
Bisakah Anda menguraikan bagian prediksi cabang? Saya tidak akan mengharapkan kode yang lebih mungkin untuk pergi ke case jika berjalan lebih cepat daripada kode yang lebih cenderung ke case yang lain. Maksudku, itulah inti dari prediksi cabang, bukan?
Roman Reiner
@ user136712: Dalam prosesor modern (cepat), instruksi diambil sebelum instruksi sebelumnya selesai diproses. Prediksi cabang digunakan untuk meningkatkan kemungkinan bahwa instruksi diambil saat mengeksekusi cabang bersyarat juga merupakan instruksi yang tepat untuk dieksekusi.
Bart van Ingen Schenau
2
Saya tahu apa prediksi cabang. Jika saya membaca posting Anda dengan benar, Anda mengatakan itu if(cond){/*more likely code*/}else{/*less likely code*/}berjalan lebih cepat daripada if(!cond){/*less likely code*/}else{/*more likely code*/}karena prediksi cabang. Saya akan berpikir bahwa prediksi cabang tidak bias terhadap pernyataan ifatau elsepernyataan dan hanya memperhitungkan sejarah. Jadi, jika elselebih mungkin terjadi itu harus dapat memprediksi itu sama baiknya. Apakah asumsi ini salah?
Roman Reiner
18

Saya pikir @JackAidley sudah mengatakan intinya , tetapi izinkan saya merumuskannya seperti ini:

tanpa Pengecualian (mis. C)

Dalam aliran kode biasa, Anda memiliki:

if (condition) {
    statement;
} else if (less_likely_condition) {
    less_likely_statement;
} else {
    least_likely_statement;
}
more_statements;

Dalam kasus "kesalahan awal", kode Anda tiba-tiba berbunyi:

/* demonstration example, do NOT code like this */
if (condition) {
    statement;
} else {
    error_handling;
    return;
}

Jika Anda menemukan pola ini - a returndi dalam else(atau bahkan if) blok, segera ulang sehingga kode tersebut tidak memiliki elseblok:

/* only code like this at University, to please structured programming professors */
function foo {
    if (condition) {
        lots_of_statements;
    }
    return;
}

Di dunia nyata ...

/* code like this instead */
if (!condition) {
    error_handling;
    return;
}
lots_of_statements;

Ini menghindari bersarang terlalu dalam dan memenuhi kasus "keluar awal" (membantu menjaga pikiran - dan aliran kode - bersih) dan tidak melanggar "memasukkan hal yang lebih mungkin ke dalam ifbagian" karena tidak ada elsebagian .

C dan pembersihan

Terinspirasi oleh jawaban pada pertanyaan serupa (yang salah), inilah cara Anda melakukan pembersihan dengan C. Anda dapat menggunakan satu atau dua titik keluar di sana, di sini satu untuk dua titik keluar:

struct foo *
alloc_and_init(size_t arg1, int arg2)
{
    struct foo *res;

    if (!(res = calloc(sizeof(struct foo), 1)))
        return (NULL);

    if (foo_init1(res, arg1))
        goto err;
    res.arg1_inited = true;
    if (foo_init2(&(res->blah), arg2))
        goto err;
    foo_init_complete(res);
    return (res);

 err:
    /* safe because we use calloc and false == 0 */
    if (res.arg1_inited)
        foo_dispose1(res);
    free(res);
    return (NULL);
}

Anda dapat menutupnya menjadi satu titik keluar jika ada sedikit pembersihan yang harus dilakukan:

char *
NULL_safe_strdup(const char *arg)
{
    char *res = NULL;

    if (arg == NULL)
        goto out;

    /* imagine more lines here */
    res = strdup(arg);

 out:
    return (res);
}

Penggunaan gotoini sangat baik, jika Anda bisa mengatasinya; saran untuk menghindari penggunaan gotodiarahkan pada orang yang belum bisa memutuskan sendiri apakah suatu penggunaan itu baik, dapat diterima, buruk, kode spageti, atau sesuatu yang lain.

Pengecualian

Pembicaraan di atas tentang bahasa tanpa pengecualian, yang saya sendiri lebih suka (saya bisa menggunakan penanganan kesalahan secara eksplisit jauh lebih baik, dan dengan lebih sedikit kejutan). Mengutip igli:

<igli> exceptions: a truly awful implementation of quite a nice idea.
<igli> just about the worst way you could do something like that, afaic.
<igli> it's like anti-design.
<mirabilos> that too… may I quote you on that?
<igli> sure, tho i doubt anyone will listen ;)

Tapi di sini adalah saran bagaimana Anda melakukannya dengan baik dalam bahasa dengan pengecualian, dan kapan Anda ingin menggunakannya dengan baik:

kesalahan kembali saat berhadapan dengan pengecualian

Anda dapat mengganti sebagian besar awal returndengan melemparkan pengecualian. Namun , aliran program normal Anda , yaitu setiap aliran kode di mana program tidak menemui, baik, pengecualian ... kondisi kesalahan atau semacamnya, tidak akan menimbulkan pengecualian.

Ini berarti bahwa ...

# this page is only available to logged-in users
if not isLoggedIn():
    # this is Python 2.5 style; insert your favourite raise/throw here
    raise "eh?"

... tidak apa-apa, tapi ...

/* do not code like this! */
try {
    openFile(xyz, "rw");
} catch (LockedException e) {
    return "file is locked";
}
closeFile(xyz);
return "file is not locked";

… tidak. Pada dasarnya, pengecualian bukanlah elemen aliran kontrol . Ini juga membuat Operasi terlihat aneh bagi Anda ("programmer Java ™ itu selalu memberi tahu kami bahwa pengecualian ini normal") dan dapat menghambat debugging (mis. Beri tahu IDE untuk tidak melakukan pengecualian apa pun). Pengecualian sering membutuhkan lingkungan run-time untuk melepas tumpukan untuk menghasilkan traceback, dll. Mungkin ada lebih banyak alasan untuk tidak melakukannya.

Ini bermuara pada: dalam bahasa yang mendukung pengecualian, gunakan apa pun yang cocok dengan logika dan gaya yang ada dan terasa alami. Jika menulis sesuatu dari awal, dapatkan ini disepakati sejak dini. Jika menulis perpustakaan dari awal, pikirkan konsumen Anda. (Jangan, pernah, gunakan abort()di perpustakaan juga ...) Tapi apa pun yang Anda lakukan, jangan, sebagai aturan praktis, memiliki pengecualian yang dilemparkan jika operasi berlanjut (kurang lebih) secara normal setelahnya.

saran umum wrt. Pengecualian

Cobalah untuk mendapatkan semua Pengecualian dalam program yang disetujui oleh seluruh tim pengembang terlebih dahulu. Pada dasarnya, rencanakan mereka. Jangan menggunakannya terlalu banyak. Terkadang, bahkan dalam C ++, Java ™, Python, pengembalian kesalahan lebih baik. Terkadang tidak; gunakan dengan pikiran.

mirabilos
sumber
Secara umum, saya melihat pengembalian awal sebagai kode bau. Saya akan melemparkan pengecualian, jika kode berikut akan gagal karena prasyarat tidak terpenuhi. Katakan saja '
DanMan
1
@DanMan: artikel saya ditulis dengan mengingat C ... Saya biasanya tidak menggunakan Pengecualian. Tapi saya sudah memperpanjang artikel dengan saran, ya, sudah cukup lama. Pengecualian; kebetulan, kami memiliki pertanyaan yang sama pada milis dev internal perusahaan kemarin ...
mirabilos
Juga, gunakan kawat gigi bahkan jika dan untuk satu baris fors. Anda tidak ingin goto fail;identitas lain disembunyikan.
Bruno Kim
1
@ BrunoKim: ini sepenuhnya tergantung pada gaya / konvensi pengkodean proyek yang Anda kerjakan. Saya bekerja dengan BSD, di mana ini disukai (lebih banyak kekacauan optik dan hilangnya ruang vertikal); di $ dayjob namun saya menempatkannya sesuai kesepakatan (kurang sulit bagi pemula, lebih sedikit kemungkinan kesalahan, dll. seperti yang Anda katakan).
mirabilos
3

Menurut saya 'Kondisi Penjaga' adalah salah satu cara terbaik dan termudah untuk membuat kode dapat dibaca. Saya sangat benci ketika saya melihat ifdi awal metode dan tidak melihat elsekode karena tidak ada layar. Saya harus gulir ke bawah hanya untuk melihat throw new Exception.

Letakkan cek di awal sehingga orang yang membaca kode tidak harus melompati seluruh metode untuk membacanya tetapi selalu memindai dari atas ke bawah.

Piotr Perak
sumber
2

(@mirabilos' jawabannya sangat baik, tapi inilah cara saya berpikir tentang pertanyaan untuk mencapai kesimpulan yang sama :)

Saya berpikir tentang diri saya (atau orang lain) membaca kode fungsi saya nanti. Ketika saya membaca baris pertama, saya tidak dapat membuat asumsi tentang input saya (kecuali yang saya tidak akan memeriksa lagi). Jadi pikiran saya adalah "Oke, saya tahu saya akan melakukan sesuatu dengan argumen saya. Tetapi pertama-tama mari kita bersihkan" - yaitu membunuh jalur kontrol di mana mereka tidak sesuai dengan keinginan saya. "Tetapi pada saat yang sama , Saya tidak melihat kasus normal sebagai sesuatu yang dikondisikan, saya ingin menekankan hal itu normal.

int foo(int* bar, int baz) {

   if (bar == NULL) /* I don't like you, leave me alone */;
   if (baz < 0) /* go away */;

   /* there, now I can do the work I came into this function to do,
      and I can safely forget about those if's above and make all 
      the assumptions I like. */

   /* etc. */
}
einpoklum - mengembalikan Monica
sumber
-3

Jenis pemesanan kondisi ini tergantung pada kritik bagian kode yang dipertanyakan dan apakah ada standar yang dapat digunakan.

Dengan kata lain:

A. bagian kritis dan tidak ada default => Gagal Dini

B. bagian yang tidak penting dan default => Gunakan default di bagian lain

C. di antara kasus => memutuskan per kasus sesuai kebutuhan

Nikos M.
sumber
Apakah ini hanya pendapat Anda atau Anda bisa menjelaskan / mendukungnya?
nyamuk
bagaimana tepatnya ini tidak didukung, karena setiap opsi menjelaskan (tanpa banyak kata memang) mengapa ini digunakan?
Nikos M.
Saya tidak ingin mengatakan ini, tetapi downvotes dalam (saya) jawaban ini di luar konteks :). ini adalah pertanyaan yang ditanyakan OP, apakah Anda memiliki jawaban alternatif adalah masalah lain
Nikos M.
Sejujurnya saya gagal melihat penjelasannya di sini. Katakanlah, jika seseorang menulis pendapat lain, seperti "bagian kritis dan tidak ada default => jangan Gagal Dini" , bagaimana jawaban ini membantu pembaca untuk memilih dua pendapat yang berlawanan? Pertimbangkan untuk mengeditnya dalam bentuk yang lebih baik, agar sesuai dengan pedoman Cara Menjawab .
nyamuk
ok saya mengerti, ini memang bisa menjadi penjelasan lain, tetapi setidaknya Anda mengerti kata "bagian kritis" dan "tidak ada default" yang dapat menyiratkan strategi untuk gagal lebih awal dan ini memang merupakan perluasan meskipun yang minimalis
Nikos M.