Haruskah "lain" digunakan dalam situasi di mana aliran kontrol menjadikannya berlebihan?

18

Saya terkadang menemukan kode yang mirip dengan contoh berikut (apa fungsi ini tidak persis di luar lingkup pertanyaan ini):

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  else if (check2(value)) {
    return value;
  }
  else {
    return false;
  }
}

Seperti yang Anda lihat, if, else ifdan elsepernyataan yang digunakan dalam hubungannya dengan returnpernyataan. Ini tampaknya cukup intuitif untuk pengamat biasa, tapi saya pikir akan lebih elegan (dari perspektif pengembang perangkat lunak) untuk menghapus else-s dan menyederhanakan kode seperti ini:

function doSomething(value) {
  if (check1(value)) {
    return -1;
  }
  if (check2(value)) {
    return value;
  }
  return false;
}

Ini masuk akal, karena segala sesuatu yang mengikuti returnpernyataan (dalam lingkup yang sama) tidak akan pernah dieksekusi, membuat kode di atas secara semantik sama dengan contoh pertama.

Manakah di atas yang lebih cocok dengan praktik pengkodean yang baik? Apakah ada kekurangan pada kedua metode sehubungan dengan keterbacaan kode?

Sunting: Saran rangkap telah dibuat dengan pertanyaan ini disediakan sebagai referensi. Saya percaya pertanyaan saya menyentuh topik yang berbeda, karena saya tidak bertanya tentang menghindari pernyataan rangkap seperti yang disajikan dalam pertanyaan lain. Kedua pertanyaan berusaha mengurangi pengulangan, meskipun dengan cara yang sedikit berbeda.

badak
sumber
14
Masalahnya elselebih kecil, masalah yang lebih besar adalah Anda jelas mengembalikan dua tipe data dari satu fungsi, yang membuat API dari fungsi tersebut tidak dapat diprediksi.
Andy
3
E_CLEARLY_NOTADUPE
kojiro
3
@ Davidvider: Setidaknya dua; bisa jadi tiga. -1adalah angka, falseadalah boolean, dan valuetidak ditentukan di sini sehingga bisa berupa semua jenis objek.
Yay295
1
@ Yay295 Saya berharap valuesebenarnya adalah bilangan bulat. Jika itu bisa apa saja maka itu bahkan lebih buruk.
Andy
@ Davidvider Ini adalah poin yang sangat penting, terutama ketika dimunculkan berkaitan dengan pertanyaan tentang praktik yang baik. Saya setuju dengan Anda dan Yay295, namun contoh kode berfungsi sebagai contoh untuk menunjukkan teknik pengkodean yang tidak terkait. Namun demikian, saya tetap ingat bahwa ini adalah masalah penting dan tidak boleh diabaikan dalam kode sebenarnya.
badak

Jawaban:

23

Saya suka yang tanpa elsedan inilah alasannya:

function doSomething(value) {
  //if (check1(value)) {
  //  return -1;
  //}
  if (check2(value)) {
    return value;
  }
  return false;
}

Karena melakukan itu tidak merusak apa pun yang tidak seharusnya.

Benci saling ketergantungan dalam semua bentuk itu (termasuk penamaan fungsi check2()). Isolasi semua yang bisa diisolasi. Terkadang Anda membutuhkan elsetetapi saya tidak melihatnya di sini.

candied_orange
sumber
Saya umumnya lebih suka ini juga, karena alasan yang dinyatakan di sini. Namun, saya biasanya menempatkan komentar pada akhir setiap ifblok } //elseuntuk memperjelas, ketika membaca kode, bahwa satu-satunya eksekusi yang dimaksudkan setelah ifblok kode adalah jika kondisi dalam ifpernyataan itu salah. Tanpa sesuatu seperti ini, terutama jika kode di dalam ifblok tumbuh menjadi lebih kompleks, mungkin tidak jelas bagi siapa pun yang menjaga kode bahwa maksudnya adalah untuk tidak pernah mengeksekusi melewati ifblok ketika ifkondisinya benar.
Makyen
@ Makyen Anda meningkatkan poin yang baik. Yang saya coba abaikan. Saya juga percaya sesuatu harus dilakukan setelah ifuntuk memperjelas bahwa bagian kode ini selesai dan struktur selesai. Untuk melakukan ini saya melakukan apa yang belum saya lakukan di sini (karena saya tidak ingin mengalihkan perhatian dari poin utama OP dengan membuat perubahan lain). Saya melakukan hal paling sederhana yang dapat saya lakukan untuk mencapai semua itu tanpa mengganggu. Saya menambahkan baris kosong.
candied_orange
27

Saya pikir itu tergantung pada semantik kode. Jika ketiga kasus Anda saling bergantung, nyatakan secara eksplisit. Ini meningkatkan keterbacaan kode dan membuatnya lebih mudah dipahami oleh orang lain. Contoh:

if (x < 0) {
    return false;
} else if (x > 0) {
    return true;
} else {
    throw new IllegalArgumentException();
}

Di sini Anda jelas tergantung pada nilai xdalam ketiga kasus. Jika Anda menghilangkan yang terakhir else, ini akan menjadi kurang jelas.

Jika kasing Anda tidak saling tergantung satu sama lain, abaikan saja:

if (x < 0) {
    return false;
}
if (y < 0) {
    return true;
}
return false;

Sulit untuk menunjukkan ini dalam contoh sederhana tanpa konteks, tetapi saya harap Anda mengerti maksud saya.

André Stannek
sumber
6

Saya lebih suka pilihan kedua (terpisah ifstanpa else ifdan awal return) TAPI yang selama blok kode pendek .

Jika blok kode panjang, lebih baik menggunakan else if, karena selain itu Anda tidak akan memiliki pemahaman yang jelas tentang beberapa titik keluar kode.

Sebagai contoh:

function doSomething(value) {
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    return -1;
  }
  if (check2(value)) {
    // ...
    // imagine 150 lines of code
    // ...
    return value;
  }
  return false;
}

Dalam hal ini, lebih baik digunakan else if, simpan kode kembali dalam variabel dan hanya memiliki satu kalimat kembali di akhir.

function doSomething(value) {
  retVal=0;
  if (check1(value)) {
    // ...
    // imagine 200 lines of code
    // ...
    retVal=-1;
  } else if (check2(value)) {
    // ...
    // imagne 150 lines of code
    retVal=value;
  }
  return retVal;
}

Tetapi sebagai salah satu harus berusaha untuk fungsi menjadi pendek, saya akan mengatakan tetap pendek dan keluar lebih awal seperti dalam cuplikan kode pertama Anda.

Tulains Córdova
sumber
1
Saya setuju dengan premis Anda, tetapi selama kita membahas bagaimana seharusnya kode, mungkinkah kita juga tidak hanya setuju bahwa blok kode harus pendek? Saya pikir lebih buruk untuk memiliki blok kode yang panjang tidak dipecah menjadi fungsi daripada menggunakan yang lain jika, jika saya harus memilih.
kojiro
1
Argumen yang aneh. returnberada dalam 3 baris ifsehingga tidak jauh berbeda else ifbahkan setelah 200 baris kode. The gaya kembali tunggal Anda memperkenalkan sini adalah tradisi c dan bahasa lain yang tidak melaporkan kesalahan dengan pengecualian. Intinya adalah menciptakan satu tempat untuk membersihkan sumber daya. Bahasa pengecualian menggunakan finallyblok untuk itu. Saya kira ini juga menciptakan tempat untuk meletakkan break point jika debugger Anda tidak akan membiarkan Anda menempatkan satu pada fungsi penutupan kurung kurawal.
candied_orange
4
Saya tidak suka tugas retValsama sekali. Jika Anda dapat dengan aman keluar dari suatu fungsi segera lakukan, tanpa menetapkan nilai aman untuk dikembalikan ke variabel keluar tunggal dari fungsi. Ketika Anda menggunakan titik pengembalian tunggal dalam fungsi yang sangat panjang, saya biasanya tidak mempercayai programmer lain dan akhirnya mencari melalui seluruh fungsi untuk modifikasi lain dari nilai kembali juga. Gagal berpuasa dan kembali lebih awal, antara lain, dua aturan yang saya jalani.
Andy
2
Menurut saya itu bahkan lebih buruk untuk blok panjang. Saya mencoba untuk keluar dari fungsi sedini mungkin tidak peduli apa konteks luarnya.
Andy
2
Saya dengan DavidPacker di sini. Gaya pengembalian tunggal memaksa kita untuk mempelajari semua poin penugasan serta kode setelah poin penugasan. Mempelajari titik keluar dari gaya pengembalian awal akan lebih baik bagi saya, panjang atau pendek. Selama bahasa mengizinkan blok akhirnya.
candied_orange
4

Saya menggunakan keduanya dalam keadaan yang berbeda. Dalam pemeriksaan validasi, saya menghilangkan yang lain. Dalam aliran kontrol, saya menggunakan yang lain.

bool doWork(string name) {
  if(name.empty()) {
    return false;
  }

  //...do work
  return true;
}

vs.

bool doWork(int value) {
  if(value % 2 == 0) {
    doWorkWithEvenNumber(value);
  }
  else {
    doWorkWithOddNumber(value);
  }
}

Kasing pertama berbunyi lebih seperti Anda memeriksa semua prasyarat terlebih dahulu, menyingkirkan semua itu, lalu maju ke kode aktual yang ingin Anda jalankan. Dengan demikian, kode berair yang benar-benar ingin Anda jalankan termasuk dalam ruang lingkup fungsi secara langsung; itulah fungsi sebenarnya.
Kasus kedua berbunyi lebih seperti kedua jalur adalah kode yang valid untuk dijalankan yang sama relevan dengan fungsi seperti yang lain berdasarkan beberapa kondisi. Dengan demikian, mereka termasuk dalam tingkat cakupan yang sama satu sama lain.

Altainia
sumber
0

Satu-satunya situasi yang pernah saya lihat benar-benar penting adalah kode seperti ini:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    }
    if (left != null) {
        return Arrays.asList(left);
    }
    if (right != null) {
        return Arrays.asList(right);
    }
    return Arrays.asList();
}

Jelas ada sesuatu yang salah di sini. Masalahnya adalah, tidak jelas apakah returnharus ditambahkan atau Arrays.asListdihapus. Anda tidak dapat memperbaikinya tanpa pemeriksaan yang lebih dalam terhadap metode terkait. Anda dapat menghindari ambiguitas ini dengan selalu menggunakan blok if-else yang lengkap. Ini:

List<?> toList() {
    if (left != null && right != null) {
        Arrays.asList(merge(left, right));
    } else if (left != null) {
        return Arrays.asList(left);
    } else if (right != null) {
        return Arrays.asList(right);
    } else {
        return Arrays.asList();
    }
}

tidak akan dikompilasi (dalam bahasa yang dicentang secara statis) kecuali Anda menambahkan pengembalian eksplisit terlebih dahulu if.

Beberapa bahasa bahkan tidak mengizinkan gaya pertama. Saya mencoba menggunakannya hanya dalam konteks yang sangat penting atau untuk prasyarat dalam metode panjang:

List<?> toList() {
    if (left == null || right == null) {
        throw new IllegalStateException()
    }
    // ...
    // long method
    // ...
}
Banthar
sumber
-2

Memiliki tiga keluar dari fungsi seperti itu benar-benar membingungkan jika Anda mencoba mengikuti kode dan praktik yang buruk.

Jika Anda memiliki satu titik keluar untuk fungsi tersebut maka elemen yang diperlukan.

var result;
if(check1(value)
{
    result = -1;
}
else if(check2(value))
{
    result = value;
}
else 
{
    result = 0;
}
return result;

Bahkan mari kita melangkah lebih jauh.

var result = 0;
var c1 = check1(value);
var c2 = check2(value);

if(c1)
{
    result = -1;
}
else if(c2)
{
    result = value;
}

return result;

Cukup adil Anda bisa berdebat untuk pengembalian awal input validasi tunggal. Hanya menghindari membungkus keseluruhan massal jika logika Anda dalam sebuah if.

Ewan
sumber
3
Gaya ini berasal dari bahasa dengan manajemen sumber daya eksplisit. Satu titik diperlukan untuk membebaskan sumber daya yang dialokasikan. Dalam bahasa dengan manajemen sumber daya otomatis, titik pengembalian tunggal tidak terlalu penting. Anda harus lebih fokus pada pengurangan jumlah dan ruang lingkup variabel.
Banthar
a: tidak ada bahasa yang ditentukan dalam pertanyaan. b: Saya pikir Anda salah dalam hal ini. ada banyak alasan bagus untuk pengembalian tunggal. itu mengurangi kompleksitas dan meningkatkan keterbacaan
Ewan
1
Terkadang mengurangi kompleksitas, kadang meningkatkannya. Lihat wiki.c2.com/?ArrowAntiPattern
Robert Johnson
Tidak, saya pikir itu selalu mengurangi kompleksitas siklomatik, lihat contoh kedua saya. check2 selalu berjalan
Ewan
memiliki pengembalian awal adalah optimasi yang menyulitkan kode dengan memindahkan beberapa kode ke bersyarat
Ewan
-3

Saya lebih suka menghilangkan pernyataan redundant else. Setiap kali saya melihatnya (dalam review kode atau refactoring) saya bertanya-tanya apakah penulis memahami aliran kontrol dan bahwa mungkin ada bug dalam kode.

Jika penulis percaya pernyataan lain diperlukan dan dengan demikian tidak memahami implikasi aliran kontrol dari pernyataan kembali tentu saja kurangnya pemahaman adalah sumber utama bug, dalam fungsi ini, dan secara umum.

Andreas Warberg
sumber
3
Sementara saya kebetulan setuju dengan Anda, ini bukan jajak pendapat. Harap cadangkan klaim Anda atau pemilih tidak akan memperlakukan Anda dengan baik.
candied_orange