Bagaimana saya bisa melakukan panggilan dengan boolean lebih jelas? Perangkap Boolean

76

Seperti dicatat dalam komentar oleh @ benjamin-gruenbaum ini disebut perangkap Boolean:

Katakanlah saya memiliki fungsi seperti ini

UpdateRow(var item, bool externalCall);

dan di controller saya, nilai itu externalCallakan selalu BENAR. Apa cara terbaik untuk memanggil fungsi ini? Saya biasanya menulis

UpdateRow(item, true);

Tetapi saya bertanya pada diri saya sendiri, haruskah saya menyatakan boolean, hanya untuk menunjukkan apa arti nilai 'sejati' itu? Anda bisa tahu itu dengan melihat deklarasi fungsi, tetapi jelas lebih cepat dan lebih jelas jika Anda hanya melihat sesuatu seperti

bool externalCall = true;
UpdateRow(item, externalCall);

PD: Tidak yakin apakah pertanyaan ini benar-benar cocok di sini, jika tidak, di mana saya bisa mendapatkan lebih banyak info tentang ini?

PD2: Saya tidak memberi tag bahasa apa pun karena saya pikir itu masalah yang sangat umum. Lagi pula, saya bekerja dengan c # dan jawaban yang diterima berfungsi untuk c #

Mario Garcia
sumber
10
Pola ini juga disebut perangkap boolean
Benjamin Gruenbaum
11
Jika Anda menggunakan bahasa dengan dukungan untuk tipe data aljabar, saya sangat merekomendasikan iklan baru daripada boolean. data CallType = ExternalCall | InternalCalldi haskell misalnya.
Filip Haglund
6
Saya kira Enums akan memenuhi tujuan yang sama persis; mendapatkan nama untuk boolean, dan sedikit tipe keamanan.
Filip Haglund
2
Saya tidak setuju bahwa mendeklarasikan boolean untuk menunjukkan artinya "jelas lebih jelas". Dengan opsi pertama, jelas bahwa Anda akan selalu lulus benar. Dengan yang kedua, Anda harus memeriksa (di mana variabel didefinisikan? Apakah nilainya berubah?). Tentu, itu bukan masalah jika kedua baris itu bersama-sama ... tetapi seseorang mungkin memutuskan bahwa tempat yang tepat untuk menambahkan kode mereka persis di antara dua baris. Itu terjadi!
AJPerez
Mendeklarasikan boolean tidak lebih bersih, lebih jelas itu hanya melompati langkah yang, melihat dokumentasi metode yang dimaksud. Jika Anda tahu tanda tangannya, kurang jelas untuk menambahkan konstanta baru.
Di dalam

Jawaban:

154

Tidak selalu ada solusi yang sempurna, tetapi Anda memiliki banyak alternatif untuk dipilih:

  • Gunakan argumen bernama , jika tersedia dalam bahasa Anda. Ini bekerja dengan sangat baik dan tidak memiliki kekurangan tertentu. Dalam beberapa bahasa, argumen apa pun dapat diajukan sebagai argumen bernama, misalnya updateRow(item, externalCall: true)( C # ) atau update_row(item, external_call=True)(Python).

    Saran Anda untuk menggunakan variabel terpisah adalah salah satu cara untuk mensimulasikan argumen bernama, tetapi tidak memiliki manfaat keamanan yang terkait (tidak ada jaminan bahwa Anda menggunakan nama variabel yang benar untuk argumen itu).

  • Gunakan fungsi berbeda untuk antarmuka publik Anda, dengan nama yang lebih baik. Ini adalah cara lain untuk mensimulasikan parameter bernama, dengan meletakkan nilai paremeter dalam nama.

    Ini sangat mudah dibaca, tetapi mengarah ke banyak boilerplate untuk Anda, yang menulis fungsi-fungsi ini. Itu juga tidak bisa menghadapi ledakan kombinatorial ketika ada beberapa argumen boolean. Kelemahan signifikan adalah bahwa klien tidak dapat menetapkan nilai ini secara dinamis, tetapi harus menggunakan jika / yang lain untuk memanggil fungsi yang benar.

  • Gunakan enum . Masalah dengan boolean adalah bahwa mereka disebut "benar" dan "salah". Jadi, alih-alih perkenalkan jenis dengan nama yang lebih baik (misalnya enum CallType { INTERNAL, EXTERNAL }). Sebagai manfaat tambahan, ini meningkatkan keamanan jenis program Anda (jika bahasa Anda mengimplementasikan enum sebagai jenis yang berbeda). Kelemahan dari enum adalah mereka menambahkan jenis ke API Anda yang dapat dilihat secara publik. Untuk fungsi internal murni, ini tidak masalah dan enum tidak memiliki kelemahan yang signifikan.

    Dalam bahasa tanpa enum, string pendek terkadang digunakan sebagai gantinya. Ini berfungsi, dan bahkan mungkin lebih baik daripada boolean mentah, tetapi sangat rentan terhadap kesalahan ketik. Fungsi kemudian harus segera menyatakan bahwa argumen cocok dengan satu set nilai yang mungkin.

Tidak satu pun dari solusi ini yang memiliki dampak kinerja yang terlarang. Parameter yang dinamai dan enum dapat diselesaikan sepenuhnya pada waktu kompilasi (untuk bahasa yang dikompilasi). Menggunakan string mungkin melibatkan perbandingan string, tetapi biaya yang dapat diabaikan untuk literal string kecil dan sebagian besar jenis aplikasi.

amon
sumber
7
Saya baru tahu bahwa "argumen bernama" ada. Saya pikir sejauh ini saran terbaik. Jika Anda dapat menjelaskan sedikit tentang opsi itu (sehingga orang lain tidak perlu terlalu google tentang hal itu) saya akan menerima jawaban ini. Juga ada petunjuk tentang kinerja jika Anda bisa ... Terima kasih
Mario Garcia
6
Satu hal yang dapat membantu meringankan masalah dengan string pendek adalah menggunakan konstanta dengan nilai string. @MarioGarcia Tidak banyak yang bisa dijabarkan. Selain dari apa yang disebutkan di sini, sebagian besar detail akan tergantung pada bahasa tertentu. Adakah sesuatu yang ingin Anda lihat yang disebutkan di sini?
jpmc26
8
Dalam bahasa di mana kita berbicara tentang metode dan enum dapat dicakup ke kelas saya biasanya menggunakan enum. Ini sepenuhnya mendokumentasikan diri sendiri (dalam satu baris kode yang menyatakan tipe enum, sehingga juga ringan), sepenuhnya mencegah metode dipanggil dengan boolean telanjang, dan dampak pada API publik dapat diabaikan (karena pengidentifikasi dicakup ke dalam kelas).
davidbak
4
Kelemahan lain menggunakan string dalam peran ini adalah bahwa Anda tidak dapat memeriksa validitasnya pada waktu kompilasi, tidak seperti enum.
Ruslan
32
enum adalah pemenangnya. Hanya karena suatu parameter hanya dapat memiliki satu dari dua status yang memungkinkan, itu tidak berarti bahwa itu seharusnya secara otomatis menjadi boolean.
Pete
39

Solusi yang tepat adalah melakukan apa yang Anda sarankan, tetapi kemas dalam fasad mini:

void updateRowExternally() {
  bool externalCall = true;
  UpdateRow(item, externalCall);
}

Keterbacaan mengalahkan pengoptimalan mikro. Anda dapat membeli panggilan fungsi ekstra, tentu saja lebih baik daripada Anda mampu membayar upaya pengembang karena harus melihat semantik bendera Boolean bahkan sekali.

Kilian Foth
sumber
8
Apakah enkapsulasi ini benar-benar layak ketika saya hanya memanggil fungsi itu sekali di controller saya?
Mario Garcia
14
Iya. Ya itu. Alasannya, seperti yang saya tulis di atas, adalah bahwa biaya (panggilan fungsi) lebih kecil daripada manfaatnya (Anda menghemat waktu pengembang yang nontrivial karena tidak ada yang pernah melihat apa yang truedilakukannya.) Akan bermanfaat bahkan jika itu biaya banyak waktu CPU karena menghemat waktu pengembang, karena waktu CPU jauh lebih murah.
Kilian Foth
8
Juga setiap pengoptimal yang kompeten harus dapat menjelaskannya untuk Anda sehingga pada akhirnya Anda tidak akan kehilangan apa pun.
firedraco
33
@KilianFoth Kecuali itu asumsi yang salah. Seorang pengelola memang perlu tahu apa yang dilakukan parameter kedua, dan mengapa itu benar, dan hampir selalu ini berkaitan dengan detail dari apa yang Anda coba lakukan. Menyembunyikan fungsionalitas dalam fungsi kecil mati-oleh-seribu-potong akan mengurangi pemeliharaan, bukan meningkatkannya. Saya telah melihat itu terjadi sendiri, sedih untuk dikatakan. Pemartisian berlebihan menjadi fungsi sebenarnya bisa lebih buruk daripada Fungsi Dewa besar.
Graham
6
Saya pikir UpdateRow(item, true /*external call*/);akan lebih bersih, dalam bahasa yang memungkinkan sintaks komentar itu. Kembung kode Anda dengan fungsi tambahan hanya untuk menghindari menulis komentar sepertinya tidak layak untuk kasus sederhana. Mungkin jika ada banyak argumen lain untuk fungsi ini, dan / atau beberapa kode rumit sekitar berantakan, itu akan mulai menarik lebih banyak. Tapi saya pikir jika Anda men-debug Anda akan berakhir memeriksa fungsi wrapper untuk melihat fungsinya, dan harus mengingat fungsi mana yang merupakan pembungkus tipis di sekitar API perpustakaan, dan yang sebenarnya memiliki beberapa logika.
Peter Cordes
25

Katakanlah saya memiliki fungsi seperti UpdateRow (item var, bool externalCall);

Mengapa Anda memiliki fungsi seperti ini?

Dalam keadaan apa Anda menyebutnya dengan argumen Panggilan eksternal diatur ke nilai yang berbeda?

Jika salah satu dari, katakanlah, aplikasi klien eksternal dan yang lain berada dalam program yang sama (yaitu modul kode yang berbeda), maka saya berpendapat bahwa Anda harus memiliki dua metode yang berbeda , satu untuk setiap kasus, bahkan mungkin ditentukan pada Antarmuka.

Namun, jika Anda membuat panggilan berdasarkan pada beberapa datum yang diambil dari sumber non-program (misalnya, file konfigurasi atau basis data dibaca), maka metode passing Boolean akan lebih masuk akal.

Phill W.
sumber
6
Saya setuju. Dan hanya untuk menekankan poin yang dibuat untuk pembaca lain, analisis dapat dibuat dari semua penelepon, yang akan lulus baik benar, salah, atau variabel. Jika tidak ada yang terakhir ditemukan, saya akan berdebat untuk dua metode terpisah (tanpa boolean) lebih dari satu dengan boolean - itu adalah argumen YAGNI bahwa boolean memperkenalkan kompleksitas yang tidak terpakai / tidak perlu. Bahkan jika ada beberapa penelepon yang lewat variabel, saya mungkin masih menyediakan (boolean) versi bebas parameter untuk digunakan untuk penelepon yang melewati konstanta - itu lebih sederhana, yang bagus.
Erik Eidt
18

Walaupun saya setuju bahwa ideal untuk menggunakan fitur bahasa untuk menegakkan keterbacaan dan keamanan nilai, Anda juga dapat memilih pendekatan praktis : komentar waktu panggilan. Suka:

UpdateRow(item, true /* row is an external call */);

atau:

UpdateRow(item, true); // true means call is external

atau (benar, seperti yang disarankan oleh Frax):

UpdateRow(item, /* externalCall */true);
uskup
sumber
1
Tidak ada alasan untuk downvote. Ini adalah saran yang sangat valid.
Suncat2000
Hargai <3 @ Suncat2000!
Uskup
11
Varian (kemungkinan lebih disukai) hanya menempatkan nama argumen yang sederhana di sana, seperti UpdateRow(item, /* externalCall */ true ). Komentar kalimat lengkap jauh lebih sulit diurai, sebenarnya sebagian besar kebisingan (terutama varian kedua, yang juga sangat longgar ditambah dengan argumen).
Frax
1
Sejauh ini, inilah jawaban yang paling tepat. Inilah yang dimaksudkan untuk komentar.
Sentinel
9
Bukan penggemar komentar seperti ini. Komentar cenderung menjadi basi jika tidak tersentuh saat refactoring atau melakukan perubahan lainnya. Belum lagi begitu Anda memiliki lebih dari satu param bool ini cepat membingungkan.
John V.
11
  1. Anda bisa 'memberi nama' bools Anda. Di bawah ini adalah contoh untuk bahasa OO (di mana ia dapat diekspresikan dalam penyediaan kelas UpdateRow()), namun konsep itu sendiri dapat diterapkan dalam bahasa apa pun:

    class Table
    {
    public:
        static const bool WithExternalCall = true;
        static const bool WithoutExternalCall = false;
    

    dan di situs panggilan:

    UpdateRow(item, Table::WithExternalCall);
    
  2. Saya percaya Item # 1 lebih baik tetapi tidak memaksa pengguna menggunakan variabel baru saat menggunakan fungsi. Jika keamanan jenis penting bagi Anda, Anda bisa membuat enumtipe dan UpdateRow()menerima ini alih-alih bool:

    UpdateRow(var item, ExternalCallAvailability ec);

  3. Anda dapat memodifikasi nama fungsi dengan cara yang mencerminkan makna boolparameter dengan lebih baik. Tidak terlalu yakin tapi mungkin:

    UpdateRowWithExternalCall(var item, bool externalCall)

simurg
sumber
8
Jangan suka # 3 seperti sekarang jika Anda memanggil fungsi dengan externalCall=false, namanya sama sekali tidak masuk akal. Jawaban Anda yang lain baik.
Lightness Races dalam Orbit
Sepakat. Saya tidak begitu yakin tetapi ini mungkin opsi yang layak untuk beberapa fungsi lainnya. Saya
simurg
@LightnessRacesinOrbit Memang. Lebih aman untuk dicoba UpdateRowWithUsuallyExternalButPossiblyInternalCall.
Eric Duminil
@EricDuminil: Spot on 😂
Lightness Races in Orbit
10

Pilihan lain yang belum saya baca di sini adalah: Gunakan IDE modern.

Misalnya IntelliJ IDEA mencetak nama variabel dari variabel-variabel dalam metode yang Anda panggil jika Anda menggunakan literal seperti trueatau nullatau username + “@company.com. Ini dilakukan dalam font kecil sehingga tidak memakan terlalu banyak ruang pada layar Anda dan terlihat sangat berbeda dari kode yang sebenarnya.

Saya masih tidak mengatakan itu ide bagus untuk melempar boolean ke mana-mana. Argumen yang mengatakan bahwa Anda membaca kode jauh lebih sering daripada yang Anda tulis seringkali sangat kuat, tetapi untuk kasus khusus ini sangat tergantung pada teknologi yang Anda (dan kolega Anda!) Gunakan untuk mendukung pekerjaan Anda. Dengan IDE itu jauh lebih sedikit masalah dibandingkan dengan misalnya vim.

Contoh modifikasi dari bagian pengaturan pengujian saya: masukkan deskripsi gambar di sini

Sebastiaan van den Broek
sumber
5
Ini hanya akan benar jika semua orang di tim Anda hanya pernah menggunakan IDE untuk membaca kode Anda. Terlepas dari prevalensi editor non-IDE, Anda juga memiliki alat peninjau kode, alat kontrol sumber, pertanyaan Stack Overflow, dll., Dan sangat penting bahwa kode tetap dapat dibaca bahkan dalam konteks tersebut.
Daniel Pryden
@DanielPryden yakin, maka komentar 'dan kolega Anda. Sudah umum memiliki IDE standar di perusahaan. Adapun alat-alat lain, saya berpendapat bahwa sangat mungkin alat-alat semacam itu juga mendukung ini atau akan di masa depan, atau bahwa argumen itu tidak berlaku (seperti untuk tim pemrograman pasangan 2 orang)
Sebastiaan van den Broek
2
@ Sebastiaan: Saya rasa saya tidak setuju. Bahkan sebagai pengembang solo, saya secara teratur membaca kode saya sendiri di Git diffs. Git tidak akan pernah dapat melakukan visualisasi yang sadar konteks yang sama seperti yang dapat dilakukan oleh IDE, begitu pula seharusnya. Saya menggunakan IDE yang baik untuk membantu Anda menulis kode, tetapi Anda tidak boleh menggunakan IDE sebagai alasan untuk menulis kode yang tidak akan Anda tulis tanpa itu.
Daniel Pryden
1
@DanielPryden Saya tidak menganjurkan penulisan kode yang sangat ceroboh. Ini bukan situasi hitam dan putih. Mungkin ada kasus di mana di masa lalu untuk situasi tertentu Anda akan 40% mendukung penulisan fungsi dengan boolean dan 60% tidak dan Anda akhirnya tidak melakukannya. Mungkin dengan dukungan IDE yang bagus sekarang saldo adalah 55% menulis seperti itu dan 45% tidak. Dan ya Anda mungkin masih perlu membacanya di luar IDE kadang-kadang jadi itu tidak sempurna. Tapi itu masih merupakan trade-off yang valid terhadap suatu alternatif, seperti menambahkan metode lain atau enumerasi untuk mengklarifikasi kode sebaliknya.
Sebastiaan van den Broek
6

2 hari dan tidak ada yang menyebutkan polimorfisme?

target.UpdateRow(item);

Ketika saya seorang klien yang ingin memperbarui baris dengan item saya tidak ingin memikirkan nama database, URL layanan mikro, protokol yang digunakan untuk berkomunikasi, atau apakah panggilan eksternal atau tidak akan perlu digunakan untuk mewujudkannya. Berhentilah mendorong detail ini pada saya. Ambil saja barang saya dan perbarui baris di suatu tempat.

Lakukan itu dan itu menjadi bagian dari masalah konstruksi. Itu bisa dipecahkan dengan banyak cara. Ini dia:

Target xyzTargetFactory(TargetBuilder targetBuilder) {
    return targetBuilder
        .connectionString("some connection string")
        .table("table_name")
        .external()
        .build()
    ; 
}

Jika Anda melihat itu dan berpikir "Tapi bahasa saya telah menamai argumen, saya tidak membutuhkan ini". Baik, bagus, gunakan saja. Jauhkan omong kosong ini dari klien panggilan yang seharusnya tidak tahu apa yang ia bicarakan.

Perlu dicatat bahwa ini lebih dari masalah semantik. Ini juga masalah desain. Lewati boolean dan Anda terpaksa menggunakan percabangan untuk menghadapinya. Tidak terlalu berorientasi objek. Ada dua atau lebih boolean yang bisa lewat? Ingin bahasa Anda memiliki beberapa pengiriman? Lihatlah koleksi apa yang bisa dilakukan ketika Anda menyimpannya. Struktur data yang tepat dapat membuat hidup Anda jauh lebih mudah.

candied_orange
sumber
2

Jika Anda dapat memodifikasi updateRowfungsinya, mungkin Anda dapat mengubahnya menjadi dua fungsi. Mengingat bahwa fungsi tersebut mengambil parameter boolean, saya menduga tampilannya seperti ini:

function updateRow(var item, bool externalCall) {
  Database.update(item);

  if (externalCall) {
    Service.call();
  }
}

Itu bisa menjadi sedikit bau kode. Fungsi tersebut mungkin memiliki perilaku yang sangat berbeda tergantung pada externalCallvariabel yang ditetapkan, dalam hal ini ia memiliki dua tanggung jawab yang berbeda. Refactoring menjadi dua fungsi yang hanya memiliki satu tanggung jawab dapat meningkatkan keterbacaan:

function updateRowAndService(var item) {
  updateRow(item);
  Service.call();
}

function updateRow(var item) {
  Database.update(item);
}

Sekarang, di mana saja Anda memanggil fungsi-fungsi ini, Anda dapat mengetahui apakah layanan eksternal sedang dipanggil.

Tentu saja tidak selalu demikian. Itu situasional dan masalah selera. Refactoring suatu fungsi yang mengambil parameter boolean menjadi dua fungsi biasanya layak dipertimbangkan, tetapi tidak selalu merupakan pilihan terbaik.

Kevin
sumber
0

Jika UpdateRow berada dalam basis kode yang dikontrol, saya akan mempertimbangkan pola strategi:

public delegate void Request(string query);    
public void UpdateRow(Item item, Request request);

Dimana Permintaan mewakili beberapa jenis DAO (panggilan balik dalam kasus sepele).

Kasus yang benar:

UpdateRow(item, query =>  queryDatabase(query) ); // perform remote call

Kasus palsu:

UpdateRow(item, query => readLocalCache(query) ); // skip remote call

Biasanya, implementasi titik akhir akan dikonfigurasikan pada tingkat abstraksi yang lebih tinggi dan saya hanya akan menggunakannya di sini. Sebagai efek samping gratis yang bermanfaat, ini memberi saya opsi untuk menerapkan cara lain untuk mengakses data jarak jauh, tanpa mengubah kode:

UpdateRow(item, query => {
  var data = readLocalCache(query);
  if (data == null) {
    data = queryDatabase(query);
  }
  return data;
} );

Secara umum, inversi kontrol semacam itu memastikan sambungan yang lebih rendah antara penyimpanan data dan model Anda.

Basilevs
sumber