Baris tambahan di blok vs parameter tambahan dalam Kode Bersih

33

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?

R. Schmitz
sumber
58
Ada apa dengan ini flurps.Add(CreateFlurp(badaBoom));?
cmaster
47
Tidak, itu hanya satu pernyataan. Itu hanya ekspresi bersarang trivial (level bersarang tunggal). Dan jika sederhana f(g(x))bertentangan dengan panduan gaya Anda, well, saya tidak bisa memperbaiki panduan gaya Anda. Maksud saya, Anda juga tidak terbagi sqrt(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.
cmaster
6
@cmaster Bahkan perakitan x86 tidak sepenuhnya memiliki pernyataan operator tunggal. Mode pengalamatan memori mencakup banyak operasi rumit dan dapat digunakan untuk aritmatika - pada kenyataannya, Anda dapat membuat komputer Turing-lengkap hanya menggunakan movinstruksi x86 dan satu jmp toStartdi akhir. Seseorang benar-benar membuat kompiler yang melakukan hal itu: D
Luaan
5
@Luaan Tidak berbicara tentang rlwimiinstruksi 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 :-)
cmaster
7
@ R.Schmitz "Saya sedang melakukan pemrograman tujuan umum" - sebenarnya tidak, Anda tidak, Anda sedang melakukan pemrograman untuk tujuan tertentu (saya tidak tahu tujuan apa , tapi saya berasumsi Anda melakukannya ;-). Ada ribuan tujuan untuk pemrograman, dan gaya pengkodean yang optimal untuk mereka berbeda-beda - jadi apa yang cocok untuk Anda mungkin tidak cocok untuk orang lain, dan sebaliknya: Seringkali saran di sini mutlak (" selalu lakukan X; Y buruk "dll) mengabaikan bahwa dalam beberapa domain itu sama sekali tidak praktis untuk bertahan. Itu sebabnya saran dalam buku-buku seperti Clean Code harus selalu diambil dengan sejumput garam (praktis) :)
psmears

Jawaban:

104

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 disebut Select. Sesuatu seperti ini:

public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
    return badaBooms.Select(BadaBoom => CreateFlurp(badaBoom)).ToList();
}
amon
sumber
7
Kode masih salah, dan itu tanpa tujuan memulihkan kembali roda. Mengapa menelepon CreateFlurps(someList)ketika BCL sudah menyediakan someList.ConvertAll(CreateFlurp)?
Ben Voigt
44
@ BenVoigt Ini adalah pertanyaan tingkat desain. Saya tidak peduli dengan sintaks yang tepat, terutama karena papan tulis tidak memiliki kompiler (dan saya terakhir menulis C # in '09). Maksud saya bukanlah "Saya telah menunjukkan kode terbaik" tetapi "sebagai tambahan, ini adalah pola umum yang sudah terpecahkan". Linq adalah salah satu cara untuk melakukannya, ConvertAll yang Anda sebutkan lagi . Terima kasih telah menyarankan alternatif itu.
amon
1
Jawaban Anda masuk akal, tetapi fakta bahwa LINQ mengabstraksi logika dan mengurangi pernyataan menjadi satu baris setelah semua tampaknya bertentangan dengan saran Anda. Sebagai catatan, BadaBoom => CreateFlurp(badaBoom)adalah mubazir; Anda dapat lulus CreateFlurpsebagai fungsi secara langsung ( Select(CreateFlurp)). (Sejauh yang saya tahu, ini selalu terjadi.)
jpmc26
2
Perhatikan bahwa ini menghilangkan kebutuhan akan metode sepenuhnya. Nama CreateFlurpsitu sebenarnya lebih menyesatkan dan lebih sulit untuk dipahami daripada sekadar melihat badaBooms.Select(CreateFlurp). Yang terakhir ini sepenuhnya deklaratif - tidak ada masalah untuk diuraikan dan oleh karena itu tidak perlu metode sama sekali.
Carl Leth
1
@ R.Schmitz Tidak sulit untuk dipahami, tetapi lebih sulit untuk dipahami daripada 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, tetapi badaBooms.Select(CreateFlurp)tidak bisa. Ini juga menyesatkan karena keliru meminta Listbukan IEnumerable.
Carl Leth
61

Jumlah ideal argumen untuk suatu fungsi adalah nol (niladic)

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:

public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
    List<Flurp> flurps = new List<Flurp>();
    foreach (BadaBoom badaBoom in badaBooms)
    {
        flurps.Add(CreateFlurp(badaBoom));
    }
    return flurps;
}

Tapi itu kode yang sangat panjang lebar (C #). Lakukan saja sebagai:

IEnumerable<Flurp> CreateFlurps(IEnumerable<BadaBoom> badaBooms) =>
    from badaBoom in babaBooms select CreateFlurp(badaBoom);
David Arno
sumber
14
Fungsi dengan argumen nol dimaksudkan untuk menyiratkan objek merangkum data yang diperlukan bukan bahwa hal-hal yang ada dalam keadaan global di luar objek.
Ryathal
19
@Ryathal, dua poin: (1) jika Anda berbicara metode, maka untuk sebagian besar (semua?) Bahasa OO, objek itu disimpulkan (atau secara eksplisit dinyatakan, dalam kasus Python) sebagai parameter pertama. Di Java, C # dll, semua metode berfungsi dengan setidaknya satu parameter. Kompiler menyembunyikan detail itu dari Anda. (2) Saya tidak pernah menyebut "global". Keadaan objek eksternal untuk metode misalnya.
David Arno
17
Saya cukup yakin, ketika Paman Bob menulis "nol" yang ia maksudkan "nol (tidak termasuk ini)".
Doc Brown
26
@DocBrown, mungkin karena ia penggemar berat negara pencampuran dan fungsionalitas dalam objek, jadi dengan "fungsi" ia cenderung merujuk secara khusus ke metode. Dan saya masih tidak setuju dengannya. Jauh lebih baik hanya memberikan metode apa yang dibutuhkan, daripada membiarkannya menggeledah objek untuk mendapatkan apa yang diinginkannya (yaitu, klasik "katakan, jangan tanya" dalam tindakan).
David Arno
8
@AlessandroTeruzzi, Yang ideal adalah satu parameter. Nol terlalu sedikit. Inilah sebabnya, misalnya, bahasa fungsional mengadopsi satu sebagai jumlah parameter untuk tujuan penjelajahan (pada kenyataannya dalam beberapa bahasa fungsional, semua fungsi memiliki satu parameter: tidak lebih; ​​tidak kurang). Menjelajah dengan parameter nol akan menjadi tidak masuk akal. Menyatakan bahwa "yang ideal adalah sesedikit mungkin, ergo nol adalah yang terbaik" adalah contoh reductio ad absurdum .
David Arno
19

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.

public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
    {
        List<Flurp> flurps = new List<Flurp>();
        foreach (BadaBoom badaBoom in badaBooms)
        {
            flurps.Add(badaBoom .CreateFlurp());
            //or
            badaBoom.AddToListAsFlurp(flurps);
            //or
            flurps.Add(new Flurp(badaBoom));
            //or
            //make flurps a member of the class
            //use linq.Select()
            //etc
        }
        return flurps;
    }

atau

foreach(var flurp in ConvertToFlurps(badaBooms))...

Seperti dicatat oleh orang lain, saran bahwa fungsi terbaik adalah satu tanpa argumen akan condong ke OOP di terbaik dan saran buruk di terburuk

Ewan
sumber
Mungkin Anda ingin mengedit jawaban ini agar lebih jelas? Pertanyaan saya adalah apakah satu hal melebihi yang lain di bawah Kode Bersih. Anda mengatakan bahwa semuanya salah dan kemudian menjelaskan salah satu opsi yang saya berikan. Saat ini jawaban ini sepertinya Anda mengikuti agenda anti-Clean Code alih-alih mencoba menjawab pertanyaan itu.
R. Schmitz
maaf saya menafsirkan pertanyaan Anda sebagai menyarankan yang pertama adalah cara 'normal', tetapi Anda didorong ke yang kedua. Saya tidak anti-kode-bersih secara umum, tetapi kutipan ini jelas salah
Ewan
19
@ R.Schmitz Saya telah membaca "Kode Bersih" sendiri, dan saya mengikuti sebagian besar apa yang dikatakan buku itu. Namun, mengenai ukuran fungsi yang sempurna menjadi hanya satu pernyataan, itu hanya salah. Satu-satunya efek adalah, itu mengubah kode spageti menjadi kode beras. Pembaca tersesat dalam banyak fungsi sepele yang hanya menghasilkan makna yang masuk akal ketika dilihat bersama. Manusia memiliki kapasitas memori kerja yang terbatas, dan Anda bisa membebani itu dengan pernyataan, atau dengan fungsi. Anda harus mencapai keseimbangan di antara keduanya jika Anda ingin dapat dibaca. Hindari yang ekstrem!
cmaster
@ cmaster Jawabannya hanya 2 paragraf pertama ketika saya menulis komentar itu. Itu jawaban yang jauh lebih baik sekarang.
R. Schmitz
7
terus terang saya lebih suka jawaban pendek saya. Terlalu banyak pembicaraan diplomatik dalam sebagian besar jawaban ini. Nasihat yang dikutip itu jelas salah, tidak perlu berspekulasi tentang 'apa arti sebenarnya' atau memutar balik untuk mencoba dan menemukan interpretasi yang baik.
Ewan
15

Kedua pasti lebih buruk, karena CreateFlurpInListmenerima 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,:

public List<Flurp> CreateFlurps(List<BadaBoom> badaBooms)
{
    return badaBooms.Select(CreateFlurp).ToList();
}

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.

Euforia
sumber
Saya tidak akan banyak mengeluh tentang metode itu "tidak murni dan sulit untuk dipikirkan" (walaupun benar), tetapi tentang hal itu menjadi metode yang sama sekali tidak perlu untuk menangani kasus khusus. Bagaimana jika saya ingin membuat Flurp mandiri, Flurp ditambahkan ke array, ke kamus, Flurp yang kemudian akan dicari di kamus dan Flurp yang cocok dihapus dll? Dengan argumen yang sama, kode Flurp akan membutuhkan semua metode ini juga.
gnasher729
10

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: Simple Flurp[], List<Flurp>, LinkedList<Flurp>, Dictionary<Key, Flurp>, dan sebagainya. Tetapi dengan CreateFlurpInList(BadaBoom, List<Flurp>), saya akan kembali kepada Anda besok untuk meminta CreateFlurpInBindingList(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

agak sering saya menemukan diri saya membuat daftar dari daftar lain

Itu hanya masalah menggunakan alat yang tersedia. Versi terpendek, paling efisien, dan terbaik adalah:

var Flurps = badaBooms.ConvertAll(CreateFlurp);

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.

Ben Voigt
sumber
Jangan gunakan List.ConvertAll. Cara idiomatik untuk memetakan enumerable objek ke objek yang berbeda dalam C # disebut Select. Satu-satunya alasan ConvertAllbahkan tersedia di sini adalah karena OP salah meminta Listdalam metode - itu harus menjadi IEnumerable.
Carl Leth
6

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

flups = badaBooms.Select(bb => new Flurp(bb));

mungkin saja kemungkinan. Atau Anda mungkin melakukan sesuatu seperti

flups.Add(new Flurp(badaBoom))

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").

dua kali kamu
sumber