Haruskah saya mengekstrak fungsionalitas spesifik ke dalam fungsi dan mengapa?

29

Saya memiliki metode besar yang melakukan 3 tugas, masing-masing dapat diekstraksi menjadi fungsi yang terpisah. Jika saya akan membuat fungsi tambahan untuk masing-masing tugas itu, apakah ini akan membuat kode saya lebih baik atau lebih buruk dan mengapa?

Jelas, itu akan membuat lebih sedikit baris kode di fungsi utama, tetapi akan ada deklarasi fungsi tambahan, jadi kelas saya akan memiliki metode tambahan, yang saya percaya tidak baik, karena akan membuat kelas lebih kompleks.

Haruskah saya melakukan itu sebelum saya menulis semua kode atau saya harus meninggalkannya sampai semuanya selesai dan kemudian mengekstrak fungsi?

dhblah
sumber
19
"Saya meninggalkannya sampai semuanya selesai" biasanya identik dengan "Itu tidak akan pernah dilakukan".
Euforia
2
Itu umumnya benar, tetapi juga ingat prinsip kebalikan dari YAGNI (yang tidak berlaku dalam kasus ini, karena Anda sudah membutuhkannya).
jhocking
Hanya ingin menekankan jangan terlalu fokus pada pengurangan baris kode. Alih-alih mencoba untuk berpikir dalam hal abstraksi. Setiap fungsi seharusnya hanya memiliki satu pekerjaan. Jika Anda menemukan bahwa fungsi-fungsi Anda melakukan lebih dari satu pekerjaan, maka umumnya Anda harus memperbaiki metode ini. Jika Anda mengikuti panduan ini, hampir tidak mungkin memiliki fungsi yang terlalu panjang.
Adrian

Jawaban:

35

Ini adalah buku yang sering saya tautkan, tetapi saya membahasnya lagi: Robert C. Martin Clean Code , bab 3, "Functions".

Jelas, itu akan membuat lebih sedikit baris kode di fungsi utama, tetapi akan ada deklarasi fungsi tambahan, jadi kelas saya akan memiliki metode tambahan, yang saya percaya tidak baik, karena akan membuat kelas lebih kompleks.

Apakah Anda lebih suka membaca fungsi dengan +150 baris, atau fungsi memanggil 3 +50 fungsi baris? Saya pikir saya lebih suka opsi kedua.

Ya , itu akan membuat kode Anda lebih baik dalam arti bahwa itu akan lebih "dapat dibaca". Buat fungsi yang melakukan satu dan hanya satu hal, mereka akan lebih mudah untuk mempertahankan dan menghasilkan test case.

Juga, hal yang sangat penting yang saya pelajari dengan buku tersebut di atas: pilih nama yang baik dan tepat untuk fungsi Anda. Semakin penting fungsinya, nama yang paling tepat seharusnya. Jangan khawatir tentang panjang nama, jika harus dipanggil FunctionThatDoesThisOneParticularThingOnly, maka nama itu seperti itu.

Sebelum melakukan refactor Anda, tulis satu atau beberapa test case. Pastikan mereka bekerja. Setelah selesai dengan refactoring, Anda akan dapat meluncurkan kotak uji ini untuk memastikan kode baru berfungsi dengan benar. Anda dapat menulis tes "kecil" tambahan untuk memastikan fungsi baru Anda bekerja dengan baik secara terpisah.

Akhirnya, dan ini tidak bertentangan dengan apa yang baru saja saya tulis, tanyakan pada diri Anda apakah Anda benar-benar perlu melakukan refactoring ini, periksa jawaban untuk " Kapan harus refactor ?" (juga, cari pertanyaan SO pada "refactoring", ada lebih banyak dan jawaban menarik untuk dibaca)

Haruskah saya melakukan itu sebelum saya menulis semua kode atau saya harus meninggalkannya sampai semuanya selesai dan kemudian mengekstrak fungsi?

Jika kode sudah ada di sana dan berfungsi dan Anda kekurangan waktu untuk rilis berikutnya, jangan sentuh itu. Kalau tidak, saya pikir orang harus membuat fungsi kecil bila memungkinkan dan dengan demikian, refactor setiap kali ada waktu sambil memastikan bahwa semuanya berfungsi seperti sebelumnya (uji kasus).

Jalayn
sumber
10
Sebenarnya, Bob Martin telah menunjukkan beberapa kali bahwa ia lebih suka 7 fungsi dengan 2 hingga 3 baris daripada satu fungsi dengan 15 baris (lihat di sini sites.google.com/site/unclebobconsultingllc/… ). Dan di situlah banyak dev bahkan yang berpengalaman akan menolak. Secara pribadi, saya pikir banyak "pengembang berpengalaman" hanya mengalami kesulitan untuk menerima bahwa mereka masih dapat meningkatkan hal mendasar seperti membangun abstraksi dengan fungsi setelah> 10 tahun pengkodean.
Doc Brown
+1 hanya untuk referensi buku yang, menurut pendapat saya yang sederhana, harus ada di rak perusahaan perangkat lunak mana pun.
Fabio Marcolini
3
Saya mungkin parafrase di sini, tetapi frasa dari buku itu yang menggema di kepala saya hampir setiap hari adalah "setiap fungsi harus melakukan satu hal saja, dan melakukannya dengan baik". Tampaknya sangat relevan di sini karena OP mengatakan "fungsi utama saya melakukan tiga hal"
wakjah
Anda benar sekali!
Jalayn
Tergantung seberapa banyak tiga fungsi yang terpisah saling terkait. Mungkin lebih mudah untuk mengikuti blok kode yang semuanya ada di satu tempat daripada tiga blok kode yang berulang kali saling mengandalkan.
user253751
13

Ya tentu saja. Jika mudah untuk melihat dan memisahkan "tugas" yang berbeda dari fungsi tunggal.

  1. Keterbacaan - Fungsi dengan nama baik membuatnya eksplisit apa kode tidak tanpa perlu membaca kode itu.
  2. Dapat digunakan kembali - Lebih mudah menggunakan fungsi yang melakukan satu hal di banyak tempat, daripada memiliki fungsi yang melakukan hal-hal yang tidak Anda butuhkan.
  3. Testability - Lebih mudah untuk menguji fungsi, yang memiliki satu "fungsi" yang didefinisikan, yang memiliki banyak dari mereka

Tetapi mungkin ada masalah dengan ini:

  • Tidak mudah melihat cara memisahkan fungsi. Ini mungkin memerlukan refactoring bagian dalam fungsi terlebih dahulu, sebelum Anda beralih ke pemisahan.
  • Fungsi ini memiliki keadaan internal yang sangat besar, yang diedarkan. Ini biasanya memerlukan semacam solusi OOP.
  • Sulit untuk mengatakan fungsi apa yang harus dilakukan. Unit mengujinya dan refactor sampai Anda tahu.
Euforia
sumber
5

Masalah yang Anda ajukan bukanlah masalah pengkodean, konvensi atau praktik pengkodean, melainkan masalah keterbacaan dan cara editor teks menunjukkan kode yang Anda tulis. Masalah yang sama juga muncul di pos:

Apakah boleh membagi fungsi dan metode yang panjang menjadi yang lebih kecil meskipun tidak akan dipanggil oleh yang lain?

Membagi fungsi menjadi sub-fungsi masuk akal ketika menerapkan sistem besar dengan maksud untuk merangkum berbagai fungsi yang akan dikomposisikan. Namun, cepat atau lambat, Anda akan menemukan diri Anda dengan sejumlah fungsi besar. Beberapa dari mereka tidak dapat dihidupkan kembali dan sulit dipertahankan apakah Anda menyimpannya sebagai fungsi tunggal yang panjang atau membaginya menjadi fungsi yang lebih kecil. Ini terutama berlaku untuk fungsi-fungsi di mana operasi yang Anda lakukan, tidak diperlukan di tempat lain di sistem Anda. Memungkinkan mengambil salah satu dari fungsi yang begitu panjang dan mempertimbangkannya dalam tampilan yang lebih luas.

Pro:

  • Setelah Anda membacanya, Anda memiliki ide lengkap tentang semua operasi fungsi (Anda dapat membacanya sebagai buku);
  • Jika Anda ingin men-debug itu, Anda dapat menjalankannya langkah demi langkah tanpa melompat ke file / bagian lain dari file;
  • Anda memiliki kebebasan untuk mengakses / menggunakan variabel apa pun yang dinyatakan pada setiap tahap fungsi;
  • Algoritma fungsi mengimplementasikan sepenuhnya terkandung dalam fungsi (dienkapsulasi);

Kontra:

  • Dibutuhkan banyak halaman layar Anda;
  • Butuh waktu lama untuk membacanya;
  • Tidak mudah untuk menghafal semua langkah yang berbeda;

Sekarang mari kita bayangkan untuk membagi fungsi panjang menjadi beberapa sub-fungsi dan melihatnya dengan prospektif yang lebih luas.

Pro:

  • Kecuali fungsi cuti, setiap fungsi menjelaskan dengan kata-kata (nama sub-fungsi) berbagai langkah yang dilakukan;
  • Dibutuhkan waktu yang sangat singkat untuk membaca setiap fungsi / subfungsi tunggal;
  • Jelaslah parameter dan variabel apa yang dipengaruhi pada masing-masing sub-fungsi (pemisahan kekhawatiran);

Kontra:

  • Sangat mudah untuk membayangkan apa fungsi seperti "sin ()", tetapi tidak mudah untuk membayangkan apa yang dilakukan sub-fungsi kami;
  • Algoritma sekarang menghilang, sekarang didistribusikan di sub-fungsi may (tidak ada ikhtisar);
  • Ketika men-debug-nya langkah demi langkah, mudah untuk melupakan panggilan fungsi tingkat kedalaman yang Anda datangi (melompat ke sana-sini dalam file proyek Anda);
  • Anda dapat dengan mudah kehilangan konteks saat membaca berbagai sub-fungsi yang berbeda;

Kedua solusi tersebut memiliki pro dan kontra. Solusi terbaik yang sebenarnya adalah memiliki editor yang memungkinkan untuk memperluas, sebaris dan untuk kedalaman penuh, setiap fungsi memanggil kontennya. Yang akan membuat, fungsi pemisahan dalam sub fungsi satu-satunya solusi terbaik.

Antonello Ceravola
sumber
2

Bagi saya ada empat alasan untuk mengekstrak blok kode ke dalam fungsi:

  • Anda menggunakannya kembali : Anda baru saja menyalin blok kode ke clipboard. Alih-alih hanya menempelkannya, letakkan di fungsi dan ganti blok dengan dengan pemanggilan fungsi di kedua sisi. Jadi, setiap kali Anda perlu mengubah blok kode itu, Anda hanya perlu mengubah fungsi tunggal itu alih-alih mengubah kode beberapa tempat. Jadi, setiap kali Anda menyalin blok kode, Anda harus membuat suatu fungsi.

  • Ini adalah panggilan balik : Ini adalah pengendali acara atau semacam kode pengguna perpustakaan atau panggilan kerangka kerja. (Saya hampir tidak bisa membayangkan ini tanpa membuat fungsi.)

  • Anda yakin itu akan digunakan kembali , dalam proyek saat ini atau mungkin di tempat lain: Anda baru saja menulis blok yang menghitung urutan umum terpanjang dari dua array. Bahkan jika program Anda memanggil fungsi ini hanya sekali, saya percaya saya akan membutuhkan fungsi ini pada akhirnya di proyek lain juga.

  • Anda ingin kode yang mendokumentasikan diri : Jadi alih-alih menulis satu baris komentar di atas satu blok kode yang meringkas apa yang dilakukannya, Anda mengekstrak semuanya menjadi sebuah fungsi, dan beri nama apa yang akan Anda tulis dalam komentar. Meskipun saya bukan penggemar ini, karena saya suka menulis nama algoritma yang digunakan, alasan mengapa saya memilih algoritma itu, dll. Nama fungsi akan terlalu lama daripada ...

Calmarius
sumber
1

Saya yakin Anda pernah mendengar saran bahwa variabel harus dicakup seketat mungkin, dan saya harap Anda setuju dengan itu. Nah, fungsi adalah wadah lingkup, dan dalam fungsi yang lebih kecil cakupan variabel lokal lebih kecil. Jauh lebih jelas bagaimana dan kapan mereka seharusnya digunakan dan lebih sulit untuk menggunakannya dalam urutan yang salah atau sebelum diinisialisasi.

Juga, fungsi adalah wadah aliran logis. Hanya ada satu jalan masuk, jalan keluar ditandai dengan jelas, dan jika fungsinya cukup pendek, aliran internal harus jelas. Ini memiliki efek mengurangi kompleksitas siklomatik yang merupakan cara yang andal untuk mengurangi tingkat cacat.

John Wu
sumber
0

Selain itu: Saya menulis ini sebagai jawaban atas pertanyaan dallin (sekarang ditutup) tetapi saya masih merasa ini bisa bermanfaat bagi seseorang jadi begini


Saya berpikir bahwa alasan fungsi atomisasi adalah 2 kali lipat, dan seperti yang disebutkan @jozefg bergantung pada bahasa yang digunakan.

Pemisahan Kekhawatiran

Alasan utama untuk melakukan ini adalah untuk menjaga potongan kode yang berbeda terpisah, sehingga setiap blok kode yang tidak secara langsung berkontribusi pada hasil / maksud fungsi yang diinginkan adalah masalah yang terpisah dan dapat diekstraksi.

Katakanlah Anda memiliki tugas latar belakang yang juga memperbarui bilah progres, pembaruan bilah progres tidak terkait langsung dengan tugas yang berjalan lama sehingga harus diekstraksi, bahkan jika itu satu-satunya bagian dari kode yang menggunakan bilah progres.

Katakan dalam JavaScript Anda memiliki fungsi getMyData (), yang 1) membangun pesan sabun dari parameter, 2) menginisialisasi referensi layanan, 3) memanggil layanan dengan pesan sabun, 4) mem-parsing hasilnya, 5) mengembalikan hasilnya. Tampaknya masuk akal, saya telah menulis fungsi persis ini berkali-kali - tetapi sebenarnya itu dapat dibagi menjadi 3 fungsi pribadi hanya termasuk kode untuk 3 & 5 (jika itu) karena tidak ada kode lain yang secara langsung bertanggung jawab untuk mendapatkan data dari layanan .

Peningkatan Pengalaman Debugging

Jika Anda memiliki fungsi atomik sepenuhnya, jejak tumpukan Anda menjadi daftar tugas, daftar semua kode yang berhasil dieksekusi, yaitu:

  • Dapatkan Data Saya
    • Bangun Pesan Sabun
    • Menginisialisasi Referensi Layanan
    • Tanggapan Layanan Parsed - GALAT

akan membagikan lebih menarik kemudian menemukan bahwa ada kesalahan saat mendapatkan data. Tetapi beberapa alat bahkan lebih berguna untuk men-debug pohon panggilan terperinci dari itu, ambil contohnya Microsoft Debugger Canvas .

Saya juga memahami kekhawatiran Anda bahwa mungkin sulit untuk mengikuti kode yang ditulis dengan cara ini karena pada akhirnya, Anda harus memilih urutan fungsi dalam satu file di mana sebagai pohon panggilan Anda akan membagikan lebih rumit dari itu . Tetapi jika fungsi dinamai dengan baik (intellisense memungkinkan saya untuk menggunakan 3-4 kata kasus camal dalam fungsi apa pun yang saya harap tanpa memperlambat saya) dan terstruktur dengan antarmuka publik di bagian atas file, kode Anda akan dibaca seperti pseudo-code yang sejauh ini merupakan cara termudah untuk mendapatkan pemahaman tingkat tinggi dari basis kode.

FYI - ini adalah salah satu dari hal-hal "lakukan apa yang saya katakan tidak seperti yang saya lakukan", menjaga kode atom tidak ada gunanya kecuali Anda secara konsisten konsisten dengan itu IMHO, padahal saya tidak.

Mati
sumber