Konteks
Dalam Clean Code , halaman 35, katanya
Ini menyiratkan bahwa blok dalam pernyataan if, statement lain, sedangkan statement, dan seterusnya harus sepanjang satu baris. Mungkin saluran itu harus menjadi pemanggilan fungsi. Ini tidak hanya menjaga fungsi penutup tetap kecil, tetapi juga menambah nilai dokumenter karena fungsi yang disebut di dalam blok dapat memiliki nama deskriptif yang bagus.
Saya sepenuhnya setuju, itu masuk akal.
Kemudian, di halaman 40, dikatakan tentang argumen fungsi
Jumlah argumen ideal untuk suatu fungsi adalah nol (niladik). Berikutnya adalah satu (monadik), diikuti oleh dua (diad). Tiga argumen (triadik) harus dihindari jika memungkinkan. Lebih dari tiga (polyadic) membutuhkan pembenaran yang sangat khusus — dan karenanya tidak seharusnya digunakan. Argumennya sulit. Mereka mengambil banyak kekuatan konseptual.
Saya sepenuhnya setuju, itu masuk akal.
Isu
Namun, agak sering saya menemukan diri saya membuat daftar dari daftar lain dan saya harus hidup dengan salah satu dari dua kejahatan.
Entah saya menggunakan dua baris di blok , satu untuk membuat sesuatu, satu untuk menambahkannya ke hasil:
public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
List<Flurp> flurps = new List<Flurp>();
foreach (BadaBoom badaBoom in badaBooms)
{
Flurp flurp = CreateFlurp(badaBoom);
flurps.Add(flurp);
}
return flurps;
}
Atau saya menambahkan argumen ke fungsi untuk daftar di mana hal itu akan ditambahkan, menjadikannya "satu argumen lebih buruk".
public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
List<Flurp> flurps = new List<Flurp>();
foreach (BadaBoom badaBoom in badaBooms)
{
CreateFlurpInList(badaBoom, flurps);
}
return flurps;
}
Pertanyaan
Apakah ada (dis-) keuntungan yang tidak saya lihat, yang membuat salah satu dari mereka disukai secara umum? Atau adakah keuntungan seperti itu dalam situasi tertentu; dalam hal itu, apa yang harus saya cari ketika membuat keputusan?
sumber
flurps.Add(CreateFlurp(badaBoom));
?f(g(x))
bertentangan dengan panduan gaya Anda, well, saya tidak bisa memperbaiki panduan gaya Anda. Maksud saya, Anda juga tidak terbagisqrt(x*x + y*y)
menjadi empat baris, bukan? Dan itu tiga (!) Bersarang subekspresi pada dua (!) Tingkat bersarang batin (terkesiap!). Tujuan Anda harus dapat dibaca , bukan pernyataan operator tunggal. Jika Anda menginginkannya nanti, well, saya memiliki bahasa yang sempurna untuk Anda: Assembler.mov
instruksi x86 dan satujmp toStart
di akhir. Seseorang benar-benar membuat kompiler yang melakukan hal itu: Drlwimi
instruksi terkenal di PPC. (Itu adalah singkatan dari Rotate Left Word Immediate Mask Insert.) Perintah ini memakan waktu tidak kurang dari lima operan (dua register, dan tiga nilai langsung), dan melakukan operasi berikut: Satu konten register diputar oleh shift langsung, mask adalah dibuat dengan menjalankan tunggal 1 bit yang dikendalikan oleh dua operan langsung lainnya, dan bit yang sesuai dengan 1 bit dalam topeng itu di operan register lainnya diganti dengan bit yang sesuai dari register yang diputar. Instruksi sangat keren :-)Jawaban:
Pedoman ini adalah kompas, bukan peta. Mereka mengarahkan Anda ke arah yang masuk akal . Tetapi mereka tidak dapat benar-benar memberi tahu Anda secara absolut solusi mana yang "terbaik". Pada titik tertentu, Anda perlu berhenti berjalan ke arah yang ditunjukkan kompas Anda, karena Anda telah tiba di tempat tujuan.
Clean Code mendorong Anda untuk membagi kode Anda menjadi blok yang sangat kecil dan jelas. Itu adalah arahan yang umumnya baik. Tetapi ketika dibawa ke ekstrem (seperti interpretasi literal dari saran yang dikutip), maka Anda akan membagi kode Anda menjadi potongan-potongan kecil yang tidak berguna. Tidak ada yang benar-benar melakukan apa-apa, semuanya hanya delegasi. Ini pada dasarnya adalah jenis lain dari kebingungan kode.
Adalah tugas Anda untuk menyeimbangkan "lebih kecil lebih baik" terhadap "terlalu kecil tidak berguna". Tanyakan pada diri Anda solusi mana yang lebih sederhana. Bagi saya, itu jelas solusi pertama karena jelas merakit daftar. Ini adalah ungkapan yang dipahami dengan baik. Dimungkinkan untuk memahami kode itu tanpa harus melihat fungsi lainnya.
Jika mungkin untuk melakukan yang lebih baik, itu dengan mencatat bahwa "mengubah semua elemen dari daftar ke daftar lain" adalah pola umum yang sering dapat diabstraksi, dengan menggunakan
map()
operasi fungsional . Dalam C #, saya pikir itu disebutSelect
. Sesuatu seperti ini:sumber
CreateFlurps(someList)
ketika BCL sudah menyediakansomeList.ConvertAll(CreateFlurp)
?BadaBoom => CreateFlurp(badaBoom)
adalah mubazir; Anda dapat lulusCreateFlurp
sebagai fungsi secara langsung (Select(CreateFlurp)
). (Sejauh yang saya tahu, ini selalu terjadi.)CreateFlurps
itu sebenarnya lebih menyesatkan dan lebih sulit untuk dipahami daripada sekadar melihatbadaBooms.Select(CreateFlurp)
. Yang terakhir ini sepenuhnya deklaratif - tidak ada masalah untuk diuraikan dan oleh karena itu tidak perlu metode sama sekali.badaBooms.Select(CreateFlurp)
. Anda membuat metode sehingga namanya (level tinggi) mendukung implementasinya (level rendah). Dalam hal ini mereka berada pada level yang sama, jadi untuk mencari tahu apa yang sebenarnya terjadi, saya harus melihat metode tersebut (daripada melihatnya secara langsung).CreateFlurps(badaBooms)
bisa menahan kejutan, tetapibadaBooms.Select(CreateFlurp)
tidak bisa. Ini juga menyesatkan karena keliru memintaList
bukanIEnumerable
.Tidak! Jumlah ideal argumen untuk suatu fungsi adalah satu. Jika nol, maka Anda menjamin bahwa fungsi tersebut harus mengakses informasi eksternal untuk dapat melakukan suatu tindakan. "Paman," Bob salah mengerti.
Mengenai kode Anda, contoh pertama Anda hanya memiliki dua baris di blok karena Anda membuat variabel lokal di baris pertama. Hapus tugas itu, dan Anda mematuhi pedoman kode bersih ini:
Tapi itu kode yang sangat panjang lebar (C #). Lakukan saja sebagai:
sumber
Nasihat 'Kode Bersih' sepenuhnya salah.
Gunakan dua atau lebih garis dalam lingkaran Anda. Menyembunyikan dua baris yang sama dalam suatu fungsi masuk akal ketika mereka adalah beberapa matematika acak yang membutuhkan deskripsi tetapi tidak melakukan apa-apa ketika garis sudah deskriptif. 'Buat' dan 'Tambah'
Metode kedua yang Anda sebutkan tidak benar-benar masuk akal, karena Anda tidak dipaksa untuk menambahkan argumen kedua untuk menghindari dua baris.
atau
Seperti dicatat oleh orang lain, saran bahwa fungsi terbaik adalah satu tanpa argumen akan condong ke OOP di terbaik dan saran buruk di terburuk
sumber
Kedua pasti lebih buruk, karena
CreateFlurpInList
menerima daftar dan memodifikasi daftar itu, membuat fungsi tidak murni dan sulit untuk dipikirkan. Tidak ada dalam nama metode yang menyarankan metode hanya menambah daftar.Dan saya menawarkan opsi ketiga, terbaik,:
Dan sial, Anda bisa langsung menguraikan metode itu jika hanya ada satu tempat di mana ia digunakan, karena satu-baris jelas dengan sendirinya, sehingga tidak perlu dienkapsulasi dengan metode untuk memberikan makna.
sumber
Versi satu argumen lebih baik, tetapi bukan terutama karena jumlah argumen.
Alasan yang paling penting adalah lebih baik karena memiliki kopling yang lebih rendah , yang membuatnya lebih berguna, lebih mudah untuk dipertimbangkan, lebih mudah untuk diuji, dan kecil kemungkinannya untuk berubah menjadi salinan + klon yang disisipkan.
Jika Anda memberikan saya dengan
CreateFlurp(BadaBoom)
, saya dapat menggunakan dengan semua jenis kontainer koleksi: SimpleFlurp[]
,List<Flurp>
,LinkedList<Flurp>
,Dictionary<Key, Flurp>
, dan sebagainya. Tetapi denganCreateFlurpInList(BadaBoom, List<Flurp>)
, saya akan kembali kepada Anda besok untuk memintaCreateFlurpInBindingList(BadaBoom, BindingList<Flurp>)
agar model tampilan saya dapat menerima pemberitahuan bahwa daftar tersebut berubah. Huek!Sebagai manfaat tambahan, tanda tangan yang lebih sederhana lebih cocok dengan API yang ada. Anda mengatakan Anda memiliki masalah berulang
Itu hanya masalah menggunakan alat yang tersedia. Versi terpendek, paling efisien, dan terbaik adalah:
Kode ini tidak hanya lebih sedikit untuk Anda tulis dan uji, tetapi juga lebih cepat, karena
List<T>.ConvertAll()
cukup pintar untuk mengetahui bahwa hasilnya akan memiliki jumlah item yang sama dengan input, dan mengalokasikan ulang daftar hasil ke ukuran yang benar. Sementara kode Anda (kedua versi) diperlukan menumbuhkan daftar.sumber
List.ConvertAll
. Cara idiomatik untuk memetakan enumerable objek ke objek yang berbeda dalam C # disebutSelect
. Satu-satunya alasanConvertAll
bahkan tersedia di sini adalah karena OP salah memintaList
dalam metode - itu harus menjadiIEnumerable
.Ingatlah tujuan keseluruhan: membuat kode mudah dibaca dan dipelihara.
Seringkali, akan mungkin untuk mengelompokkan beberapa baris menjadi satu fungsi yang bermakna. Lakukan dalam kasus ini. Terkadang, Anda perlu mempertimbangkan kembali pendekatan umum Anda.
Misalnya, dalam kasus Anda, ganti seluruh implementasi dengan var
mungkin saja kemungkinan. Atau Anda mungkin melakukan sesuatu seperti
Kadang-kadang, solusi terbersih dan paling mudah dibaca tidak akan cocok dalam satu baris. Jadi, Anda akan memiliki dua baris. Jangan membuat kode lebih sulit untuk dipahami, hanya untuk memenuhi beberapa aturan sewenang-wenang.
Contoh kedua Anda (menurut saya) jauh lebih sulit untuk dipahami daripada yang pertama. Bukan hanya karena Anda memiliki parameter kedua, itu adalah parameter diubah oleh fungsi. Lihat apa yang dikatakan Clean Code tentang hal itu. (Tidak punya buku saat ini, tapi saya cukup yakin itu pada dasarnya "jangan lakukan itu jika Anda bisa menghindarinya").
sumber