Praktik Terbaik - Membungkus jika sekitar panggilan fungsi vs Menambahkan keluar awal jika penjaga berfungsi

9

Saya tahu ini bisa sangat spesifik kasus penggunaan, tetapi saya mendapati diri saya bertanya-tanya terlalu sering. Apakah ada sintaks yang umumnya disukai.

Saya tidak bertanya apa pendekatan terbaik ketika dalam suatu fungsi, saya bertanya apakah saya harus keluar lebih awal atau saya tidak memanggil fungsi itu.

Bungkus jika sekitar panggilan fungsi


if (shouldThisRun) {
  runFunction();
}

Memiliki if ( guard ) in function

runFunction() {
  if (!shouldThisRun) return;
}

Opsi terakhir jelas memiliki potensi untuk mengurangi duplikasi kode jika fungsi ini disebut beberapa kali, tetapi kadang-kadang rasanya salah untuk menambahkannya di sini karena Anda mungkin kehilangan satu-satunya tanggung jawab fungsi.


Inilah contohnya

Jika saya memiliki fungsi updateStatus () yang hanya memperbarui status sesuatu. Saya hanya ingin status diperbarui jika status telah berubah. Saya tahu tempat-tempat dalam kode saya di mana statusnya berpotensi untuk berubah, dan saya tahu tempat-tempat lain di mana ia telah berubah.

Saya tidak yakin apakah ini hanya saya tetapi rasanya agak kotor untuk memeriksa fungsi dalam ini karena saya ingin menjaga fungsi ini semurni mungkin - jika saya menyebutnya saya berharap statusnya akan diperbarui. Tapi saya tidak tahu apakah lebih baik membungkus telepon dengan memeriksa beberapa tempat yang saya tahu berpotensi untuk tidak berubah.

Matthew Mullin
sumber
3
@gnat Tidak, pertanyaan itu pada dasarnya adalah 'Apa sintaks yang disukai dalam keluar awal' sedangkan milik saya adalah 'Haruskah saya keluar lebih awal atau haruskah saya tidak memanggil fungsi'
Matthew Mullin
4
Anda tidak dapat mempercayai pengembang, bahkan diri Anda sendiri , untuk dengan benar memeriksa prasyarat fungsi di mana pun namanya dipanggil. untuk alasan ini, saya akan menyarankan bahwa fungsi memvalidasi secara internal setiap kondisi yang diperlukan jika memiliki kemampuan untuk melakukannya.
TZHX
"Saya hanya ingin status diperbarui jika status telah berubah" - Anda ingin status diperbarui (= diubah) jika status yang sama telah berubah? Kedengarannya cukup melingkar. Bisakah Anda menjelaskan apa yang Anda maksudkan dengan ini, jadi saya dapat menambahkan contoh yang bermakna untuk jawaban saya tentang ini?
Doc Brown
@DocBrown Mari misalnya katakan saya ingin menyimpan dua properti status objek yang berbeda dalam sinkronisasi. Ketika salah satu objek berubah saya sebut syncStatuses () - tetapi ini bisa dipicu untuk banyak perubahan bidang yang berbeda (tidak hanya bidang status).
Matius Mullin

Jawaban:

15

Membungkus if di sekitar panggilan fungsi:
Ini untuk memutuskan apakah fungsi tersebut harus dipanggil sama sekali, dan merupakan bagian dari proses pengambilan keputusan program Anda.

Fungsi klausa penjaga (pengembalian awal):
Ini untuk melindungi agar tidak dipanggil dengan parameter yang tidak valid

Klausa penjaga yang digunakan dengan cara ini menjaga fungsi "murni" (istilah Anda). Itu ada hanya untuk memastikan bahwa fungsi tidak putus dengan data input yang salah.

Logika tentang apakah memanggil fungsi sama sekali adalah pada tingkat abstraksi yang lebih tinggi, bahkan jika hanya saja. Seharusnya ada di luar fungsi itu sendiri. Seperti yang dikatakan DocBrown, Anda dapat memiliki fungsi perantara yang melakukan pemeriksaan ini, untuk menyederhanakan kode.

Ini adalah pertanyaan yang bagus untuk diajukan, dan termasuk dalam rangkaian pertanyaan yang mengarah pada pengenalan tingkat abstraksi. Setiap fungsi harus beroperasi pada satu level abstraksi, dan memiliki logika program dan logika fungsi dalam fungsi terasa salah bagi Anda - ini karena mereka berada pada level abstraksi yang berbeda. Fungsi itu sendiri adalah level yang lebih rendah.

Dengan memisahkan ini, Anda memastikan bahwa kode Anda akan lebih mudah untuk ditulis, dibaca, dan dipelihara.

Baldrickk
sumber
Jawaban yang luar biasa. Saya suka fakta itu memberi saya cara yang jelas untuk memikirkan hal ini. Jika harus berada di luar karena merupakan 'bagian dari proses pengambilan keputusan', apakah fungsi tersebut harus dipanggil atau tidak. Dan secara inheren tidak ada hubungannya dengan fungsi itu sendiri. Rasanya aneh untuk menandai jawaban pendapat sebagai benar, tetapi saya akan melihat lagi dalam beberapa jam dan melakukannya.
Matius Mullin
Jika itu membantu, saya tidak menganggap ini sebagai jawaban "pendapat". Saya perhatikan bahwa itu "terasa" salah, tetapi itu karena tingkat abstraksi yang berbeda tidak terpisah. Apa yang saya dapatkan dari pertanyaan Anda adalah bahwa Anda dapat melihat bahwa itu tidak 'benar' tetapi karena Anda tidak memikirkan tingkat abstraksi, sulit untuk diukur, maka itu adalah sesuatu yang Anda perjuangkan untuk kata-kata.
Baldrickk
7

Anda dapat memiliki keduanya - fungsi yang tidak memeriksa parameter, dan yang lainnya, seperti ini (mungkin mengembalikan beberapa informasi tentang jika panggilan dilakukan):

bool tryRunFunction(...)
{
    bool shouldThisRun = /* some logic using data not available inside "runFunction"*/;
    if (shouldThisRun)
        runFunction();
    return shouldThisRun;
}

Dengan begitu, Anda dapat menghindari logika duplikat dengan memberikan fungsi yang dapat digunakan kembali tryRunFunctiondan masih memiliki fungsi asli Anda (mungkin murni) yang tidak membuat pemeriksaan di dalam.

Perhatikan bahwa terkadang Anda memerlukan fungsi seperti tryRunFunctiondengan cek terintegrasi secara eksklusif, sehingga Anda dapat mengintegrasikan cek tersebut ke dalam runFunction. Atau Anda tidak perlu menggunakan kembali cek di mana saja di program Anda lagi, dalam hal ini Anda dapat membiarkannya tetap dalam fungsi panggilan.

Namun, cobalah untuk membuatnya transparan kepada penelepon apa yang terjadi dengan memberikan nama yang tepat fungsi Anda. Jadi penelepon tidak perlu menebak atau melihat ke dalam implementasi jika mereka harus melakukan pemeriksaan sendiri, atau jika fungsi yang dipanggil sudah melakukannya untuk mereka. Awalan sederhana seperti trysering dapat mencukupi untuk ini.

Doc Brown
sumber
1
Harus mengakui, idiom "tryXXX ()" selalu tampak agak aneh dan tidak pantas di sini. Anda tidak mencoba melakukan sesuatu mengharapkan kesalahan. Anda memperbarui jika kotor.
user949300
@ user949300: memilih nama baik atau skema penamaan tergantung pada kasus penggunaan nyata, nama fungsi sebenarnya, bukan beberapa nama yang dibuat seperti runFunction. Fungsi seperti updateStatus()dapat disertai dengan fungsi lain seperti updateIfStatusHasChanged(). Tapi ini 100% case dependend, tidak ada solusi "satu ukuran untuk semua", jadi ya, saya setuju, idiom "coba" tidak selalu merupakan pilihan yang baik.
Doc Brown
Tidak ada yang bernama "dryRun"? Kurang lebih menjadi eksekusi reguler tanpa efek samping. Cara menonaktifkan efek samping adalah cerita lain
Laiv
3

Adapun siapa yang memutuskan apakah akan menjalankan, jawabannya adalah, dari GRASP , siapa "ahli informasi" yang tahu.

Setelah Anda memutuskan itu, pertimbangkan untuk mengganti nama fungsi agar lebih jelas.

Sesuatu seperti ini, jika fungsinya memutuskan:

 ensureUpdated()
 updateIfDirty()

Atau, jika penelepon seharusnya memutuskan:

 writeStatus()
pengguna949300
sumber
2

Saya ingin memperluas jawaban @ Baldrickk.

Tidak ada jawaban umum untuk pertanyaan Anda. Itu tergantung pada makna (kontrak) dari fungsi yang akan dipanggil dan sifat dari kondisi tersebut.

Jadi mari kita bahas dalam konteks contoh panggilan Anda updateStatus(). Kontraknya mungkin adalah memperbarui beberapa status karena sesuatu dengan pengaruh pada status telah terjadi. Saya berharap panggilan ke metode itu diizinkan bahkan jika tidak ada perubahan status nyata, dan diperlukan jika ada perubahan nyata.

Jadi, situs panggilan dapat melewati panggilan updateStatus()jika ia tahu bahwa (di dalam cakrawala domainnya) tidak ada yang relevan berubah. Itulah situasi di mana panggilan harus dikelilingi oleh ifkonstruk yang sesuai .

Di dalam updateStatus()fungsi, mungkin ada situasi di mana fungsi ini mendeteksi (dari data di dalam cakrawala domainnya) bahwa tidak ada yang harus dilakukan, dan di situlah ia harus kembali lebih awal.

Jadi, pertanyaannya adalah:

  • Dari tampilan luar, kapan diizinkan / diminta untuk memanggil fungsi, dengan mempertimbangkan kontrak fungsi?
  • Di dalam fungsi, apakah ada situasi di mana ia dapat kembali lebih awal tanpa kerja nyata?
  • Apakah kondisi apakah memanggil fungsi / apakah kembali lebih awal milik domain internal fungsi atau ke luar?

Dengan sebuah updateStatus()fungsi, saya berharap melihat keduanya, memanggil situs yang tahu tidak ada yang berubah, melewatkan panggilan, dan implementasi yang memeriksa situasi "tidak ada yang berubah" lebih awal, bahkan jika dengan cara ini kondisi yang sama kadang-kadang diperiksa dua kali, keduanya di dalam dan di luar.

Ralf Kleberhoff
sumber
2

Ada banyak penjelasan bagus. Tapi saya ingin terlihat dengan cara yang tidak biasa: Anggap Anda menggunakan cara ini:

if (shouldThisRun) {
   runFunction();
}

runFunction() {
   if (!shouldThisRun) return;
}

Dan Anda perlu memanggil fungsi lain dalam runFunctionmetode seperti ini:

runFunction() {
   if (!shouldThisRun) return;
   someOtherfunction();
}

Apa yang akan kamu lakukan? Apakah Anda menyalin semua validasi dari atas ke bawah?

someOtherfunction() {
   if (!shouldThisRun) return;
}

Saya kira tidak. Jadi, saya biasanya melakukan pendekatan yang sama: Validasi input dan periksa kondisi dalam publicmetode. Metode publik harus melakukan validasinya sendiri dan memeriksa kondisi yang diperlukan bahkan penelepon melakukannya. Tetapi biarkan metode pribadi lakukan saja bisnisnya sendiri . Beberapa fungsi lain dapat memanggil runFunctiontanpa melakukan validasi atau memeriksa kondisi apa pun.

public someFunction() {
   if (shouldThisRun) {
      runFunction();
   }
}

private runFunction() {
 // do your business.
}
Insinyur
sumber