Fungsi yang hanya memanggil fungsi lainnya. Apakah ini praktik yang baik?

13

Saat ini saya sedang mengerjakan serangkaian laporan yang memiliki banyak bagian berbeda (semua memerlukan pemformatan berbeda), dan saya mencoba mencari cara terbaik untuk menyusun kode saya. Laporan serupa yang kami lakukan di masa lalu memiliki fungsi yang sangat besar (200+ baris) yang melakukan semua manipulasi dan pemformatan data untuk laporan, sehingga alur kerjanya terlihat seperti ini:

DataTable reportTable = new DataTable();
void RunReport()
{
    reportTable = DataClass.getReportData();
    largeReportProcessingFunction();
    outputReportToUser();
}

Saya ingin dapat memecah fungsi-fungsi besar ini menjadi potongan-potongan yang lebih kecil, tetapi saya takut bahwa pada akhirnya saya akan memiliki puluhan fungsi yang tidak dapat digunakan kembali, dan fungsi "do everything here" yang serupa yang tugasnya hanya untuk panggil semua fungsi yang lebih kecil ini, seperti:

void largeReportProcessingFunction()
{
    processSection1HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    processSection1SummaryTableData();
    calculateSection1SummaryTableTotalRow();
    formatSection1SummaryTableDisplay();
    processSection1FooterData();
    getSection1FooterSummaryTotals();
    formatSection1FooterDisplay();

    processSection2HeaderData();
    calculateSection1HeaderAverages();
    formatSection1HeaderDisplay();
    calculateSection1HeaderAverages();
    ...
}

Atau, jika kita melangkah lebih jauh:

void largeReportProcessingFunction()
{
    callAllSection1Functions();

    callAllSection2Functions();

    callAllSection3Functions();
    ...        
}

Apakah ini benar-benar solusi yang lebih baik? Dari sudut pandang organisasi saya kira itu (yaitu semuanya jauh lebih terorganisir daripada yang seharusnya), tetapi sejauh pembacaan kode saya tidak yakin (rantai fungsi yang berpotensi besar yang hanya memanggil fungsi lain).

Pikiran?

Eric C.
sumber
Pada akhirnya, apakah lebih mudah dibaca? Ya, jadi ini solusi yang lebih baik.
sylvanaar

Jawaban:

18

Ini biasanya disebut sebagai dekomposisi fungsional dan umumnya merupakan hal yang baik untuk dilakukan, jika dilakukan dengan benar.

Juga, implementasi suatu fungsi harus dalam satu level abstraksi. Jika Anda mengambil largeReportProcessingFunction, maka perannya adalah untuk menentukan langkah pemrosesan yang berbeda yang harus diambil dalam urutan yang mana. Implementasi dari masing-masing langkah tersebut adalah pada lapisan abstraksi di bawah ini dan largeReportProcessingFunctiontidak harus bergantung padanya secara langsung.

Harap perhatikan bahwa ini adalah pilihan penamaan yang buruk:

void largeReportProcessingFunction() {
    callAllSection1Functions();
    callAllSection2Functions();
    callAllSection3Functions();
    ...        
}

Anda lihat callAllSection1Functionsadalah nama yang tidak benar-benar memberikan abstraksi, karena itu tidak benar-benar mengatakan apa yang dilakukannya, melainkan bagaimana melakukannya. Itu harus disebut processSection1sebaliknya atau apa pun yang sebenarnya bermakna dalam konteks.

back2dos
sumber
6
Saya setuju pada prinsipnya dengan jawaban ini, kecuali saya tidak bisa melihat bagaimana "processSection1" adalah nama yang bermakna. Ini praktis setara dengan "callAllSection1Functions".
Eric King
2
@EricKing: callAllSection1Functionsdetail kebocoran tentang implementasi. Dari perspektif luar, tidak masalah apakah processSection1menolak pekerjaannya untuk "semua fungsi section1" atau apakah itu melakukannya secara langsung atau apakah itu menggunakan debu peri untuk memanggil unicorn yang akan melakukan pekerjaan yang sebenarnya. Yang penting adalah, bahwa setelah panggilan ke processSection1, bagian1 dapat dianggap diproses . Saya setuju bahwa pemrosesan adalah nama generik, tetapi jika Anda memiliki kohesi yang tinggi (yang harus selalu Anda perjuangkan) maka konteks Anda biasanya cukup sempit untuk membuatnya ambigu.
back2dos
2
Yang saya maksudkan adalah, metode yang disebut "processXXX" hampir selalu mengerikan, nama yang tidak berguna. Semua metode 'memproses' hal-hal, jadi menamainya 'processXXX' sama bermanfaatnya dengan memberi nama 'doSomethingWithXXX' atau 'memanipulasiXXX' dan sejenisnya. Hampir selalu ada nama yang lebih baik untuk metode yang bernama 'processXXX'. Dari sudut pandang praktis, ini berguna (tidak berguna) seperti 'callAllSection1Functions'.
Eric King
4

Anda bilang Anda menggunakan C #, jadi saya ingin tahu apakah Anda bisa membangun hierarki kelas terstruktur mengambil keuntungan dari fakta bahwa Anda menggunakan bahasa berorientasi objek? Misalnya, jika masuk akal untuk memecah laporan menjadi beberapa bagian, memiliki Sectionkelas dan memperluasnya untuk persyaratan spesifik klien.

Mungkin masuk akal untuk memikirkannya seolah-olah Anda sedang membuat GUI non-interaktif. Pikirkan tentang objek yang mungkin Anda buat seolah-olah mereka komponen GUI yang berbeda. Tanyakan pada diri Anda seperti apa laporan dasar itu? Bagaimana dengan yang rumit? Di mana titik ekstensi seharusnya?

Jeremy Heiler
sumber
3

Tergantung. Jika semua fungsi yang Anda buat benar-benar satu unit kerja maka tidak apa-apa, jika mereka hanya baris 1-15, 16-30, 31-45 dari fungsi panggilan mereka yang buruk. Fungsi panjang bukan kejahatan, jika mereka melakukan satu hal, jika Anda memiliki 20 filter untuk laporan Anda yang memiliki fungsi validasi yang memeriksa semua dua puluh akan lama. Tidak apa-apa, memecahnya dengan filter atau grup filter hanya menambah kompleksitas tambahan tanpa manfaat nyata.

Memiliki banyak fungsi pribadi juga membuat pengujian unit otomatis menjadi lebih sulit, sehingga beberapa akan mencoba menghindarinya karena alasan itu, tetapi menurut saya ini adalah alasan yang buruk karena cenderung membuat desain yang lebih besar dan lebih rumit untuk mengakomodasi pengujian.

Ryathal
sumber
3
Dalam pengalaman saya, fungsi panjang itu jahat.
Bernard
Jika contoh Anda dalam bahasa OO, saya akan membuat kelas filter dasar dan masing-masing dari 20 filter akan berada dalam kelas turunan yang berbeda. Ganti pernyataan akan diganti dengan panggilan penciptaan untuk mendapatkan filter yang tepat. Setiap fungsi melakukan satu hal dan semuanya kecil.
DXM
3

Karena Anda menggunakan C #, coba dan pikirkan lebih dalam objek. Sepertinya Anda masih mengkode secara prosedural dengan memiliki variabel kelas "global" yang bergantung pada fungsi selanjutnya.

Melepaskan logika Anda dalam fungsi-fungsi yang terpisah jelas lebih baik daripada memiliki semua logika dalam satu fungsi. Saya berani bertaruh Anda bisa melangkah lebih jauh dengan memisahkan data yang berfungsi berfungsi dengan memecah ini dalam beberapa objek.

Pertimbangkan untuk memiliki kelas ReportBuilder tempat Anda dapat menyampaikan data laporan. Jika Anda dapat membagi ReportBuilder ke dalam kelas yang terpisah, lakukan ini juga.

pembuat kode
sumber
2
Prosedur melayani fungsi yang sangat baik.
DeadMG
1

Iya.

Jika Anda memiliki segmen kode yang perlu digunakan di beberapa tempat, fungsi sendiri. Kalau tidak, jika Anda harus membuat perubahan pada fungsionalitas Anda perlu membuat perubahan di banyak tempat. Ini tidak hanya meningkatkan pekerjaan tetapi meningkatkan risiko bug muncul ketika kode diubah di satu tempat tetapi tidak di tempat lain tetapi di mana tetapi diperkenalkan di satu tempat tetapi tidak di tempat lain.

Ini juga membantu membuat metode lebih mudah dibaca jika nama metode deskriptif digunakan.

SoylentGray
sumber
1

Fakta bahwa Anda menggunakan penomoran untuk membedakan fungsi menunjukkan bahwa ada masalah mendasar dengan duplikasi atau kelas melakukan terlalu banyak. Memecah fungsi besar menjadi banyak fungsi yang lebih kecil bisa menjadi langkah yang berguna dalam refactoring keluar duplikasi, tetapi seharusnya tidak menjadi yang terakhir. Misalnya, Anda membuat dua fungsi:

processSection1HeaderData();
processSection2HeaderData();

Apakah tidak ada kesamaan dalam kode yang digunakan untuk memproses tajuk bagian? Bisakah Anda mengekstrak fungsi umum untuk keduanya dengan parameterisasi perbedaan?

Garrett Hall
sumber
Ini sebenarnya bukan kode produksi, jadi nama-nama fungsi hanyalah contoh (dalam produksi nama-nama fungsi lebih deskriptif). Secara umum, tidak, tidak ada kesamaan antara bagian yang berbeda (meskipun ketika ada kesamaan, saya mencoba menggunakan kembali kode sebanyak mungkin).
Eric C.
1

Memecah fungsi menjadi subfungsi logis sangat membantu, bahkan jika subfungsi tidak digunakan kembali - memecahnya menjadi blok pendek, mudah dipahami. Tapi itu harus dilakukan oleh unit logis, bukan hanya n # baris. Bagaimana Anda harus memecahnya akan tergantung pada kode Anda, tema umum adalah loop, kondisi, operasi pada objek yang sama; pada dasarnya segala sesuatu yang dapat Anda gambarkan sebagai tugas tersendiri.

Jadi, ya, ini gaya yang bagus - ini mungkin terdengar aneh, tetapi mengingat penggambaran Anda yang terbatas, saya akan merekomendasikan agar Anda memikirkan dengan hati-hati tentang penggunaan kembali yang PERLUASAN. Pastikan penggunaannya benar-benar identik, dan bukan hanya kebetulan yang sama. Mencoba untuk menangani semua kasus di blok yang sama sering kali Anda berakhir dengan fungsi canggung yang besar di tempat pertama.

jmoreno
sumber
0

Saya pikir ini sebagian besar pilihan pribadi. Jika fungsi Anda akan digunakan kembali (dan apakah Anda yakin Anda tidak bisa membuatnya lebih generik dan dapat digunakan kembali?) Saya pasti akan mengatakan membagi mereka. Saya juga mendengar bahwa ini adalah prinsip yang baik bahwa tidak ada fungsi atau metode yang harus memiliki lebih dari 2-3 layar kode di dalamnya. Jadi, jika fungsi besar Anda terus berjalan, itu mungkin membuat kode Anda lebih mudah dibaca untuk dibagi.

Anda tidak menyebutkan teknologi apa yang Anda gunakan untuk mengembangkan laporan ini. Sepertinya Anda mungkin menggunakan C #. Apakah itu benar? Jika demikian, Anda mungkin juga mempertimbangkan arahan #region sebagai alternatif jika Anda tidak ingin membagi fungsi Anda menjadi yang lebih kecil.

Joshua Carmody
sumber
Ya, saya bekerja di C #. Ada kemungkinan bahwa saya akan dapat menggunakan kembali beberapa fungsi, tetapi untuk banyak dari laporan ini bagian yang berbeda memiliki data dan tata letak yang sangat berbeda (kebutuhan pelanggan).
Eric C.
1
+1 kecuali untuk wilayah yang disarankan. Saya tidak akan menggunakan daerah.
Bernard
@ Brian - Ada alasan khusus? Saya berasumsi si penanya hanya akan mempertimbangkan menggunakan #regions jika dia sudah mengesampingkan pemisahan fungsi.
Joshua Carmody
2
Wilayah cenderung menyembunyikan kode dan dapat disalahgunakan (yaitu wilayah bersarang). Saya suka melihat semua kode sekaligus dalam file kode. Jika saya perlu memisahkan kode secara visual dalam file saya menggunakan garis garis miring.
Bernard
2
Daerah biasanya merupakan pertanda bahwa kode ini perlu refactoring
coder