Saya telah melihat latihan dari waktu ke waktu bahwa "merasa" salah, tapi aku bisa't cukup mengartikulasikan apa yang salah tentang hal itu. Atau mungkin itu's hanya prasangka saya. Here goes:
Pengembang mendefinisikan sebuah metode dengan boolean sebagai salah satu parameter, dan bahwa metode panggilan yang lain, dan seterusnya, dan akhirnya boolean yang digunakan, semata-mata untuk menentukan apakah atau tidak untuk mengambil tindakan tertentu. Ini dapat digunakan, misalnya, untuk memungkinkan tindakan hanya jika pengguna memiliki hak-hak tertentu, atau mungkin jika kita (atau tidak't) dalam test mode atau modus batch atau mode live, atau mungkin hanya ketika sistem dalam keadaan tertentu.
Yah selalu ada cara lain untuk melakukan hal itu, apakah dengan query ketika saatnya untuk mengambil tindakan (bukan melewati parameter), atau dengan memiliki beberapa versi dari metode, atau beberapa implementasi dari kelas, dll. Pertanyaan saya isn't begitu banyak cara untuk meningkatkan ini, melainkan apakah atau tidak itu benar-benar salah (seperti yang saya duga), dan jika itu adalah, apa yang salah tentang hal itu.
Saya berhenti menggunakan ini pola lama yang lalu, untuk alasan yang sangat sederhana, biaya pemeliharaan. Beberapa kali saya menemukan bahwa saya memiliki beberapa fungsi yang mengatakan frobnicate(sesuatu, forwards_flag)
yang disebut berkali-kali dalam kode saya, dan diperlukan untuk menemukan semua tempat di dalam kode di mana nilai palsu
yang telah disahkan sebagai nilai forwards_flag
. Anda dapat't dengan mudah mencari mereka, sehingga hal ini menjadi sebuah pemeliharaan sakit kepala. Dan jika anda perlu untuk membuat sebuah bugfix pada masing-masing situs tersebut, anda mungkin memiliki masalah disayangkan jika anda melewatkan satu.
Tapi ini khusus masalah ini mudah diperbaiki tanpa fundamental mengubah pendekatan:
enum FrobnicationDirection {
FrobnicateForwards,
FrobnicateBackwards;
};
void frobnicate(Object what, FrobnicationDirection direction);
Dengan kode ini, seseorang hanya perlu untuk mencari contoh-contoh FrobnicateBackwards
. Sementara itu's mungkin ada beberapa kode yang memberikan ini untuk variabel sehingga anda harus mengikuti beberapa benang dari kontrol, saya menemukan dalam praktek yang ini cukup jarang terjadi bahwa alternatif ini bekerja OK.
Namun, ada masalah lain dengan melewati bendera dengan cara ini, setidaknya pada prinsipnya. Ini adalah bahwa beberapa (hanya beberapa) sistem yang memiliki desain ini dapat mengekspos terlalu banyak pengetahuan tentang rincian pelaksanaan sangat-bersarang bagian dari kode (yang menggunakan bendera) untuk lapisan luar (yang perlu tahu mana nilai untuk lulus dalam bendera ini). Untuk menggunakan Larry Constantine's terminologi, desain ini mungkin lebih kuat kopling antara setter dan pengguna boolean flag. Franky meskipun itu's sulit untuk mengatakan dengan tingkat kepastian pada pertanyaan ini tanpa mengetahui lebih lanjut tentang codebase.
Untuk alamat tertentu contoh yang anda berikan, saya akan memiliki beberapa tingkat kekhawatiran di masing-masing tapi terutama karena alasan risiko/kebenaran. Artinya, jika kebutuhan sistem anda untuk lulus sekitar bendera yang menunjukkan apa negara dalam sistem, anda mungkin menemukan bahwa anda've punya kode yang harus diperhitungkan, tapi doesn't memeriksa parameter (karena itu tidak berlalu untuk fungsi ini). Jadi anda memiliki bug karena seseorang dihilangkan untuk melewatkan parameter.
It's juga perlu mengakui bahwa sistem negara-indikator yang perlu untuk diteruskan ke hampir setiap fungsi sebenarnya pada dasarnya variabel global. Banyak kelemahan dari sebuah variabel global yang akan berlaku. Saya pikir dalam banyak kasus itu adalah praktek yang lebih baik untuk merangkum pengetahuan tentang sistem negara (atau pengguna's kredensial, atau sistem's identity) di dalam suatu benda yang bertanggung jawab untuk bertindak dengan benar berdasarkan data tersebut. Kemudian anda melewati sekitar referensi ke objek yang dibandingkan dengan data mentah. Konsep kunci di sini encapsulation.
Ya, kemungkinan besar ini adalah kode bau, yang akan menyebabkan unmaintainable kode yang sulit dimengerti dan yang memiliki kesempatan yang lebih rendah dari yang mudah digunakan kembali.
Sebagai poster lain telah mencatat konteks adalah segalanya (don't-tiba masuk jika itu's merupakan salah satu off atau jika praktek telah diakui sebagai sengaja dikeluarkan teknis utang yang harus kembali diperhitungkan nanti) tetapi secara umum jika ada parameter yang dilewatkan ke fungsi yang memilih perilaku tertentu yang akan dijalankan maka selanjutnya langkah-langkah perbaikan yang diperlukan; Breaking up ini berfungsi dalam lebih kecil fungsi akan menghasilkan lebih banyak sangat kohesif orang-orang.
Jadi apa yang sangat kohesif fungsi?
It's sebuah fungsi yang melakukan satu hal dan satu hal saja.
Masalah dengan parameter yang dilewatkan dalam seperti yang anda sebutkan, adalah bahwa fungsi adalah melakukan lebih dari dua hal; mungkin atau mungkin tidak memeriksa pengguna hak akses tergantung pada keadaan parameter Boolean, maka tergantung pada keputusan bahwa pohon itu akan melaksanakan bagian dari fungsi.
Akan lebih baik untuk memisahkan masalah Kontrol Akses dari keprihatinan Tugas, Tindakan atau Perintah.
Seperti yang anda telah ketahui, terjalinnya kekhawatiran ini tampaknya off.
Jadi gagasan Kekompakan membantu kami mengidentifikasi bahwa fungsi tersebut tidak sangat kohesif dan bahwa kita bisa refactor kode untuk menghasilkan satu set yang lebih kohesif fungsi.
Jadi pertanyaan bisa disajikan kembali; Mengingat bahwa kita semua setuju lewat perilaku pemilihan parameter yang sebaiknya dihindari bagaimana kita bisa memperbaiki hal ini?
Aku akan menyingkirkan dari parameter sama sekali. Memiliki kemampuan untuk menonaktifkan akses kontrol bahkan untuk pengujian adalah potensi risiko keamanan. Untuk tujuan pengujian baik [tag:rintisan] atau [tag:pura-pura] akses periksa untuk tes kedua akses yang diperbolehkan dan akses ditolak skenario.
Hal ini tidak selalu salah, tetapi hal ini dapat mewakili kode bau.
Dasar skenario yang harus dihindari mengenai parameter boolean adalah:
public void foo(boolean flag) {
doThis();
if (flag)
doThat();
}
Kemudian ketika memanggil anda'd biasanya menyebutnya foo(palsu)
dan foo(benar)
tergantung pada perilaku yang tepat yang anda inginkan.
Ini adalah benar-benar masalah karena itu's merupakan kasus buruk kohesi. Anda're menciptakan ketergantungan antara metode yang tidak benar-benar diperlukan.
Apa yang seharusnya anda lakukan dalam hal ini adalah meninggalkan melakukanini
dan doThat
sebagai terpisah dan metode-metode umum kemudian lakukan:
doThis();
doThat();
atau
doThis();
Dengan cara itu anda meninggalkan keputusan yang benar untuk pemanggil (persis seperti jika anda melewati sebuah parameter boolean) tanpa membuat kopling.
Tentu saja tidak semua parameter boolean yang digunakan dalam cara yang buruk, tapi itu's pasti kode bau dan anda're hak untuk curiga jika anda melihat bahwa banyak di kode sumber.
Ini adalah salah satu contoh dari bagaimana untuk memecahkan masalah ini berdasarkan contoh-contoh yang saya tulis. Ada kasus lain di mana pendekatan yang berbeda akan diperlukan.
Ada yang baik artikel dari Martin Fowler menjelaskan dalam rincian lebih lanjut ide yang sama.
PS: jika metode foo
alih-alih memanggil dua metode sederhana memiliki lebih kompleks implementasi kemudian semua yang harus anda lakukan adalah oleskan kecil refactoring penggalian metode jadi kode yang dihasilkan terlihat mirip dengan pelaksanaan foo
yang saya tulis.
Pertama: pemrograman adalah bukan ilmu pengetahuan yang begitu banyak seperti itu adalah sebuah seni. Jadi ada sangat jarang "salah" dan "benar" cara untuk program. Paling coding-standar hanya "preferensi" bahwa beberapa programmer mempertimbangkan berguna, tetapi pada akhirnya mereka agak sewenang-wenang. Jadi saya tidak akan pernah label pilihan parameter untuk menjadi "salah" di dan dari dirinya sendiri-dan tentu saja bukan sesuatu yang generik dan berguna sebagai parameter boolean. Penggunaan boolean
(atau int
, dalam hal ini) untuk merangkum negara ini sangat dibenarkan dalam banyak kasus.
Coding keputusan, oleh & besar, harus didasarkan terutama pada kinerja dan pemeliharaan. Jika kinerja isn't dipertaruhkan (dan saya dapat't membayangkan bagaimana hal itu bisa menjadi contoh anda), maka anda berikutnya harus dipertimbangkan: cara mudah ini akan menjadi bagi saya (atau masa depan reduktor) untuk mempertahankan? Itu intuitif dan mudah dipahami? Itu terisolasi? Contoh dari fungsi panggilan dirantai sesungguhnya tampak berpotensi rapuh dalam hal ini: jika anda memutuskan untuk mengubah anda bIsUp
untuk bIsDown
, berapa banyak tempat-tempat lain di kode tersebut akan perlu diubah juga? Juga, anda paramater daftar balon? Jika fungsi memiliki 17 parameter, kemudian pembacaan masalah, dan anda perlu mempertimbangkan kembali apakah anda menghargai manfaat dari object-oriented architecture.
Saya pikir Robert C Martins kode yang Bersih artikel menyatakan bahwa anda harus menghilangkan boolean argumen mana mungkin karena mereka menunjukkan sebuah metode tidak lebih dari satu hal. Metode harus melakukan satu hal dan satu hal saja yang saya pikir adalah salah satu motto.
Saya pikir yang paling penting di sini adalah untuk menjadi praktis.
Ketika boolean yang menentukan seluruh perilaku, hanya membuat kedua metode.
Ketika boolean hanya menentukan sedikit perilaku di tengah, anda mungkin ingin untuk tetap di salah satu untuk mengurangi duplikasi kode. Mana mungkin, anda bahkan mungkin bisa membagi metode tiga: Dua memanggil metode yang baik untuk boolean pilihan, dan salah satu yang melakukan sebagian besar pekerjaan.
Misalnya:
private void FooInternal(bool flag)
{
//do work
}
public void Foo1()
{
FooInternal(true);
}
public void Foo2()
{
FooInternal(false);
}
Tentu saja, dalam prakteknya anda'll selalu memiliki titik di antara ekstrem. Biasanya aku hanya pergi dengan apa yang terasa benar, tapi aku lebih memilih untuk berbuat salah di sisi kurang duplikasi kode.
Saya suka pendekatan menyesuaikan perilaku melalui pembangun metode yang kembali berubah contoh. Berikut ini's bagaimana Jambu biji Splitter
menggunakannya:
private static final Splitter MY_SPLITTER = Splitter.on(',')
.trimResults()
.omitEmptyStrings();
MY_SPLITTER.split("one,,,, two,three");
Manfaat dari hal ini adalah:
Splitter
. Anda'a tidak pernah menempatkan someVaguelyStringRelatedOperation(Daftar<Badan> myEntities)
di kelas yang disebut Splitter
, tapi anda'a berpikir tentang menempatkan itu sebagai sebuah metode statis dalam StringUtils
kelas.true
atau false
untuk sebuah metode untuk mendapatkan perilaku yang benar.TL;DR: don't menggunakan boolean argumen.
Lihat di bawah mengapa mereka yang buruk, dan bagaimana untuk menggantikan mereka (di bold wajah).
Boolean argumen yang sangat sulit untuk dibaca, dan dengan demikian sulit untuk mempertahankan. Masalah utama adalah bahwa tujuan umumnya jelas ketika anda membaca metode tanda tangan di mana argumen yang bernama. Namun, penamaan parameter ini umumnya tidak diperlukan dalam kebanyakan bahasa. Jadi, anda akan memiliki anti-pola seperti [RSACryptoServiceProvider#enkripsi(Byte[], Boolean)
](https://msdn.microsoft.com/en-us/library/f17a0e2k(v=vs. 110).aspx) dimana parameter boolean yang menentukan apa jenis enkripsi yang akan digunakan dalam fungsi.
Jadi anda akan mendapatkan panggilan seperti:
rsaProvider.encrypt(data, true);
di mana pembaca untuk pencarian metode's signature hanya untuk menentukan apa yang benar
bisa benar-benar berarti. Lewat sebuah bilangan bulat ini tentu saja hanya sebagai buruk:
rsaProvider.encrypt(data, 1);
akan memberitahu anda banyak - atau lebih tepatnya: hanya sedikit. Bahkan jika anda mendefinisikan konstanta yang akan digunakan untuk bilangan bulat maka pengguna fungsi mungkin hanya mengabaikan mereka dan tetap menggunakan nilai literal.
Cara terbaik untuk memecahkan masalah ini adalah dengan menggunakan pencacahan. Jika anda harus melewati sebuah enum RSAPadding
dengan dua nilai: OAEP
atau PKCS1_V1_5
maka anda akan segera mampu membaca kode:
rsaProvider.encrypt(data, RSAPadding.OAEP);
Boolean hanya dapat memiliki dua nilai. Ini berarti bahwa jika anda memiliki pilihan ketiga, maka anda'd harus refactor tanda tangan anda. Umumnya ini tidak dapat dengan mudah dilakukan jika mundur kompatibilitas masalah, sehingga anda'd harus memperpanjang setiap kelas yang umum dengan yang lain metode publik. Ini adalah apa yang Microsoft akhirnya lakukan ketika mereka diperkenalkan [RSACryptoServiceProvider#enkripsi(Byte[], RSAEncryptionPadding)
](https://msdn.microsoft.com/en-us/library/mt132683(v=vs. 110).aspx) di mana mereka digunakan enumerasi (atau setidaknya kelas meniru pencacahan) bukan boolean.
Bahkan mungkin lebih mudah untuk menggunakan full objek atau antarmuka sebagai parameter, dalam hal parameter itu sendiri harus diberi parameter. Dalam contoh di atas OAEP padding itu sendiri bisa menjadi parameter dengan nilai hash untuk penggunaan internal. Perhatikan bahwa sekarang ada 6 SHA-2 algoritma hash dan 4 SHA-3 algoritma hash, sehingga jumlah nilai enumerasi dapat meledak jika anda hanya menggunakan satu pencacahan daripada parameter (ini mungkin adalah hal berikutnya Microsoft akan mengetahui).
Parameter Boolean juga dapat menunjukkan bahwa metode atau kelas yang tidak dirancang dengan baik. Seperti contoh di atas: setiap kriptografi perpustakaan lain .NET salah satu doesn't menggunakan padding bendera dalam metode tanda tangan sama sekali.
Hampir semua perangkat lunak guru bahwa saya ingin memperingatkan terhadap boolean argumen. Misalnya, Joshua Bloch memperingatkan terhadap mereka sangat dihargai buku "Efektif Jawa". Pada umumnya mereka hanya tidak boleh digunakan. Anda bisa berpendapat bahwa mereka dapat digunakan jika kasus itu ada satu parameter yang mudah untuk memahami. Tetapi bahkan kemudian: Sedikit.set(boolean)
mungkin lebih baik diimplementasikan dengan menggunakan dua metode: Sedikit.set()
dan Sedikit.unset()
.
Jika anda tidak dapat langsung refactor kode yang anda bisa mendefinisikan konstanta untuk setidaknya membuat mereka lebih mudah dibaca:
const boolean ENCRYPT = true;
const boolean DECRYPT = false;
...
cipher.init(key, ENCRYPT);
adalah jauh lebih mudah dibaca dari:
cipher.init(key, true);
bahkan jika anda'd suka:
cipher.initForEncryption(key);
cipher.initForDecryption(key);
sebaliknya.
Pasti kode bau. Jika itu doesn't melanggar Single tanggung Jawab Prinsip maka mungkin melanggar Katakan, Don't Ask. Pertimbangkan:
Jika ternyata tidak melanggar salah satu dari dua prinsip, anda masih harus menggunakan enum. Boolean flag adalah boolean setara - angka ajaib. foo(palsu)
masuk akal seperti bar(42)
. Enums dapat berguna untuk Pola Strategi dan memiliki fleksibilitas membiarkan anda menambahkan strategi lain. (Hanya ingat untuk nama mereka dengan tepat.)
Anda contoh khusus terutama mengganggu saya. Mengapa bendera ini melewati begitu banyak metode? Ini terdengar seperti anda perlu untuk split parameter anda menjadi subclass.
I'm terkejut tidak ada yang telah disebutkan bernama-parameter.
Masalah yang saya lihat dengan boolean-bendera adalah bahwa mereka menyakiti mudah dibaca. Misalnya, apa yang benar
di
myObject.UpdateTimestamps(true);
lakukan? Saya tidak punya ide. Tetapi apa yang tentang:
myObject.UpdateTimestamps(saveChanges: true);
Sekarang ini's jelas apa parameter kita're lewat dimaksudkan untuk melakukan: kita're menceritakan fungsi untuk menyimpan perubahan. Dalam hal ini, jika kelas non-publik, saya pikir parameter boolean baik-baik saja.
Tentu saja, anda dapat't memaksa pengguna kelas anda untuk menggunakan nama-parameter. Untuk alasan ini, baik yang enum
atau dua metode yang biasanya disukai, tergantung pada seberapa sering anda default kasus ini. Ini adalah persis apa yang .Net tidak:
//Enum used
double a = Math.Round(b, MidpointRounding.AwayFromZero);
//Two separate methods used
IEnumerable<Stuff> ascendingList = c.OrderBy(o => o.Key);
IEnumerable<Stuff> descendingList = c.OrderByDescending(o => o.Key);
Saya setuju dengan semua kekhawatiran yang menggunakan Boolean Parameter untuk menentukan kinerja dalam rangka untuk, meningkatkan, mudah Dibaca, Keandalan, menurunkan Kompleksitas, Menurunkan risiko dari miskin Enkapsulasi & Kohesi dan menurunkan Total Biaya Kepemilikan dengan Rawatan.
Saya mulai merancang perangkat keras di pertengahan 70's yang sekarang kita sebut SCADA (supervisory control and data acquisition) dan ini adalah baik-baik saja disetel keras dengan kode mesin dalam EPROM menjalankan makro remote kontrol dan mengumpulkan data kecepatan tinggi.
Logika disebut Mealey & Moore mesin yang kita sebut sekarang Finite State Machines. Ini juga harus dilakukan dengan aturan yang sama seperti di atas, kecuali itu adalah real time machine dengan terbatas waktu eksekusi dan kemudian pintas harus dilakukan untuk melayani tujuan.
Data sinkron tetapi perintah asynchronous dan perintah logika berikut memoryless Boolean logika tapi dengan berurutan perintah berdasarkan memori dari sebelumnya, saat ini dan yang diinginkan negara berikutnya. Dalam rangka untuk itu untuk fungsi yang paling efisien bahasa mesin (hanya 64kB), great perawatan diambil untuk menentukan setiap proses heuristik IBM HIPO fashion. Yang kadang-kadang berarti melewati Boolean variabel dan melakukan diindeks cabang.
Tapi sekarang dengan banyak memori dan kemudahan OOK, Enkapsulasi merupakan unsur penting hari ini, tapi penalti ketika kode yang dihitung dalam satuan byte untuk real time dan SCADA kode mesin..
aku't cukup mengartikulasikan apa yang salah tentang hal itu.
Jika itu terlihat seperti kode bau, terasa seperti kode bau dan - baik - bau kode bau, it's mungkin kode bau.
Apa yang ingin anda lakukan adalah:
Menghindari metode dengan efek samping.
Menangani diperlukan, negara-negara sentral, negara formal-mesin (seperti ini).
Yang belum tentu salah, tapi dalam contoh konkret dari tindakan tergantung pada beberapa atribut "user" aku akan melewati sebuah referensi untuk pengguna daripada sebuah bendera.
Ini menjelaskan dan membantu dalam beberapa cara.
Siapa pun yang membaca menyerukan pernyataan akan menyadari bahwa hasilnya akan berubah tergantung pada pengguna.
Dalam fungsi yang pada akhirnya disebut anda dapat dengan mudah menerapkan banyak aturan bisnis yang kompleks karena dapat anda akses setiap pengguna atribut.
Jika salah satu fungsi/metode dalam "jaringan" apakah sesuatu yang berbeda tergantung pada atribut pengguna, hal ini sangat mungkin bahwa yang mirip ketergantungan pada atribut pengguna akan diperkenalkan dengan beberapa metode lain dalam "jaringan".
Sebagian besar waktu, saya akan mempertimbangkan ini buruk coding. Saya bisa memikirkan dua kasus, namun, di mana hal ini dapat menjadi praktek yang baik. Karena ada banyak jawaban sudah mengatakan mengapa hal itu's buruk, saya menawarkan dua kali saat itu mungkin baik:
Yang pertama adalah jika setiap panggilan dalam urutan yang masuk akal dalam dirinya sendiri. Itu akan masuk akal jika kode panggilan might diubah dari true ke false atau false ke true, or jika disebut metode might diubah dengan menggunakan parameter boolean langsung daripada lewat di. Kemungkinan sepuluh panggilan tersebut berturut-turut adalah kecil, tapi itu bisa terjadi, dan jika tidak itu akan menjadi praktek pemrograman yang baik.
Kasus kedua adalah sedikit peregangan mengingat bahwa kita're berhadapan dengan boolean. Tapi jika program ini memiliki beberapa benang atau peristiwa, lewat parameter adalah cara paling sederhana untuk melacak benang/acara tertentu data. Sebagai contoh, sebuah program dapat mendapatkan masukan dari dua atau lebih soket. Kode berjalan selama satu soket mungkin perlu untuk menghasilkan pesan peringatan, dan orang lain mungkin tidak. Kemudian (semacam) akal untuk boolean ditetapkan pada tingkat yang sangat tinggi untuk bisa melewati banyak metode panggilan ke tempat-tempat di mana pesan peringatan yang mungkin dihasilkan. Data dapat't akan disimpan (kecuali dengan kesulitan besar) urut-urutan global karena beberapa thread atau disisipkan acara masing-masing akan membutuhkan nilai mereka sendiri.
Untuk memastikan, dalam kasus terakhir saya'd mungkin membuat kelas/struktur yang hanya konten boolean dan lulus that sekitar sebaliknya. I'a akan hampir pasti membutuhkan bidang lain sebelum terlalu lama--seperti where untuk mengirim pesan peringatan, misalnya.
Konteks adalah penting. Metode tersebut cukup umum di iOS. Sebagai salah satu yang sering digunakan misalnya, UINavigationController menyediakan metode -pushViewController:animasi:
, dan animasi
parameter BOOL. Metode ini pada dasarnya melakukan fungsi yang sama dengan cara baik, tapi itu menjiwai transisi dari satu view controller lain jika anda lulus dalam YA, dan doesn't jika anda TIDAK lulus. Ini tampaknya sepenuhnya wajar; itu'd konyol harus menyediakan dua metode di tempat yang satu ini agar anda bisa menentukan apakah atau tidak untuk menggunakan animasi.
Mungkin akan lebih mudah untuk membenarkan hal semacam ini di Objective-C, di mana metode-penamaan sintaks menyediakan konteks yang lebih banyak untuk masing-masing parameter dari yang anda dapatkan dalam bahasa seperti C dan Java. Namun demikian, saya'a berpikir bahwa metode yang mengambil satu parameter yang bisa dengan mudah mengambil boolean dan masih masuk akal:
file.saveWithEncryption(false); // is there any doubt about the meaning of 'false' here?