Apakah metode pribadi dengan gaya referensi tunggal buruk?

139

Secara umum saya menggunakan metode pribadi untuk merangkum fungsionalitas yang digunakan kembali di banyak tempat di kelas. Tetapi kadang-kadang saya memiliki metode publik besar yang dapat dipecah menjadi langkah-langkah yang lebih kecil, masing-masing dengan metode pribadi. Ini akan membuat metode publik lebih pendek, tetapi saya khawatir bahwa memaksa siapa pun yang membaca metode untuk beralih ke metode pribadi yang berbeda akan merusak keterbacaan.

Apakah ada konsensus tentang ini? Apakah lebih baik memiliki metode publik yang panjang, atau memecahnya menjadi potongan-potongan kecil bahkan jika masing-masing bagian tidak dapat digunakan kembali?

Jordak
sumber
7
Semua jawaban didasarkan pada pendapat. Penulis asli selalu berpikir kode ravioli mereka lebih mudah dibaca; editor selanjutnya menyebutnya ravioli.
Frank Hileman
5
Saya sarankan Anda membaca Kode Bersih. Ini merekomendasikan gaya ini dan memiliki banyak contoh. Ini juga memiliki solusi untuk masalah "melompat-lompat".
Kat
1
Saya pikir ini tergantung pada tim Anda dan baris kode apa yang terkandung.
Semoga bermanfaat
1
Bukankah seluruh kerangka kerja STRUT didasarkan pada metode Getter / Setter sekali pakai, yang semuanya merupakan metode sekali pakai?
Zibbobz

Jawaban:

203

Tidak, ini bukan gaya yang buruk. Sebenarnya itu adalah gaya yang sangat bagus.

Fungsi pribadi tidak perlu ada hanya karena usabilitas ulang. Itu tentu salah satu alasan bagus untuk membuatnya, tetapi ada alasan lain: dekomposisi.

Pertimbangkan fungsi yang terlalu banyak. Panjangnya seratus baris, dan mustahil dipikirkan.

Jika Anda membagi fungsi ini menjadi potongan-potongan kecil, itu masih "tidak" berfungsi sebanyak sebelumnya, tetapi dalam potongan-potongan kecil. Ini memanggil fungsi lain yang harus memiliki nama deskriptif. Fungsi utamanya membaca hampir seperti buku: lakukan A, lalu lakukan B, lalu lakukan C, dll. Fungsi-fungsi yang dipanggil hanya dapat dipanggil di satu tempat, tetapi sekarang mereka lebih kecil. Setiap fungsi tertentu harus dikosongkan dari fungsi lain: mereka memiliki cakupan yang berbeda.

Saat Anda menguraikan masalah besar menjadi masalah yang lebih kecil, bahkan jika masalah yang lebih kecil (fungsi) hanya digunakan / diselesaikan satu kali, Anda mendapatkan beberapa manfaat:

  • Keterbacaan. Tidak ada yang bisa membaca fungsi monolitik dan memahami apa yang dilakukannya sepenuhnya. Anda bisa terus membohongi diri sendiri, atau membaginya menjadi potongan seukuran gigitan yang masuk akal.

  • Lokalitas referensi. Sekarang menjadi tidak mungkin untuk mendeklarasikan dan menggunakan variabel, kemudian membuatnya bertahan, dan digunakan lagi 100 baris kemudian. Fungsi-fungsi ini memiliki cakupan yang berbeda.

  • Pengujian. Walaupun hanya perlu menguji unit anggota publik dari suatu kelas, mungkin diinginkan untuk menguji anggota pribadi tertentu juga. Jika ada bagian kritis dari fungsi panjang yang mungkin mendapat manfaat dari pengujian, tidak mungkin untuk mengujinya secara independen tanpa mengekstraksinya ke fungsi yang terpisah.

  • Modularitas. Sekarang Anda memiliki fungsi pribadi, Anda dapat menemukan satu atau lebih dari itu yang dapat diekstraksi ke dalam kelas yang terpisah, apakah itu hanya digunakan di sini atau dapat digunakan kembali. Untuk poin sebelumnya, kelas yang terpisah ini cenderung lebih mudah untuk diuji juga, karena akan membutuhkan antarmuka publik.

Gagasan untuk memecah kode besar menjadi potongan-potongan kecil yang lebih mudah dipahami dan diuji adalah poin kunci dari buku Kode Bersih Paman Bob . Pada saat menulis jawaban ini, bukunya berusia sembilan tahun, tetapi sama relevannya dengan zaman dulu.


sumber
7
Jangan lupa mempertahankan level abstraksi yang konsisten dan nama baik yang memastikan mengintip ke dalam metode tidak akan mengejutkan Anda. Jangan heran jika seluruh objek bersembunyi di sana.
candied_orange
14
Kadang-kadang metode pemisahan bisa baik, tetapi menjaga hal-hal yang sejalan juga dapat memiliki keuntungan juga. Jika pemeriksaan batas secara masuk akal dapat dilakukan baik di dalam atau di luar blok kode, misalnya, menjaga blok kode in-line akan membuatnya mudah untuk melihat apakah pemeriksaan batas dilakukan sekali, lebih dari sekali, atau tidak sama sekali . Menarik blok kode ke dalam subrutinnya sendiri akan membuat hal-hal seperti itu lebih sulit untuk dipastikan.
supercat
7
Peluru "keterbacaan" dapat diberi label "Abstraksi". Ini tentang abstraksi. "Potongan seukuran gigitan" melakukan satu hal , satu hal tingkat rendah yang Anda tidak terlalu peduli tentang detail seluk-beluk ketika Anda membaca atau melangkah melalui metode publik. Dengan mengabstraksikannya ke suatu fungsi, Anda dapat melangkahinya dan tetap fokus pada skema besar hal-hal / apa yang dilakukan metode publik pada tingkat yang lebih tinggi.
Mathieu Guindon
7
"Pengujian" peluru dipertanyakan IMO. Jika anggota pribadi Anda ingin diuji, maka mereka mungkin termasuk dalam kelas mereka sendiri, sebagai anggota publik. Memang, langkah pertama untuk mengekstraksi kelas / antarmuka adalah mengekstraksi metode.
Mathieu Guindon
6
Memisahkan sebuah fungsi semata-mata dengan nomor baris, blok kode, dan ruang lingkup variabel tanpa memperhatikan fungsionalitas sebenarnya adalah ide yang mengerikan, dan saya berpendapat alternatif yang lebih baik adalah membiarkannya dalam satu fungsi. Anda harus memisahkan fungsi sesuai fungsi. Lihat jawaban DaveGauer . Anda agak mengisyaratkan hal ini dalam jawaban Anda (dengan menyebutkan nama dan masalah), tetapi saya merasa bahwa aspek ini membutuhkan lebih banyak fokus, mengingat pentingnya dan relevansinya dalam kaitannya dengan pertanyaan.
Dukeling
36

Itu mungkin ide yang bagus!

Saya memang mengambil masalah dengan memecah urutan linear panjang aksi menjadi fungsi terpisah murni untuk mengurangi panjang fungsi rata-rata dalam basis kode Anda:

function step1(){
  // ...
  step2(zarb, foo, biz);
}

function step2(zarb, foo, biz){
  // ...
  step3(zarb, foo, biz, gleep);
}

function step3(zarb, foo, biz, gleep){
  // ...
}

Sekarang Anda benar-benar telah menambahkan baris sumber dan mengurangi keterbacaan total. Apalagi jika Anda sekarang melewati banyak parameter antara masing-masing fungsi untuk melacak keadaan. Astaga!

Namun , jika Anda berhasil mengekstrak satu atau lebih baris menjadi fungsi murni yang melayani tujuan tunggal yang jelas ( bahkan jika dipanggil hanya sekali ), maka Anda telah meningkatkan keterbacaan:

function foo(){
  f = getFrambulation();
  g = deglorbFramb(f);
  r = reglorbulate(g);
}

Ini kemungkinan tidak akan mudah dalam situasi dunia nyata, tetapi bagian-bagian dari fungsionalitas murni seringkali dapat dihilangkan jika Anda memikirkannya cukup lama.

Anda akan tahu bahwa Anda berada di jalur yang benar ketika Anda memiliki fungsi dengan nama kata kerja yang bagus dan ketika fungsi orangtua Anda memanggil mereka dan semuanya secara praktis dibaca seperti paragraf prosa.

Kemudian ketika Anda kembali berminggu-minggu kemudian untuk menambahkan lebih banyak fungsi dan Anda menemukan bahwa Anda benar - benar dapat menggunakan kembali salah satu dari fungsi-fungsi itu, maka oh, sukacita yang meriah! Sungguh kegembiraan yang luar biasa bercahaya!

DaveGauer
sumber
2
Saya telah mendengar argumen ini selama bertahun-tahun, dan itu tidak pernah terjadi di dunia nyata. Saya berbicara secara khusus tentang satu atau dua fungsi garis, dipanggil hanya dari satu tempat. Sementara penulis asli selalu berpikir itu "lebih mudah dibaca", editor berikutnya sering menyebutnya kode ravioli. Ini tergantung pada kedalaman panggilan, secara umum.
Frank Hileman
34
@ FrankHileman Agar adil, saya tidak pernah melihat pengembang memuji kode pendahulunya.
T. Sar
4
@Tar pengembang sebelumnya adalah sumber dari semua masalah!
Frank Hileman
18
@TSar Saya bahkan tidak pernah memuji pengembang sebelumnya ketika saya adalah pengembang sebelumnya.
IllusiveBrian
3
Hal yang menyenangkan tentang penguraian adalah Anda kemudian dapat menulis foo = compose(reglorbulate, deglorbFramb, getFrambulation);
:;
19

Jawabannya sangat tergantung pada situasi. @Snowman membahas hal positif dari memecah fungsi publik yang besar, tetapi penting untuk diingat bahwa ada efek negatif juga, karena Anda benar-benar khawatir.

  • Banyak fungsi pribadi dengan efek samping dapat membuat kode sulit dibaca dan cukup rapuh. Ini terutama benar ketika fungsi-fungsi pribadi ini bergantung pada efek samping satu sama lain. Hindari memiliki fungsi yang digabungkan secara ketat.

  • Abstraksi bocor . Meskipun menyenangkan untuk berpura-pura bagaimana operasi dilakukan atau bagaimana data disimpan tidak masalah, ada beberapa kasus di mana itu dilakukan, dan penting untuk mengidentifikasi mereka.

  • Semantik dan konteks penting. Jika Anda gagal menangkap dengan jelas fungsi dari namanya, Anda dapat mengurangi keterbacaan dan meningkatkan kerapuhan fungsi publik. Ini terutama benar ketika Anda mulai membuat fungsi pribadi dengan sejumlah besar parameter input dan output. Dan sementara dari fungsi publik, Anda melihat apa fungsi pribadi yang dipanggil, dari fungsi pribadi, Anda tidak melihat apa fungsi publik menyebutnya. Ini dapat menyebabkan "perbaikan bug" dalam fungsi pribadi yang merusak fungsi publik.

  • Kode yang sangat terurai selalu lebih jelas bagi penulis daripada yang lain. Ini tidak berarti bahwa itu masih belum jelas bagi orang lain, tetapi mudah untuk mengatakan bahwa itu masuk akal yang bar()harus dipanggil sebelumnya foo()pada saat penulisan.

  • Penggunaan kembali yang berbahaya. Fungsi publik Anda kemungkinan membatasi input apa yang mungkin untuk setiap fungsi pribadi. Jika asumsi input ini tidak ditangkap dengan benar (mendokumentasikan SEMUA asumsi sulit), seseorang mungkin menggunakan kembali salah satu fungsi pribadi Anda dengan tidak benar, memasukkan bug ke dalam basis kode.

Mengurai fungsi berdasarkan kohesi dan kopling internal, bukan panjang absolut.

dlasalle
sumber
6
Abaikan keterbacaan, mari kita lihat aspek pelaporan bug: jika pengguna melaporkan bahwa bug terjadi MainMethod(cukup mudah didapat, biasanya simbol disertakan dan Anda harus memiliki file simbol dereferensi) yang merupakan baris 2k (nomor baris tidak pernah dimasukkan dalam laporan bug) dan itu adalah NRE yang dapat terjadi pada 1k dari baris itu, baik saya akan terkutuk jika saya melihat ke dalamnya. Tetapi jika mereka mengatakan itu terjadi pada SomeSubMethodA8 baris dan pengecualiannya adalah NRE, maka saya mungkin akan menemukan dan memperbaikinya dengan cukup mudah.
410_Lewat
2

IMHO, nilai mengeluarkan blok kode murni sebagai alat untuk memecah kompleksitas, akan sering dikaitkan dengan perbedaan dalam kompleksitas antara:

  1. Deskripsi bahasa manusia yang lengkap dan akurat tentang apa yang dilakukan kode, termasuk bagaimana menangani kasus sudut , dan

  2. Kode itu sendiri.

Jika kode ini jauh lebih rumit daripada deskripsi, mengganti kode in-line dengan panggilan fungsi dapat membuat kode di sekitarnya lebih mudah dimengerti. Di sisi lain, beberapa konsep dapat diekspresikan lebih mudah dalam bahasa komputer daripada dalam bahasa manusia. Saya akan menganggap w=x+y+z;, misalnya, lebih mudah dibaca daripada w=addThreeNumbersAssumingSumOfFirstTwoDoesntOverflow(x,y,z);.

Ketika fungsi besar terpecah, akan ada semakin sedikit perbedaan di antara kompleksitas sub-fungsi versus deskripsi mereka, dan keuntungan dari subdivisi lebih lanjut akan berkurang. Jika ada beberapa hal yang terpecah sehingga deskripsi akan lebih rumit daripada kode, perpecahan lebih lanjut akan membuat kode lebih buruk.

supercat
sumber
2
Nama fungsi Anda menyesatkan. Tidak ada pada w = x + y + z yang menunjukkan segala jenis kontrol overflow, sedangkan nama metode dengan sendirinya tampaknya menyiratkan sebaliknya. Nama yang lebih baik untuk fungsi Anda adalah "addAll (x, y, z)", misalnya, atau bahkan hanya "Jumlah (x, y, z)".
T. Sar
6
Karena sedang berbicara tentang metode pribadi , pertanyaan ini tidak mungkin mengacu pada C. Pokoknya, itu bukan poin saya - Anda bisa memanggil fungsi yang sama "jumlah" tanpa perlu menangani asumsi pada tanda tangan metode. Dengan logika Anda, metode rekursif apa pun harus disebut "DoThisAssumingStackDoesntOverflow", misalnya. Hal yang sama berlaku untuk "FindSubstringAssumingStartingPointIsInsideBounds".
T. Sar
1
Dengan kata lain, asumsi dasar, apa pun itu (dua angka tidak akan meluap, tumpukan tidak akan meluap, indeks diteruskan dengan benar) tidak boleh ada pada nama metode. Hal-hal itu harus didokumentasikan, tentu saja, jika diperlukan, tetapi tidak pada metode tanda tangan.
T. Sar
1
@ TTS: Saya menjadi sedikit jenaka dengan nama, tetapi intinya adalah (beralih ke rata-rata karena itu adalah contoh yang lebih baik) seorang programmer yang melihat "(x + y + z + 1) / 3" dalam konteks akan menjadi jauh lebih baik ditempatkan untuk mengetahui apakah itu cara yang tepat untuk menghitung rata-rata yang diberikan kode panggilan, daripada seseorang yang melihat definisi atau situs panggilan int average3(int n1, int n2, int n3). Membagi fungsi "rata-rata" akan berarti bahwa seseorang yang ingin melihat apakah itu cocok untuk persyaratan harus mencari di dua tempat.
supercat
1
Jika kita mengambil C # misalnya, Anda hanya akan memiliki satu metode "Rata-rata", yang dapat dibuat untuk menerima sejumlah parameter. Bahkan, dalam c #, ia berfungsi atas kumpulan angka - dan saya yakin itu lebih mudah dipahami daripada menangani matematika mentah ke dalam kode.
T. Sar
2

Ini adalah tantangan penyeimbang.

Lebih baik lebih baik

Metode pribadi secara efektif memberikan nama untuk kode yang terkandung, dan kadang-kadang tanda tangan yang bermakna (kecuali setengah dari parameternya sepenuhnya data ad-hoc tramp dengan saling ketergantungan yang tidak jelas dan tidak terdokumentasi).

Memberi nama untuk kode konstruksi umumnya baik, selama nama-nama tersebut menyarankan kontrak yang bermakna bagi penelepon, dan kontrak metode pribadi secara akurat sesuai dengan apa yang disarankan oleh namanya.

Dengan memaksa diri untuk memikirkan kontrak yang berarti untuk bagian kode yang lebih kecil, pengembang awal dapat menemukan beberapa bug dan menghindarinya tanpa rasa sakit bahkan sebelum pengujian dev. Ini hanya berfungsi jika pengembang berusaha untuk penamaan ringkas (terdengar sederhana tapi akurat) dan bersedia untuk menyesuaikan batas-batas metode pribadi sehingga penamaan ringkas bahkan mungkin.

Pemeliharaan selanjutnya juga terbantu, karena penamaan tambahan membantu membuat kode tersebut didokumentasikan sendiri .

  • Kontrak lebih dari potongan kode kecil
  • Metode tingkat yang lebih tinggi kadang-kadang berubah menjadi urutan panggilan pendek, yang masing-masing melakukan sesuatu yang signifikan dan diberi nama berbeda - disertai dengan lapisan tipis penanganan kesalahan umum, terluar. Metode seperti itu dapat menjadi sumber pelatihan yang bernilai bagi siapa saja yang harus dengan cepat menjadi seorang ahli pada keseluruhan struktur modul.

Sampai terlalu baik

Apakah mungkin untuk memberi nama secara berlebihan pada potongan kode kecil, dan berakhir dengan terlalu banyak, metode pribadi yang terlalu kecil? Tentu. Satu atau lebih dari gejala berikut menunjukkan bahwa metode menjadi terlalu kecil untuk berguna:

  • Terlalu banyak kelebihan yang tidak benar-benar mewakili tanda tangan alternatif untuk logika esensial yang sama, melainkan tumpukan panggilan tetap tunggal.
  • Sinonim yang digunakan hanya untuk merujuk pada konsep yang sama berulang kali ("penamaan menghibur")
  • Banyak dekorasi penamaan ala kadarnya seperti XxxInternalatau DoXxx, terutama jika tidak ada skema terpadu dalam memperkenalkannya.
  • Nama-nama yang canggung hampir lebih panjang dari implementasinya sendiri, seperti LogDiskSpaceConsumptionUnlessNoUpdateNeeded
Jirka Hanika
sumber
1

Berlawanan dengan apa yang dikatakan orang lain, saya berpendapat bahwa metode publik yang panjang adalah aroma desain yang tidak diperbaiki dengan mendekomposisi menjadi metode pribadi.

Tetapi kadang-kadang saya memiliki metode publik yang besar yang dapat dipecah menjadi langkah-langkah yang lebih kecil

Jika ini masalahnya maka saya berpendapat setiap langkah harus menjadi warga negara kelas satu dengan tanggung jawab tunggal masing-masing. Dalam paradigma berorientasi objek, saya akan menyarankan untuk membuat antarmuka dan implementasi untuk setiap langkah sedemikian rupa sehingga masing-masing memiliki tanggung jawab tunggal yang mudah diidentifikasi dan dapat dinamai dengan cara yang tanggung jawabnya jelas. Ini memungkinkan Anda untuk menguji unit metode publik besar (sebelumnya), serta setiap langkah individu secara independen satu sama lain. Mereka semua harus didokumentasikan juga.

Mengapa tidak terurai menjadi metode pribadi? Berikut ini beberapa alasan:

  • Kopling ketat dan testabilitas. Dengan mengurangi ukuran metode publik Anda, Anda telah meningkatkan keterbacaannya, tetapi semua kode masih tergabung erat. Anda dapat menguji unit metode pribadi individu (menggunakan fitur canggih dari kerangka pengujian), tetapi Anda tidak dapat dengan mudah menguji metode publik secara independen dari metode pribadi. Ini bertentangan dengan prinsip-prinsip pengujian unit.
  • Ukuran dan kompleksitas kelas. Anda telah mengurangi kompleksitas metode, tetapi Anda telah meningkatkan kompleksitas kelas. Metode publik lebih mudah dibaca, tetapi kelas sekarang lebih sulit dibaca karena memiliki lebih banyak fungsi yang mendefinisikan perilakunya. Preferensi saya adalah untuk kelas kecil dengan tanggung jawab tunggal, jadi metode yang panjang merupakan tanda bahwa kelas terlalu banyak melakukan.
  • Tidak dapat digunakan kembali dengan mudah. Ini sering terjadi bahwa sebagai badan kode yang matang dapat digunakan kembali berguna. Jika langkah Anda menggunakan metode pribadi, mereka tidak dapat digunakan kembali di tempat lain tanpa terlebih dahulu mengekstraksinya. Selain itu dapat mendorong copy-paste ketika langkah diperlukan di tempat lain.
  • Pemisahan dengan cara ini cenderung sewenang-wenang. Saya berpendapat bahwa pemisahan metode publik yang panjang tidak mengambil banyak pemikiran atau pertimbangan desain seolah-olah Anda harus membagi tanggung jawab ke dalam kelas. Setiap kelas harus dibenarkan dengan nama, dokumentasi, dan tes yang tepat, sedangkan metode pribadi tidak mendapatkan banyak pertimbangan.
  • Menyembunyikan masalah. Jadi Anda telah memutuskan untuk membagi metode publik Anda menjadi metode pribadi kecil. Sekarang tidak ada masalah! Anda dapat terus menambahkan lebih banyak langkah dengan menambahkan lebih banyak metode pribadi! Sebaliknya, saya pikir ini adalah masalah besar. Itu menetapkan pola untuk menambahkan kompleksitas ke kelas yang akan diikuti oleh perbaikan bug berikutnya dan implementasi fitur. Segera metode pribadi Anda akan tumbuh dan mereka harus dipisah.

tetapi saya khawatir bahwa memaksa siapa pun yang membaca metode ini untuk beralih ke metode pribadi yang berbeda akan merusak keterbacaan

Ini adalah argumen yang saya miliki dengan salah satu rekan saya baru-baru ini. Dia berpendapat bahwa memiliki seluruh perilaku modul dalam file / metode yang sama meningkatkan keterbacaan. Saya setuju bahwa kode lebih mudah untuk diikuti ketika semua bersama-sama, tetapi kode ini kurang mudah untuk dipikirkan ketika kompleksitas bertambah. Ketika sebuah sistem tumbuh, ia menjadi sulit untuk dipikirkan tentang keseluruhan modul secara keseluruhan. Ketika Anda menguraikan logika kompleks menjadi beberapa kelas masing-masing dengan responsiblity tunggal, maka itu menjadi lebih mudah untuk alasan tentang setiap bagian.

Samuel
sumber
1
Saya harus tidak setuju. Terlalu banyak atau abstraksi prematur menyebabkan kode rumit yang tidak perlu. Metode pribadi dapat menjadi langkah pertama dalam jalur yang berpotensi perlu dihubungkan dan disarikan, tetapi pendekatan Anda di sini menyarankan untuk berkeliaran di sekitar tempat-tempat yang mungkin tidak perlu dikunjungi.
WillC
1
Saya memahami di mana Anda datang dari, tapi saya pikir kode bau seperti yang OP adalah bertanya tentang adalah tanda-tanda bahwa itu adalah siap untuk desain yang lebih baik. Abstraksi prematur akan mendesain antarmuka ini bahkan sebelum Anda mengalami masalah itu.
Samuel
Saya setuju dengan pendekatan ini. Bahkan, ketika Anda mengikuti TDD, itu adalah perkembangan alami. Orang lain mengatakan itu terlalu banyak abstraksi prematur, tetapi saya merasa lebih mudah untuk dengan cepat mengejek fungsi yang saya butuhkan di kelas yang terpisah (di belakang antarmuka), daripada harus benar-benar mengimplementasikannya dalam metode pribadi agar tes dapat lulus .
Eternal21