Prinsip Refactoring dan Terbuka / Tertutup

12

Baru-baru ini saya membaca situs web tentang pengembangan kode bersih (saya tidak menaruh tautan di sini karena tidak dalam bahasa Inggris).

Salah satu prinsip yang diiklankan oleh situs ini adalah Prinsip Terbuka Tertutup : setiap komponen perangkat lunak harus terbuka untuk ekstensi dan ditutup untuk modifikasi. Misalnya, ketika kami telah mengimplementasikan dan menguji suatu kelas, kami hanya harus memodifikasinya untuk memperbaiki bug atau untuk menambahkan fungsionalitas baru (mis. Metode baru yang tidak memengaruhi yang sudah ada). Fungsi dan implementasi yang ada tidak boleh diubah.

Saya biasanya menerapkan prinsip ini dengan mendefinisikan antarmuka Idan kelas implementasi yang sesuai A. Ketika kelas Atelah menjadi stabil (diimplementasikan dan diuji), saya biasanya tidak memodifikasinya terlalu banyak (mungkin, tidak sama sekali), yaitu

  1. Jika persyaratan baru tiba (mis. Kinerja, atau implementasi antarmuka yang sama sekali baru) yang memerlukan perubahan besar pada kode, saya menulis implementasi baru B, dan tetap menggunakan Aselama Btidak matang. Saat Bsudah matang, yang dibutuhkan hanyalah mengubah cara Iinstantiated.
  2. Jika persyaratan baru menyarankan perubahan ke antarmuka juga, saya mendefinisikan antarmuka baru I'dan implementasi baru A'. Jadi I, Adibekukan dan tetap implementasi untuk sistem produksi selama I'dan A'tidak cukup stabil untuk menggantikannya.

Jadi, mengingat pengamatan ini, saya sedikit terkejut bahwa halaman web kemudian menyarankan penggunaan refactoring kompleks , "... karena tidak mungkin untuk menulis kode secara langsung dalam bentuk akhirnya."

Apakah tidak ada kontradiksi / konflik antara menegakkan Prinsip Terbuka / Tertutup dan menyarankan penggunaan refactoring kompleks sebagai praktik terbaik? Atau idenya di sini adalah bahwa seseorang dapat menggunakan refactoring yang kompleks selama pengembangan suatu kelas A, tetapi ketika kelas itu telah diuji dengan sukses, ia harus dibekukan?

Giorgio
sumber

Jawaban:

9

Saya menganggap prinsip Open-Closed sebagai tujuan desain . Jika Anda akhirnya harus melanggarnya, maka itu berarti desain awal Anda gagal, yang tentu saja mungkin, dan bahkan mungkin.

Refactoring berarti Anda mengubah desain tanpa mengubah fungsionalitasnya. Kemungkinan Anda mengubah desain karena ada masalah dengannya. Mungkin masalahnya adalah sulit untuk mengikuti prinsip buka-tutup ketika membuat modifikasi pada kode yang ada, dan Anda sedang berusaha memperbaikinya.

Anda mungkin melakukan refactoring untuk memungkinkan penerapan fitur Anda berikutnya tanpa melanggar OCP ketika Anda melakukannya.

Scott Whitlock
sumber
Anda tentu tidak boleh berpikir sebagai prinsip sebagai tujuan desain . Mereka adalah alat - Anda tidak membuat perangkat lunak menjadi cantik dan secara teoritis benar di bagian dalam, Anda mencoba menghasilkan nilai untuk klien Anda. Itu adalah pedoman , tidak lebih.
T. Sar
@ T.Sar Prinsip adalah pedoman, sesuatu yang Anda perjuangkan, mereka berorientasi pada pemeliharaan dan skalabilitas. Itu terlihat seperti tujuan desain bagi saya. Saya tidak bisa melihat prinsip sebagai alat dalam cara saya melihat pola desain atau kerangka kerja sebagai alat.
Tulains Córdova
@ TulainsCórdova Maintabilitas, Kinerja, Ketepatan, Skalabilitas - itu adalah tujuan. Prinsip Terbuka-Tertutup adalah sarana untuk mereka - hanya satu dari banyak. Anda tidak perlu mendorong sesuatu ke arah prinsip tertutup-terbuka jika tidak berlaku untuk itu atau itu akan mengurangi tujuan sebenarnya dari proyek. Anda tidak menjual "Keterbukaan terbuka" kepada klien. Sebagai pedoman belaka , tidak ada yang lebih baik daripada aturan praktis yang dapat dibuang jika Anda akhirnya menemukan cara untuk melakukan pekerjaan Anda dengan cara yang lebih mudah dibaca dan jelas. Bagaimanapun juga, pedoman adalah alat, tidak lebih.
T. Sar
@ T.Sar Ada begitu banyak hal yang tidak bisa Anda jual ke klien ... Di sisi lain, saya setuju dengan Anda karena orang tidak boleh melakukan hal-hal yang mengurangi tujuan proyek.
Tulains Córdova
9

Prinsip Terbuka-Tertutup lebih merupakan indikator seberapa baik perangkat lunak Anda dirancang ; bukan prinsip untuk mengikuti secara harfiah. Ini juga merupakan prinsip yang membantu menjaga kita dari mengubah antarmuka yang ada secara tidak sengaja (kelas & metode yang Anda panggil dan bagaimana Anda mengharapkannya berfungsi).

Tujuannya adalah untuk menulis perangkat lunak yang berkualitas. Salah satu kualitas ini adalah sifat dapat diperpanjang. Ini berarti mudah untuk menambah, menghapus, mengubah kode dengan perubahan-perubahan itu cenderung terbatas pada beberapa kelas yang ada sebagai praktis. Menambahkan kode baru kurang berisiko daripada mengubah kode yang ada sehingga dalam hal ini Open-Closed adalah hal yang baik untuk dilakukan. Tapi kode apa yang sebenarnya kita bicarakan? Kejahatan melanggar OC jauh lebih sedikit ketika Anda dapat menambahkan metode baru ke kelas alih-alih perlu mengubah yang sudah ada.

OC fraktal . Apel di semua kedalaman desain Anda. Semua orang menganggap itu hanya diterapkan di tingkat kelas. Tetapi ini juga berlaku di tingkat metode atau di tingkat perakitan.

Pelanggaran OC yang terlalu sering pada tingkat yang sesuai menunjukkan bahwa mungkin inilah saatnya untuk melakukan refactor . "Level yang tepat" adalah panggilan penilaian yang memiliki segalanya untuk dilakukan dengan desain keseluruhan Anda.

Mengikuti Open-Closed secara harfiah berarti jumlah kelas akan meledak. Anda akan membuat Antarmuka (huruf "I") tidak perlu. Anda akan berakhir dengan sedikit fungsionalitas yang tersebar di seluruh kelas dan Anda kemudian harus menulis lebih banyak kode untuk menyatukan semuanya. Pada titik tertentu, Anda akan menyadari bahwa mengubah kelas asli akan lebih baik.

radarbob
sumber
2
"Kejahatan melanggar OC jauh lebih sedikit ketika Anda dapat menambahkan metode baru ke kelas daripada perlu mengubah yang sudah ada.": Sejauh yang saya mengerti, menambahkan metode baru tidak melanggar prinsip OC (terbuka untuk perpanjangan) sama sekali . Masalahnya adalah mengubah metode yang sudah ada yang mengimplementasikan antarmuka yang terdefinisi dengan baik dan oleh karena itu sudah memiliki semantik yang telah ditentukan (ditutup untuk modifikasi). Pada prinsipnya, refactoring tidak mengubah semantik, jadi satu-satunya risiko yang saya lihat adalah memperkenalkan bug pada kode yang sudah stabil dan teruji dengan baik.
Giorgio
1
Berikut adalah jawaban CodeReview yang menggambarkan terbuka untuk ekstensi . Desain kelas itu bisa diperpanjang. Sebaliknya, menambahkan metode adalah memodifikasi kelas.
radarbob
Menambahkan metode baru melanggar LSP, bukan OCP.
Tulains Córdova
1
Menambahkan metode baru tidak melanggar LSP. Jika Anda menambahkan metode, Anda telah memperkenalkan antarmuka baru @ TulainsCórdova
RubberDuck
6

Prinsip Terbuka-Tertutup tampaknya menjadi prinsip yang muncul sebelum TDD lebih lazim. Gagasannya adalah bahwa itu berisiko untuk memperbaiki kode karena Anda mungkin merusak sesuatu sehingga lebih aman untuk meninggalkan kode yang ada sebagaimana adanya dan hanya menambahkannya. Dengan tidak adanya tes ini masuk akal. Kelemahan dari pendekatan ini adalah atrofi kode. Setiap kali Anda memperpanjang kelas daripada refactoring, Anda berakhir dengan lapisan tambahan. Anda hanya mengunci kode di atas. Setiap kali Anda menambahkan lebih banyak kode pada Anda meningkatkan kemungkinan duplikasi. Membayangkan; ada layanan di basis kode saya yang ingin saya gunakan, saya merasa tidak memiliki apa yang saya inginkan jadi saya membuat kelas baru untuk memperluasnya dan menyertakan fungsionalitas baru saya. Pengembang lain datang kemudian dan juga ingin menggunakan layanan yang sama. Sayangnya, mereka tidak t menyadari bahwa versi saya yang diperluas ada. Mereka kode terhadap implementasi asli tetapi mereka juga membutuhkan salah satu fitur yang saya kode. Alih-alih menggunakan versi saya, mereka sekarang juga memperluas implementasi dan menambahkan fitur baru. Sekarang kita punya 3 kelas, yang asli satu dan dua versi baru yang memiliki beberapa fungsi yang digandakan. Ikuti prinsip terbuka / tertutup dan duplikasi ini akan terus membangun selama masa hidup proyek yang mengarah ke basis kode yang rumit dan tidak perlu.

Dengan sistem yang telah teruji dengan baik, Anda tidak perlu mengalami atrofi kode ini, Anda dapat dengan aman memperbaiki kode yang memungkinkan desain Anda berasimilasi dengan persyaratan baru daripada harus terus-menerus membaut kode baru. Gaya pengembangan ini disebut desain muncul dan mengarah ke basis kode yang mampu tetap dalam kondisi baik sepanjang hidup mereka daripada secara bertahap mengumpulkan cruft.

opsb
sumber
1
Saya bukan pendukung prinsip buka-tutup atau TDD (dalam arti bahwa saya tidak menciptakannya). Yang mengejutkan saya adalah seseorang mengusulkan prinsip terbuka-tertutup DAN penggunaan refactoring DAN TDD pada saat bersamaan. Ini sepertinya bertentangan dengan saya dan karena itu saya mencoba mencari cara untuk menyatukan semua pedoman ini ke dalam proses yang koheren.
Giorgio
"Gagasannya adalah beresiko untuk memperbaiki kode karena Anda mungkin memecahkan sesuatu sehingga lebih aman untuk meninggalkan kode yang ada sebagaimana adanya dan hanya menambahkannya.": Sebenarnya saya tidak melihatnya dengan cara ini. Idenya adalah untuk memiliki unit kecil yang mandiri yang dapat Anda ganti atau tambah (sehingga memungkinkan perangkat lunak untuk berkembang), tetapi Anda tidak boleh menyentuh setiap unit begitu unit tersebut telah diuji secara menyeluruh.
Giorgio
Anda harus berpikir bahwa kelas tidak hanya akan digunakan dalam basis kode Anda. Perpustakaan yang Anda tulis dapat digunakan dalam proyek lain. Jadi OCP itu penting. Juga, seorang programmer baru yang tidak mengetahui kelas yang diperluas dengan fungsi yang dia butuhkan adalah masalah komunikasi / dokumentasi, bukan masalah desain.
Tulains Córdova
@ TulainsCórdova dalam kode aplikasi ini tidak relevan. Untuk kode perpustakaan saya berpendapat bahwa versi semantik lebih cocok untuk mengkomunikasikan perubahan yang melanggar.
opsb
1
@ TulainsCórdova dengan kestabilan kode perpustakaan API jauh lebih penting karena tidak mungkin untuk menguji kode klien. Dengan kode aplikasi, cakupan tes Anda akan memberi tahu Anda setiap kerusakan dengan segera. Dengan kata lain, kode aplikasi dapat membuat perubahan melanggar tanpa risiko sedangkan kode perpustakaan harus mengelola risiko dengan mempertahankan API yang stabil dan memberi sinyal kerusakan menggunakan mis.
Versi
6

Dalam kata-kata awam:

A. Prinsip O / C berarti bahwa spesialisasi harus dilakukan dengan memperluas, bukan dengan memodifikasi kelas untuk mengakomodasi kebutuhan khusus.

B. Menambahkan fungsi yang hilang (tidak terspesialisasi) berarti desainnya tidak lengkap dan Anda harus menambahkannya ke kelas dasar, jelas tanpa melanggar kontrak. Saya pikir ini tidak melanggar prinsip.

C. Refactoring tidak melanggar prinsip.

Ketika desain matang , katakanlah, setelah beberapa waktu dalam produksi:

  • Seharusnya hanya ada sedikit alasan untuk melakukannya (titik B), cenderung nol dari waktu ke waktu.
  • (Poin C) akan selalu dimungkinkan meskipun lebih jarang.
  • Semua fungsionalitas baru seharusnya menjadi spesialisasi, artinya kelas harus diperluas (diwarisi dari) (titik A).
Tulains Córdova
sumber
Prinsip terbuka / tertutup sangat disalahpahami. Poin Anda A dan B benar.
gnasher729
1

Bagi saya, Prinsip Terbuka-Tertutup adalah pedoman, bukan aturan yang keras dan cepat.

Berkenaan dengan bagian terbuka dari prinsip, kelas akhir di Jawa dan kelas di C ++ dengan semua konstruktor dinyatakan sebagai pribadi melanggar bagian terbuka dari prinsip buka-tutup. Ada kasus penggunaan padat yang baik (catatan: solid, bukan SOLID) untuk kelas akhir. Merancang untuk diperpanjang adalah penting. Namun, ini membutuhkan banyak tinjauan ke depan dan usaha, dan Anda selalu melewati batas melanggar YAGNI (Anda tidak akan membutuhkannya) dan menyuntikkan bau kode dari generalisasi spekulatif. Haruskah komponen perangkat lunak utama terbuka untuk ekstensi? Iya. Semua? Tidak. Itu sendiri adalah generalisasi spekulatif.

Berkenaan dengan bagian tertutup, ketika beralih dari versi 2.0 ke 2.1 ke 2.2 ke 2.3 dari beberapa produk, tidak memodifikasi perilaku adalah ide yang sangat bagus. Pengguna benar-benar tidak menyukainya ketika setiap rilis minor memecah kode mereka sendiri. Namun, di sepanjang jalan orang sering menemukan bahwa implementasi awal dalam versi 2.0 pada dasarnya rusak, atau bahwa kendala eksternal yang membatasi desain awal tidak lagi berlaku. Apakah Anda menyeringai dan menanggungnya dan mempertahankan desain itu dalam rilis 3.0, atau apakah Anda membuat 3.0 tidak kompatibel dalam beberapa hal? Kompatibilitas mundur dapat menjadi kendala besar. Batas rilis utama adalah tempat di mana melanggar kompatibilitas dapat diterima. Anda perlu berhati-hati bahwa melakukan hal ini dapat membuat pengguna Anda kesal. Harus ada alasan yang bagus mengapa perpisahan dengan masa lalu ini diperlukan.

David Hammen
sumber
0

Refactoring, menurut definisi, adalah mengubah struktur kode tanpa mengubah perilaku. Jadi ketika Anda refactor, Anda tidak menambahkan fitur baru.

Apa yang Anda lakukan sebagai contoh untuk prinsip Buka Tutup terdengar OK. Prinsip ini adalah tentang memperluas kode yang ada dengan fitur baru.

Namun, jangan salah jawab. Saya tidak menyiratkan bahwa Anda hanya harus melakukan fitur atau hanya melakukan refactoring untuk potongan besar data. Cara pemrograman yang paling umum adalah melakukan sedikit fitur daripada segera melakukan sedikit refactoring (dikombinasikan dengan tes tentu saja untuk memastikan Anda tidak mengubah perilaku apa pun). Refactoring yang kompleks tidak berarti refactoring "besar", itu berarti menerapkan teknik refactoring yang rumit dan dipikirkan dengan baik.

Tentang prinsip-prinsip SOLID. Mereka benar-benar pedoman yang baik untuk pengembangan perangkat lunak tetapi tidak ada aturan agama yang harus diikuti secara membabi buta. Terkadang, berkali-kali, setelah Anda menambahkan fitur kedua dan ketiga dan ke-n, Anda menyadari bahwa desain awal Anda, bahkan jika itu menghormati Open-Close, itu tidak menghormati prinsip-prinsip lain atau persyaratan perangkat lunak. Ada beberapa poin dalam evolusi desain dan perangkat lunak ketika perubahan yang lebih kompleks harus dilakukan. Intinya adalah untuk menemukan dan menyadari masalah ini sesegera mungkin dan menerapkan teknik refactoring sebaik mungkin.

Tidak ada yang namanya desain sempurna. Tidak ada desain seperti itu yang dapat dan harus menghormati semua prinsip atau pola yang ada. Itu adalah pengkodean utopia.

Saya harap jawaban ini membantu Anda dalam dilema Anda. Jangan ragu untuk meminta klarifikasi jika diperlukan.

Patkos Csaba
sumber
1
"Jadi ketika Anda refactor, Anda tidak menambahkan fitur baru.": Tapi saya bisa memperkenalkan bug pada perangkat lunak yang teruji.
Giorgio
"Kadang-kadang, berkali-kali, setelah Anda menambahkan fitur kedua dan ketiga dan ke-n, Anda menyadari bahwa desain awal Anda, bahkan jika itu menghormati Open-Close, itu tidak menghormati prinsip atau persyaratan perangkat lunak lain.": Saat itulah saya akan mulai menulis implementasi baru Bdan, ketika sudah siap, ganti implementasi yang lama Adengan implementasi yang baru B(itu adalah satu penggunaan antarmuka). AKode dapat berfungsi sebagai dasar untuk Bkode dan kemudian saya dapat menggunakan refactoring pada Bkode selama pengembangannya, tetapi saya berpikir bahwa Akode yang sudah diuji harus tetap beku.
Giorgio
@Giorgio Ketika Anda refactor Anda dapat memperkenalkan bug, itu sebabnya Anda menulis tes (atau bahkan lebih baik melakukan TDD). Cara teraman untuk refactor adalah mengubah kode ketika Anda tahu itu berfungsi. Anda tahu ini dengan memiliki serangkaian tes yang lulus. Setelah Anda mengubah kode produksi, tes harus tetap berlalu, sehingga Anda tahu Anda tidak memperkenalkan bug. Dan ingat, tes sama pentingnya dengan kode produksi, jadi Anda menerapkan aturan yang sama dengannya seperti pada kode produksi dan tetap bersih dan refactor secara berkala dan sering.
Patkos Csaba
@Giorgio Jika kode Bdibangun di atas kode Asebagai evolusi A, daripada, ketika Bdirilis, Aharus dihapus dan tidak pernah digunakan lagi. Klien yang sebelumnya menggunakan Ahanya akan menggunakan Btanpa mengetahui tentang perubahan, karena antarmuka Itidak berubah (mungkin sedikit Prinsip Substitusi Liskov di sini? ... L dari SOLID)
Patkos Csaba
Ya, inilah yang ada dalam pikiran saya: jangan membuang kode kerja sampai Anda memiliki pengganti yang valid (teruji dengan baik).
Giorgio
-1

Sesuai pemahaman saya - jika Anda menambahkan metode baru ke kelas yang ada maka tidak akan merusak OCP. Namun saya agak bingung dengan penambahan variabel baru di Kelas. Tetapi jika Anda mengubah metode dan parameter yang ada dalam metode yang ada maka pasti akan merusak OCP, karena kode sudah diuji dan lulus jika kami sengaja mengubah metode [Ketika persyaratan berubah] maka itu akan menjadi masalah.

Narender Parmar
sumber