Apakah fungsi lama dapat diterima jika memiliki struktur internal?

23

Ketika berhadapan dengan algoritma rumit dalam bahasa dengan dukungan untuk fungsi bersarang (seperti Python dan D) saya sering menulis fungsi besar (karena algoritme rumit) tetapi mengurangi ini dengan menggunakan fungsi bersarang untuk menyusun kode rumit. Apakah fungsi besar (100+ garis) masih dianggap jahat meskipun terstruktur dengan baik secara internal melalui penggunaan fungsi bersarang?

Sunting: Bagi Anda yang tidak terbiasa dengan Python atau D, fungsi bersarang dalam bahasa ini juga memungkinkan akses ke lingkup fungsi luar. Di D, akses ini memungkinkan mutasi variabel di lingkup luar. Dalam Python hanya memungkinkan membaca. Dalam D Anda dapat secara eksplisit menonaktifkan akses ke lingkup luar di fungsi bersarang dengan mendeklarasikannya static.

dsimcha
sumber
5
Saya secara teratur menulis 400+ fungsi / metode garis. Harus menempatkan 500 pernyataan saklar kasus di suatu tempat :)
Callum Rogers
7
@Callum Rogers: Dalam Python, alih-alih memiliki 500 pernyataan beralih kasus, Anda akan menggunakan kamus pencarian / pemetaan. Saya percaya mereka lebih unggul daripada memiliki pernyataan switch-case 500 atau lebih. Anda masih memerlukan 500 baris definisi kamus (sebagai alternatif, dengan Python, Anda dapat menggunakan refleksi untuk menyebutkannya secara dinamis), tetapi definisi kamus adalah data, bukan kode; dan ada jauh lebih sedikit keraguan tentang memiliki definisi data yang besar daripada definisi fungsi yang besar. Selain itu, data lebih kuat daripada kode.
Lie Ryan
Saya merasa ngeri setiap kali saya melihat fungsi dengan banyak LOC, membuat saya bertanya-tanya apa tujuan dari modularitas. Lebih mudah untuk mengikuti logika dan men-debug dengan fungsi yang lebih kecil.
Aditya P
Bahasa Pascal kuno dan akrab juga memungkinkan akses ke lingkup luar, dan begitu pula ekstensi fungsi bersarang di GNU C. (Bahasa-bahasa ini hanya memungkinkan funargs ke bawah saja, namun: yaitu, argumen yang merupakan fungsi yang membawa lingkup, hanya dapat diturunkan, dan tidak dikembalikan, yang akan memerlukan dukungan penutupan leksikal penuh).
Kaz
Contoh fungsi yang cenderung panjang adalah kombinator yang Anda tulis ketika melakukan pemrograman reaktif. Mereka dengan mudah mencapai tiga puluh garis, tetapi akan berlipat ganda jika terpecah karena Anda kehilangan penutupan.
Craig Gidney

Jawaban:

21

Selalu ingat aturannya, sebuah fungsi melakukan satu hal dan melakukannya dengan baik! Jika Anda bisa melakukannya, hindari fungsi bersarang.

Ini menghambat keterbacaan dan pengujian.

Bryan Harrington
sumber
9
Saya selalu membenci aturan ini karena fungsi yang melakukan "satu hal" ketika dilihat pada tingkat abstraksi yang tinggi dapat melakukan "banyak hal" ketika dilihat pada tingkat yang lebih rendah. Jika diterapkan secara tidak benar, aturan "satu hal" dapat menyebabkan API berbutir halus.
dsimcha
1
@dsimcha: Aturan apa yang tidak bisa diterapkan dan menyebabkan masalah? Ketika seseorang menerapkan "aturan" / kata-kata hampa melewati titik di mana mereka berguna, hanya mengeluarkan yang lain: semua generalisasi adalah salah.
2
@Roger: Keluhan yang sah, kecuali bahwa IMHO aturannya sering kali salah diterapkan dalam praktik untuk membenarkan API yang terlalu halus dan direkayasa ulang. Ini memiliki biaya konkret karena API menjadi lebih sulit dan lebih bertele-tele untuk digunakan untuk kasus-kasus penggunaan umum yang sederhana.
dsimcha
2
"Ya, aturannya salah diterapkan, tetapi aturan itu terlalu banyak diterapkan!"? : P
1
Fungsi saya melakukan satu hal dan melakukan hal itu dengan sangat baik: yaitu, menempati 27 layar penuh. :)
Kaz
12

Beberapa berpendapat bahwa fungsi pendek bisa lebih rentan kesalahan daripada fungsi panjang .

Card and Glass (1990) menunjukkan bahwa kompleksitas desain benar-benar melibatkan dua aspek: kompleksitas dalam setiap komponen dan kompleksitas hubungan antar komponen.

Secara pribadi, saya telah menemukan bahwa kode garis lurus yang dikomentari dengan baik lebih mudah untuk diikuti (terutama ketika Anda bukan orang yang awalnya menulisnya) daripada ketika itu dipecah menjadi beberapa fungsi yang tidak pernah digunakan di tempat lain. Tetapi itu benar-benar tergantung pada situasinya.

Saya pikir kesimpulan utama adalah ketika Anda membagi satu blok kode, Anda memperdagangkan satu jenis kompleksitas untuk yang lain. Mungkin ada titik manis di suatu tempat di tengah.

Jonathan Tran
sumber
Sudahkah Anda membaca "Kode Bersih"? Ini membahas panjang lebar ini. amazon.com/Clean-Code-Handbook-Software-Craftsmanship/dp/…
Martin Wickman
9

Idealnya, seluruh fungsi harus dapat dilihat tanpa harus menggulir. Terkadang, ini tidak mungkin. Tetapi jika Anda dapat memecahnya menjadi beberapa bagian maka itu akan membuat membaca kode jauh lebih mudah.

Saya tahu bahwa segera setelah saya mendorong Page Up / Down atau pindah ke bagian kode yang berbeda, saya hanya dapat mengingat 7 +/- 2 hal dari halaman sebelumnya. Dan sayangnya, beberapa lokasi tersebut akan digunakan ketika membaca kode baru.

Saya selalu suka memikirkan memori jangka pendek saya seperti register komputer (CISC, bukan RISC). Jika Anda memiliki seluruh fungsi pada halaman yang sama, Anda dapat pergi ke cache untuk mendapatkan informasi yang diperlukan dari bagian lain dari program. Jika seluruh fungsi tidak dapat ditampung pada halaman, itu akan menjadi setara dengan selalu mendorong memori ke disk setelah setiap operasi.

jsternberg
sumber
3
Anda hanya perlu mencetaknya pada printer matriks - lihat, 10 meter kode sekaligus :)
Atau dapatkan monitor dengan resolusi lebih tinggi
frogstarr78
Dan menggunakannya dalam orientasi vertikal.
Calmarius
4

Mengapa menggunakan fungsi bersarang, daripada fungsi eksternal normal?

Sekalipun fungsi eksternal hanya pernah digunakan dalam fungsi Anda, yang sebelumnya-besar, masih membuat keseluruhan kekacauan lebih mudah dibaca:

DoThing(int x){
    x += ;1
    int y = FirstThing(x);
    x = SecondThing(x, y);
    while(spoo > fleem){
        x = ThirdThing(x+y);
    }
}

FirstThing(int x){...}
SecondThing(int x, int y){...}
ThirdThing(int z){...}
Fishtoaster
sumber
2
Dua kemungkinan alasan: mengacaukan namespace, dan apa yang saya sebut "kedekatan leksikal". Ini mungkin alasan yang baik atau buruk, tergantung pada bahasa Anda.
Frank Shearar
Tetapi fungsi bersarang bisa menjadi kurang efisien karena mereka perlu mendukung funargs ke bawah, dan kemungkinan penutupan leksikal penuh. Jika bahasa dioptimalkan dengan buruk, mungkin diperlukan kerja ekstra untuk menciptakan lingkungan yang dinamis untuk diteruskan ke fungsi anak.
Kaz
3

Saya tidak memiliki buku di depan saya saat ini (untuk mengutip), tetapi menurut Kode Lengkap "sweetspot" untuk panjang fungsi sekitar 25-50 baris kode menurut penelitiannya.

Ada saatnya ok memiliki fungsi yang panjang:

  • Ketika kompleksitas cyclomatic dari fungsi rendah. Rekan pengembang Anda mungkin sedikit frustrasi jika mereka harus melihat fungsi yang berisi pernyataan if raksasa dan pernyataan lain untuk itu jika tidak ada di layar secara bersamaan.

Saat tidak memiliki fungsi panjang:

  • Anda memiliki fungsi dengan kondisional bersarang mendalam. Bantulah sesama pembaca kode Anda, tingkatkan keterbacaan dengan memecah fungsinya. Sebuah fungsi memberikan indikasi kepada pembaca bahwa "Ini adalah blok kode yang melakukan satu hal". Juga tanyakan pada diri Anda apakah panjang fungsi menunjukkan bahwa ia melakukan terlalu banyak dan perlu difaktorkan ke kelas lain.

Intinya adalah bahwa pemeliharaan harus menjadi salah satu prioritas tertinggi dalam daftar Anda. Jika pengembang lain tidak dapat melihat kode Anda dan mendapatkan "inti" dari apa yang dilakukan kode dalam waktu kurang dari 5 detik, kode Anda tidak memberikan cukup "metadata" untuk mengetahui apa yang dilakukannya. Pengembang lain harus dapat mengetahui apa yang dikerjakan oleh kelas Anda hanya dengan melihat browser objek di IDE yang Anda pilih alih-alih membaca 100 baris kode.

Fungsi yang lebih kecil memiliki keunggulan sebagai berikut:

  • Portabilitas: Jauh lebih mudah untuk memindahkan fungsionalitas (baik di dalam kelas saat refactoring ke yang lain)
  • Debugging: Ketika Anda melihat stacktrace itu jauh lebih cepat untuk menentukan kesalahan jika Anda melihat suatu fungsi dengan 25 baris kode daripada 100.
  • Keterbacaan - Nama fungsi memberi tahu apa yang dilakukan oleh seluruh blok kode. Seorang dev di tim Anda mungkin tidak ingin membaca blok itu jika mereka tidak bekerja dengannya. Plus, di sebagian besar IDE modern, pengembang lain dapat memiliki pemahaman yang lebih baik tentang apa yang dikerjakan oleh kelas Anda dengan membaca nama-nama fungsi di browser objek.
  • Navigasi - Sebagian besar IDE akan memungkinkan Anda mencari nama fungsi. Juga, sebagian besar IDE modern memiliki kemampuan untuk melihat sumber fungsi di jendela lain, ini memberikan pengembang lain untuk melihat fungsi panjang Anda pada 2 layar (jika multi-monitor) alih-alih membuatnya gulir.

Daftar ini berlanjut .....

Korbin
sumber
2

Jawabannya tergantung, namun Anda mungkin harus mengubahnya menjadi kelas.

Lie Ryan
sumber
Kecuali Anda tidak menulis dalam OOPL, dalam hal ini mungkin tidak :)
Frank Shearar
dsimcha menyebutkan mereka menggunakan D dan Python.
frogstarr78
Kelas terlalu sering menjadi penopang bagi orang yang belum pernah mendengar hal-hal seperti penutupan leksikal.
Kaz
2

Saya tidak suka sebagian besar fungsi bersarang. Lambdas termasuk dalam kategori itu tetapi biasanya tidak menandai saya kecuali mereka memiliki lebih dari 30-40 karakter.

Alasan dasarnya adalah bahwa itu menjadi fungsi yang sangat lokal dengan rekursi semantik internal, yang berarti bahwa sulit bagi saya untuk membungkus otak saya, dan hanya lebih mudah untuk mendorong beberapa hal keluar ke fungsi pembantu yang tidak mengacaukan ruang kode .

Saya menganggap bahwa fungsi harus Melakukan Hal Ini. Melakukan Hal Lain adalah fungsi yang dilakukan fungsi lain. Jadi jika Anda memiliki fungsi 200-line Melakukan Hal, dan semuanya mengalir, itu A-OK.

Paul Nathan
sumber
Kadang-kadang Anda harus melewati begitu banyak konteks ke fungsi eksternal (yang bisa saja memiliki akses mudah ke fungsi lokal) sehingga ada lebih banyak kerumitan dalam bagaimana fungsi dipanggil daripada apa yang dilakukannya.
Kaz
1

Apakah ini dapat diterima? Itu benar-benar pertanyaan yang hanya bisa Anda jawab. Apakah fungsi tersebut mencapai apa yang diperlukan? Apakah bisa dirawat? Apakah 'dapat diterima' oleh anggota tim Anda yang lain? Jika demikian, maka itulah yang benar-benar penting.

Sunting: Saya tidak melihat hal tentang fungsi bersarang. Secara pribadi, saya tidak akan menggunakannya. Saya akan menggunakan fungsi reguler sebagai gantinya.

GrandmasterB
sumber
1

Fungsi "garis-lurus" yang panjang bisa menjadi cara yang jelas untuk menentukan urutan panjang langkah-langkah yang selalu terjadi dalam urutan tertentu.

Namun, seperti yang telah disebutkan orang lain, formulir ini cenderung memiliki kompleksitas lokal di mana aliran keseluruhan kurang jelas. Menempatkan kompleksitas lokal itu ke fungsi bersarang (yaitu: didefinisikan di tempat lain dalam fungsi panjang, mungkin di atas atau bawah) dapat mengembalikan kejelasan ke aliran arus utama.

Pertimbangan penting kedua adalah mengendalikan ruang lingkup variabel yang dimaksudkan untuk digunakan hanya dalam rentang lokal dari fungsi yang panjang. Diperlukan kewaspadaan untuk menghindari variabel yang diperkenalkan dalam satu bagian kode tanpa disadari dirujuk di tempat lain (misalnya, setelah siklus pengeditan), karena kesalahan semacam ini tidak akan muncul sebagai kesalahan kompilasi atau runtime.

Dalam beberapa bahasa, masalah ini mudah dihindari: rangkaian kode lokal dapat dibungkus dengan bloknya sendiri, seperti dengan "{...}", di mana variabel yang baru diperkenalkan hanya dapat dilihat oleh blok itu. Beberapa bahasa, seperti Python, kekurangan fitur ini, dalam hal ini fungsi lokal dapat berguna untuk menegakkan wilayah lingkup yang lebih kecil.

gwideman
sumber
1

Tidak, fungsi multi-halaman tidak diinginkan, dan tidak boleh melewati tinjauan kode. Saya dulu menulis fungsi panjang juga, tetapi setelah membaca Refactoring Martin Fowler , saya berhenti. Fungsi panjang sulit untuk ditulis dengan benar, sulit dimengerti dan sulit untuk diuji. Saya belum pernah melihat fungsi bahkan 50 baris yang tidak akan lebih mudah dipahami dan diuji jika dipecah menjadi satu set fungsi yang lebih kecil. Dalam fungsi multi-halaman hampir pasti seluruh kelas yang harus diperhitungkan. Sulit untuk lebih spesifik. Mungkin Anda harus memposting salah satu fungsi panjang Anda ke Code Review dan seseorang (mungkin saya) dapat menunjukkan kepada Anda bagaimana memperbaikinya.

kevin cline
sumber
0

Ketika saya sedang memprogram dalam python, saya ingin mundur setelah saya menulis sebuah fungsi dan bertanya pada diri sendiri apakah itu mematuhi "Zen of Python" (ketik 'import this' dalam interpreter python Anda):

Cantik lebih baik daripada jelek.
Eksplisit lebih baik daripada implisit.
Sederhana lebih baik daripada kompleks.
Kompleks lebih baik daripada rumit.
Flat lebih baik daripada bersarang.
Jarang lebih baik daripada padat.
Jumlah keterbacaan diperhitungkan.
Kasus khusus tidak cukup istimewa untuk melanggar aturan.
Meskipun kepraktisan mengalahkan kemurnian.
Kesalahan tidak boleh terjadi secara diam-diam.
Kecuali secara eksplisit dibungkam.
Dalam menghadapi ambiguitas, tolak godaan untuk menebak.
Harus ada satu - dan lebih disukai hanya satu - cara yang jelas untuk melakukannya.
Meskipun demikian mungkin tidak jelas pada awalnya kecuali Anda orang Belanda.
Sekarang lebih baik daripada tidak sama sekali.
Meskipun tidak pernah sering lebih baik daripada benarsekarang.
Jika implementasi sulit dijelaskan, itu ide yang buruk.
Jika implementasinya mudah dijelaskan, mungkin itu ide yang bagus.
Namespaces adalah salah satu ide bagus - mari kita lakukan lebih dari itu!


sumber
1
Jika eksplisit lebih baik daripada implisit, kita harus kode dalam bahasa mesin. Bahkan di assembler; ia melakukan hal-hal tersirat jahat seperti secara otomatis mengisi slot penundaan cabang dengan instruksi, menghitung offset cabang relatif, dan menyelesaikan referensi simbolis di antara global. Sobat, para pengguna Python bodoh ini dan agama kecil mereka ...
Kaz
0

Tempatkan mereka di modul terpisah.

Dengan asumsi solusi Anda tidak lebih besar daripada membutuhkan Anda tidak punya terlalu banyak pilihan. Anda telah membagi fungsi dalam berbagai sub fungsi sehingga pertanyaannya adalah di mana Anda harus meletakkannya:

  1. Tempatkan mereka dalam modul dengan hanya satu fungsi "publik".
  2. Tempatkan mereka di kelas dengan hanya satu fungsi "publik" (statis).
  3. Tempatkan mereka dalam suatu fungsi (seperti yang telah Anda jelaskan).

Sekarang baik alternatif kedua dan ketiga dalam beberapa hal bersarang, tetapi alternatif kedua oleh beberapa programmer tampaknya tidak buruk. Jika Anda tidak mengesampingkan alternatif kedua, saya tidak melihat terlalu banyak alasan untuk mengesampingkan alternatif ketiga.

meroket
sumber