Demi argumen, inilah fungsi sampel yang mencetak konten file yang diberikan baris demi baris.
Versi 1:
void printFile(const string & filePath) {
fstream file(filePath, ios::in);
string line;
while (std::getline(file, line)) {
cout << line << endl;
}
}
Saya tahu direkomendasikan bahwa fungsi melakukan satu hal pada satu tingkat abstraksi. Bagi saya, meskipun kode di atas tidak cukup banyak satu hal dan cukup atom.
Beberapa buku (seperti Kode Bersih Robert C. Martin) tampaknya menyarankan memecah kode di atas menjadi fungsi yang terpisah.
Versi 2:
void printFile(const string & filePath) {
fstream file(filePath, ios::in);
printLines(file);
}
void printLines(fstream & file) {
string line;
while (std::getline(file, line)) {
printLine(line);
}
}
void printLine(const string & line) {
cout << line << endl;
}
Saya mengerti apa yang ingin mereka capai (buka file / baca baris / print line), tetapi bukankah itu sedikit berlebihan?
Versi aslinya sederhana dan dalam beberapa hal sudah melakukan satu hal - mencetak file.
Versi kedua akan menghasilkan sejumlah besar fungsi yang sangat kecil yang mungkin jauh lebih tidak terbaca dari versi pertama.
Bukankah, dalam hal ini, lebih baik memiliki kode di satu tempat?
Pada titik mana paradigma "Do One Thing" menjadi berbahaya?
printFile
,printLines
dan akhirnyaprintLine
.Jawaban:
Tentu saja, ini hanya menimbulkan pertanyaan "Apa itu satu hal?" Apakah membaca satu baris adalah satu hal dan menulis satu baris lagi? Atau menyalin garis dari satu aliran ke yang lain dianggap satu hal? Atau menyalin file?
Tidak ada jawaban yang sulit dan obyektif untuk itu. Terserah kamu. Anda bisa memutuskan. Anda harus memutuskan. Tujuan utama paradigma "do one thing" mungkin untuk menghasilkan kode yang semudah mungkin dipahami, sehingga Anda dapat menggunakannya sebagai pedoman. Sayangnya, ini juga tidak dapat diukur secara objektif, jadi harus mengandalkan firasat Anda dan "WTF?" hitung dalam ulasan kode .
IMO suatu fungsi yang terdiri dari hanya satu baris kode jarang yang sepadan dengan masalahnya. Anda
printLine()
tidak memiliki keuntungan menggunakanstd::cout << line << '\n'
1 secara langsung. Jika saya melihatprintLine()
, saya harus menganggapnya melakukan apa yang dikatakan namanya, atau mencarinya dan memeriksa. Jika saya melihatstd::cout << line << '\n'
, saya langsung tahu apa fungsinya, karena ini adalah cara kanonik untuk mengeluarkan konten string sebagai barisstd::cout
.Namun, tujuan penting lain dari paradigma adalah untuk memungkinkan penggunaan kembali kode, dan itu ukuran yang jauh lebih objektif. Misalnya, dalam versi ke-2 Anda
printLines()
dapat dengan mudah ditulis sehingga merupakan algoritme yang berguna secara universal yang menyalin garis dari satu aliran ke yang lain:Algoritma seperti itu dapat digunakan kembali dalam konteks lain juga.
Anda kemudian dapat meletakkan segala sesuatu yang spesifik untuk kasing yang satu ini ke dalam fungsi yang memanggil algoritma umum ini:
1 Perhatikan bahwa saya menggunakan
'\n'
daripadastd::endl
.'\n'
seharusnya menjadi pilihan default untuk menghasilkan baris baru ,std::endl
adalah kasus aneh .sumber
do_x_and_y()
dengan menyebutkan fungsinyado_everything()
. Ya, itu adalah contoh konyol, tetapi itu menunjukkan bahwa aturan ini gagal bahkan mencegah contoh paling buruk dari desain buruk. IMO ini adalah keputusan perasaan usus sebanyak yang ditentukan oleh konvensi. Kalau tidak, jika itu obyektif, Anda bisa menghasilkan metrik untuk itu - yang Anda tidak bisa.printLine
dll adalah valid - masing-masing adalah abstraksi tunggal - tetapi itu tidak berarti perlu.printFile
sudah "satu hal". Meskipun Anda dapat menguraikannya menjadi tiga abstraksi level bawah yang terpisah, Anda tidak perlu mendekomposisi pada setiap level abstraksi yang memungkinkan . Setiap fungsi harus melakukan "satu hal", tetapi tidak setiap kemungkinan "satu hal" perlu menjadi fungsi. Memindahkan terlalu banyak kerumitan ke grafik panggilan itu sendiri bisa menjadi masalah.Memiliki fungsi hanya melakukan "satu hal" adalah sarana untuk dua tujuan yang diinginkan, bukan perintah dari Tuhan:
Jika fungsi Anda hanya melakukan "satu hal", itu akan membantu Anda menghindari duplikasi kode dan mengasapi API karena Anda akan dapat menyusun fungsi untuk menyelesaikan hal-hal yang lebih rumit daripada melakukan ledakan kombinatorial pada level yang lebih tinggi, fungsi yang lebih sedikit komposisinya .
Memiliki fungsi hanya melakukan "satu hal" dapat membuat kode lebih mudah dibaca. Ini tergantung pada apakah Anda mendapatkan lebih banyak kejelasan dan kemudahan bernalar dengan memisahkan hal-hal daripada Anda kehilangan verbosity, tipuan dan overhead konseptual dari konstruksi yang memungkinkan Anda untuk memisahkan hal-hal.
Karena itu, "satu hal" tidak dapat dihindari subjektif dan tergantung pada tingkat abstraksi apa yang relevan dengan program Anda. Jika
printLines
dianggap sebagai operasi tunggal yang mendasar dan satu-satunya cara mencetak garis yang Anda pedulikan atau perhatikan, maka untuk tujuan AndaprintLines
hanya melakukan satu hal. Kecuali jika Anda menemukan versi kedua lebih mudah dibaca (saya tidak) versi pertama baik-baik saja.Jika Anda mulai membutuhkan kontrol lebih besar atas level abstraksi yang lebih rendah dan berakhir dengan duplikasi halus dan ledakan kombinatorial (yaitu a
printLines
untuk nama file dan yang benar-benar terpisahprintLines
untukfstream
objek,printLines
untuk konsol danprintLines
untuk file) makaprintLines
melakukan lebih dari satu hal di level abstraksi yang Anda sayangi.sumber
Pada skala ini, itu tidak masalah. Implementasi fungsi tunggal sangat jelas dan dapat dimengerti. Namun, menambahkan sedikit lebih banyak kerumitan membuatnya sangat menarik untuk memisahkan iterasi dari tindakan. Misalnya, Anda perlu mencetak baris dari serangkaian file yang ditentukan oleh pola seperti "* .txt". Lalu saya akan memisahkan iterasi dari tindakan:
Sekarang file iterasi dapat diuji secara terpisah.
Saya membagi fungsi untuk menyederhanakan pengujian, atau untuk meningkatkan keterbacaan. Jika tindakan yang dilakukan pada setiap baris data cukup kompleks untuk menjamin komentar, maka saya pasti akan membaginya menjadi fungsi yang terpisah.
sumber
Ekstrak metode ketika Anda merasa perlu komentar untuk menjelaskan sesuatu.
Tulis metode yang hanya melakukan apa yang namanya mereka katakan dengan cara yang jelas, atau menceritakan sebuah kisah dengan memanggil metode yang disebut dengan cerdik.
sumber
Bahkan dalam kasus sederhana Anda, Anda kehilangan detail bahwa Prinsip Tanggung Jawab Tunggal akan membantu Anda mengelola dengan lebih baik. Misalnya, apa yang terjadi ketika ada masalah dengan membuka file. Menambahkan penanganan pengecualian untuk mengeras terhadap kasus tepi akses file akan menambah 7-10 baris kode ke fungsi Anda.
Setelah Anda membuka file, Anda masih tidak aman. Itu bisa ditarik dari Anda (terutama jika itu file di jaringan), Anda bisa kehabisan memori, lagi sejumlah kasus tepi dapat terjadi yang ingin Anda keraskan dan akan menggembungkan fungsi monolitik Anda.
Satu-liner, printline tampaknya cukup berbahaya. Tetapi ketika fungsionalitas baru ditambahkan ke printer file (parsing dan memformat teks, render ke berbagai jenis tampilan, dll.) Itu akan tumbuh dan Anda akan berterima kasih kepada diri sendiri nanti.
Tujuan SRP adalah memungkinkan Anda untuk memikirkan satu tugas pada satu waktu. Ini seperti memecah blok teks yang besar menjadi beberapa paragraf sehingga pembaca dapat memahami poin yang ingin Anda sampaikan. Dibutuhkan sedikit lebih banyak waktu untuk menulis kode yang menganut prinsip-prinsip ini. Tetapi dengan melakukan itu kami membuatnya lebih mudah untuk membaca kode itu. Pikirkan betapa bahagianya diri Anda di masa depan ketika ia harus melacak bug dalam kode dan menemukannya dipartisi dengan rapi.
sumber
Saya pribadi lebih suka pendekatan yang terakhir, karena itu menyelamatkan Anda bekerja di masa depan dan memaksa "bagaimana melakukannya dengan cara yang umum" pola pikir. Meskipun demikian, dalam kasus Anda, Versi 1 lebih baik daripada Versi 2 - hanya karena masalah yang dipecahkan oleh Versi 2 terlalu sepele dan spesifik untuk arus. Saya pikir itu harus dilakukan dengan cara berikut (termasuk perbaikan bug yang diusulkan oleh Nawaz):
Fungsi utilitas umum:
Fungsi khusus domain:
Sekarang
printLines
danprintLine
dapat bekerja tidak hanya denganfstream
, tetapi dengan aliran apa pun.sumber
printLine()
fungsi tidak memiliki nilai. Lihat jawaban saya .Setiap paradigma , (bukan hanya yang Anda kutip) yang harus diikuti memerlukan disiplin, dan dengan demikian - mengurangi "kebebasan berbicara" - menghasilkan overhead awal (setidaknya hanya karena Anda harus mempelajarinya!). Dalam pengertian ini setiap paradigma dapat menjadi berbahaya ketika biaya overhead itu tidak dikompensasikan oleh keuntungan paradigma dirancang untuk tetap dengan dirinya sendiri.
Dengan demikian, jawaban yang benar untuk pertanyaan itu membutuhkan kemampuan yang baik untuk "meramalkan" masa depan, seperti:
A
danB
A-
danB+
(yaitu sesuatu yang terlihat seperti A dan B, tetapi hanya sedikit berbeda)?A*
atauA*-
?Jika probabilitas itu relatif tinggi, itu akan menjadi peluang yang baik jika - sambil memikirkan A dan B - saya juga memikirkan kemungkinan varian mereka, dengan demikian untuk mengisolasi bagian-bagian umum sehingga saya akan dapat menggunakannya kembali.
Jika probabilitas itu sangat rendah (varian apa pun di sekitar
A
pada dasarnya tidak lebih dariA
dirinya sendiri), pelajari cara menguraikan A lebih jauh kemungkinan besar akan menghasilkan waktu yang terbuang.Sebagai contoh, izinkan saya menceritakan kisah nyata ini kepada Anda:
Selama kehidupan masa lalu saya sebagai seorang guru, saya menemukan bahwa - pada sebagian besar proyek siswa - praktis semuanya menyediakan fungsi mereka sendiri untuk menghitung panjang string C .
Setelah beberapa penyelidikan saya menemukan bahwa, sebagai masalah yang sering terjadi, semua siswa datang ke ide untuk menggunakan fungsi untuk itu. Setelah memberi tahu mereka bahwa ada fungsi perpustakaan untuk itu (
strlen
), banyak dari mereka menjawab bahwa karena masalahnya sangat sederhana dan sepele, lebih efektif bagi mereka untuk menulis fungsi mereka sendiri (2 baris kode) daripada mencari buku pedoman C library. (Itu tahun 1984, lupakan WEB dan google!) dengan urutan alfabet yang ketat untuk melihat apakah ada fungsi yang siap untuk itu.Ini adalah contoh di mana paradigma "jangan menemukan kembali roda" dapat menjadi berbahaya, tanpa katalog roda yang efektif!
sumber
Contoh Anda baik-baik saja untuk digunakan dalam alat pembuangan yang diperlukan kemarin untuk melakukan beberapa tugas tertentu. Atau sebagai alat administrasi yang dikendalikan langsung oleh administrator. Sekarang membuatnya kuat agar cocok untuk pelanggan Anda.
Tambahkan penanganan kesalahan / pengecualian yang tepat dengan pesan yang bermakna. Mungkin Anda memerlukan verifikasi parameter, termasuk keputusan yang harus dibuat, misalnya cara menangani file yang tidak ada. Tambahkan fungsionalitas logging, mungkin dengan level berbeda seperti info dan debugging. Tambahkan komentar sehingga kolega tim Anda tahu apa yang terjadi di sana. Tambahkan semua bagian yang biasanya dihilangkan untuk singkatnya dan dibiarkan sebagai latihan untuk pembaca ketika memberikan contoh kode. Jangan lupakan tes unit Anda.
Fungsi kecil Anda yang bagus dan cukup linier tiba-tiba berakhir dalam kekacauan kompleks yang memohon untuk dipisahkan menjadi fungsi terpisah.
sumber
IMO itu menjadi berbahaya ketika sejauh itu bahwa suatu fungsi hampir tidak melakukan apa-apa sama sekali kecuali mendelegasikan pekerjaan ke fungsi lain, karena itu pertanda bahwa itu bukan lagi abstraksi dari apa pun dan pola pikir yang mengarah ke fungsi-fungsi seperti itu selalu dalam bahaya melakukan hal-hal buruk ...
Dari pos asli
Jika Anda cukup bertele-tele, Anda mungkin memperhatikan bahwa printLine masih melakukan dua hal: menulis baris ke cout dan menambahkan karakter "garis akhir". Beberapa orang mungkin ingin mengatasinya dengan membuat fungsi baru:
Oh tidak, sekarang kami telah memperburuk masalahnya! Sekarang bahkan LUAR BIASA bahwa printLine melakukan DUA hal !!! 1! Tidaklah banyak kebodohan untuk menciptakan "cara kerja" yang paling tidak masuk akal yang bisa dibayangkan orang hanya untuk menyingkirkan masalah yang tak terhindarkan itu dengan mencetak sebuah garis yang terdiri dari mencetak garis itu sendiri dan menambahkan karakter end-of-line.
sumber
Jawaban singkat ... itu tergantung.
Pikirkan tentang ini: bagaimana jika, di masa depan, Anda tidak ingin mencetak hanya ke output standar, tetapi ke file.
Saya tahu apa itu YAGNI, tapi saya hanya mengatakan mungkin ada kasus di mana beberapa implementasi diketahui diperlukan, tetapi ditunda. Jadi mungkin arsitek atau apa pun yang tahu fungsi itu harus dapat juga mencetak ke file, tetapi tidak ingin melakukan implementasi sekarang. Jadi dia menciptakan fungsi tambahan ini jadi, di masa depan, Anda hanya perlu mengubah output di satu tempat. Masuk akal?
Namun jika Anda yakin Anda hanya perlu keluaran di konsol, itu tidak masuk akal. Menulis "bungkus"
cout <<
sepertinya tidak berguna.sumber
Seluruh alasan bahwa ada buku-buku yang menyediakan bab untuk kebajikan "melakukan satu hal" adalah bahwa masih ada pengembang di luar sana yang menulis fungsi yang panjangnya 4 halaman dan sarangnya memiliki 6 level. Jika kode Anda sederhana dan jelas, Anda telah melakukannya dengan benar.
sumber
Seperti komentar poster lain, melakukan satu hal adalah masalah skala.
Saya juga menyarankan bahwa ide One Thing adalah menghentikan orang-orang yang mengkode efek samping. Ini dicontohkan oleh penggabungan berurutan di mana metode harus dipanggil dalam urutan tertentu untuk mendapatkan hasil yang 'benar'.
sumber