Saya punya proyek. Dalam proyek ini saya ingin memperbaiki itu untuk menambahkan fitur, dan saya refactored proyek untuk menambahkan fitur.
Masalahnya adalah ketika saya selesai, ternyata saya perlu membuat sedikit perubahan antarmuka untuk mengakomodasi itu. Jadi saya membuat perubahan. Dan kemudian kelas konsumsi tidak dapat diimplementasikan dengan antarmuka saat ini dalam hal yang baru, sehingga perlu antarmuka baru juga. Sekarang tiga bulan kemudian, dan saya harus memperbaiki masalah yang tidak berhubungan yang tak terhitung banyaknya, dan saya sedang mencari penyelesaian masalah yang dipetakan selama satu tahun dari sekarang atau hanya terdaftar sebagai tidak akan diperbaiki karena kesulitan sebelum hal itu dikompilasi lagi.
Bagaimana saya bisa menghindari jenis refactor cascading di masa depan? Apakah ini hanya gejala dari kelas saya sebelumnya yang terlalu bergantung satu sama lain?
Sunting singkat: Dalam hal ini, refactor adalah fitur, karena refactor meningkatkan ekstensibilitas potongan kode tertentu dan mengurangi beberapa kopling. Ini berarti bahwa pengembang eksternal dapat berbuat lebih banyak, yang merupakan fitur yang ingin saya sampaikan. Jadi refactor asli itu sendiri seharusnya tidak menjadi perubahan fungsional.
Sunting yang lebih besar yang saya janjikan lima hari yang lalu:
Sebelum saya memulai refactor ini, saya memiliki sistem di mana saya memiliki antarmuka, tetapi dalam implementasinya, saya hanya dynamic_cast
melalui semua kemungkinan implementasi yang saya kirimkan. Ini jelas berarti bahwa Anda tidak bisa hanya mewarisi dari antarmuka, untuk satu hal, dan kedua, bahwa tidak mungkin bagi siapa pun tanpa akses implementasi untuk mengimplementasikan antarmuka ini. Jadi saya memutuskan bahwa saya ingin memperbaiki masalah ini dan membuka antarmuka untuk konsumsi publik sehingga siapa pun dapat menerapkannya dan mengimplementasikan antarmuka adalah seluruh kontrak yang diperlukan - jelas merupakan peningkatan.
Ketika saya menemukan dan membunuh-dengan-api semua tempat yang telah saya lakukan ini, saya menemukan satu tempat yang terbukti menjadi masalah khusus. Itu tergantung pada detail implementasi dari semua berbagai kelas turunan dan fungsi duplikat yang sudah dilaksanakan tetapi lebih baik di tempat lain. Itu bisa saja diimplementasikan dalam hal antarmuka publik dan digunakan kembali implementasi yang ada dari fungsi itu. Saya menemukan bahwa diperlukan sepotong konteks untuk berfungsi dengan benar. Secara kasar, penerapan implementasi sebelumnya tampak seperti
for(auto&& a : as) {
f(a);
}
Namun, untuk mendapatkan konteks ini, saya perlu mengubahnya menjadi sesuatu yang lebih mirip
std::vector<Context> contexts;
for(auto&& a : as)
contexts.push_back(g(a));
do_thing_now_we_have_contexts();
for(auto&& con : contexts)
f(con);
Ini berarti bahwa untuk semua operasi yang dulunya merupakan bagian dari f
, beberapa dari mereka perlu dibuat menjadi bagian dari fungsi baru g
yang beroperasi tanpa konteks, dan beberapa dari mereka harus dibuat dari bagian yang ditangguhkan sekarang f
. Tetapi tidak semua metode f
membutuhkan atau menginginkan konteks ini - beberapa dari mereka memerlukan konteks berbeda yang mereka peroleh melalui cara yang berbeda. Jadi untuk semua yang pada f
akhirnya memanggil (yang secara kasar, hampir semuanya ), saya harus menentukan apa, jika ada, konteks yang mereka butuhkan, dari mana mereka seharusnya mendapatkannya, dan bagaimana memecahnya dari yang lama f
menjadi baru f
dan baru g
.
Dan begitulah akhirnya saya sampai di tempat saya sekarang. Satu-satunya alasan saya terus maju adalah karena saya memerlukan refactoring ini karena alasan lain.
Jawaban:
Terakhir kali saya mencoba memulai refactoring dengan konsekuensi yang tidak terduga, dan saya tidak bisa menstabilkan build dan / atau tes setelah satu hari , saya menyerah dan mengembalikan basis kode ke titik sebelum refactoring.
Kemudian, saya mulai menganalisis apa yang salah dan mengembangkan rencana yang lebih baik bagaimana melakukan refactoring dalam langkah-langkah yang lebih kecil. Jadi saran saya untuk menghindari cascading refactorings adalah: tahu kapan harus berhenti , jangan biarkan hal-hal di luar kendali Anda!
Terkadang Anda harus menggigit peluru dan membuang pekerjaan sehari penuh - jelas lebih mudah daripada membuang pekerjaan tiga bulan. Hari Anda kehilangan tidak sepenuhnya sia-sia, setidaknya Anda telah belajar bagaimana untuk tidak mendekati masalah. Dan menurut pengalaman saya, selalu ada kemungkinan untuk membuat langkah-langkah kecil dalam refactoring.
Catatan : Anda tampaknya berada dalam situasi di mana Anda harus memutuskan apakah Anda bersedia mengorbankan tiga bulan penuh kerja dan memulai lagi dengan rencana refactoring baru (dan semoga lebih sukses). Saya bisa membayangkan itu bukan keputusan yang mudah, tetapi tanyakan pada diri Anda, seberapa tinggi risiko yang Anda butuhkan tiga bulan lagi tidak hanya untuk menstabilkan bangunan, tetapi juga untuk memperbaiki semua bug yang tidak terduga yang mungkin Anda perkenalkan selama penulisan ulang Anda, Anda lakukan tiga bulan terakhir ? Saya menulis "menulis ulang", karena saya rasa itulah yang sebenarnya Anda lakukan, bukan "refactoring". Bukan tidak mungkin bahwa Anda dapat menyelesaikan masalah Anda saat ini dengan lebih cepat dengan kembali ke revisi terakhir tempat proyek Anda dikompilasi dan mulai dengan refactoring nyata (berlawanan dengan "menulis ulang") lagi.
sumber
Tentu. Satu perubahan yang menyebabkan segudang perubahan lainnya adalah definisi kopling.
Dalam jenis kode basis terburuk, satu perubahan akan terus mengalir, pada akhirnya menyebabkan Anda mengubah (hampir) semuanya. Bagian dari setiap refactor di mana ada kopling luas adalah untuk mengisolasi bagian yang sedang Anda kerjakan. Anda harus melakukan refactor tidak hanya di mana fitur baru Anda menyentuh kode ini, tetapi di mana yang lain menyentuh kode itu.
Biasanya itu berarti membuat beberapa adapter untuk membantu kode lama bekerja dengan sesuatu yang terlihat dan bertindak seperti kode lama, tetapi menggunakan implementasi / antarmuka baru. Lagi pula, jika semua yang Anda lakukan adalah mengubah antarmuka / implementasi tetapi meninggalkan kopling Anda tidak mendapatkan apa-apa. Ini lipstik pada babi.
sumber
Sepertinya refactoring Anda terlalu ambisius. Refactoring harus diterapkan dalam langkah-langkah kecil, yang masing-masing dapat diselesaikan dalam (katakanlah) 30 menit - atau, dalam skenario terburuk, paling banyak sehari - dan membuat proyek dapat dibangun dan semua tes masih berjalan.
Jika Anda menjaga agar setiap perubahan individu minimal, seharusnya tidak mungkin bagi refactoring untuk merusak bangunan Anda untuk waktu yang lama. Kasus terburuk mungkin adalah mengubah parameter ke metode di antarmuka yang banyak digunakan, misalnya untuk menambahkan parameter baru. Tetapi perubahan konsekuensial dari ini bersifat mekanis: menambahkan (dan mengabaikan) parameter di setiap implementasi, dan menambahkan nilai default di setiap panggilan. Bahkan jika ada ratusan referensi, seharusnya tidak butuh waktu sehari untuk melakukan refactoring seperti itu.
sumber
Desain Berpikir Wishful
Tujuannya adalah desain dan implementasi OO yang sangat baik untuk fitur baru. Menghindari refactoring juga merupakan tujuan.
Mulai dari awal dan buat desain untuk fitur baru yang Anda inginkan. Luangkan waktu untuk melakukannya dengan baik.
Namun perlu dicatat bahwa kuncinya di sini adalah "tambahkan fitur". Hal-hal baru cenderung membuat kita mengabaikan struktur basis kode saat ini. Desain angan-angan kami independen. Tetapi kemudian kita membutuhkan dua hal lagi:
Heuristik, Pembelajaran, dll.
Refactoring semudah menambahkan parameter default ke pemanggilan metode yang ada; atau satu panggilan ke metode kelas statis.
Metode penyuluhan pada kelas yang ada dapat membantu menjaga kualitas desain baru dengan risiko minimal absolut.
"Struktur" adalah segalanya. Struktur adalah realisasi dari Prinsip Tanggung Jawab Tunggal; desain yang memfasilitasi fungsi. Kode akan tetap singkat dan sederhana sepanjang hierarki kelas. Waktu untuk desain baru dibuat selama pengujian, bekerja kembali, dan menghindari peretasan melalui hutan kode warisan.
Kelas-kelas angan-angan fokus pada tugas yang dihadapi. Secara umum, lupakan tentang memperluas kelas yang ada - Anda hanya mendorong kaskade refactor lagi & harus berurusan dengan overhead kelas "yang lebih berat".
Bersihkan sisa-sisa fungsi baru ini dari kode yang ada. Di sini, fungsionalitas fitur baru yang lengkap dan dienkapsulasi dengan baik lebih penting daripada menghindari refactoring.
sumber
Dari buku (luar biasa) Bekerja Secara Efektif dengan Legacy Code oleh Michael Feathers :
sumber
Kedengarannya seperti (terutama dari diskusi di komentar) Anda telah memasukkan diri ke dalam aturan yang diberlakukan sendiri yang berarti bahwa perubahan "kecil" ini adalah jumlah pekerjaan yang sama dengan penulisan ulang lengkap perangkat lunak.
Solusinya harus "jangan lakukan itu, kalau begitu" . Inilah yang terjadi dalam proyek nyata. Banyak API lama memiliki antarmuka yang jelek atau parameter yang diabaikan (selalu nol), atau fungsi bernama DoThisThing2 () yang melakukan hal yang sama seperti DoThisThing () dengan daftar parameter yang sama sekali berbeda. Trik umum lainnya termasuk menyimpan informasi dalam global atau penunjuk yang ditandai untuk menyelundupkannya melewati sejumlah besar kerangka kerja. (Misalnya, saya punya proyek di mana setengah buffer audio hanya berisi nilai ajaib 4-byte karena itu jauh lebih mudah daripada mengubah cara perpustakaan memanggil codec audio-nya.)
Sulit untuk memberikan saran khusus tanpa kode khusus.
sumber
Tes otomatis. Anda tidak perlu menjadi fanatik TDD, Anda juga tidak perlu cakupan 100%, tetapi tes otomatis adalah apa yang memungkinkan Anda untuk melakukan perubahan dengan percaya diri. Selain itu, sepertinya Anda memiliki desain dengan kopling sangat tinggi; Anda harus membaca tentang prinsip-prinsip SOLID, yang dirumuskan secara khusus untuk mengatasi masalah semacam ini dalam desain perangkat lunak.
Saya juga merekomendasikan buku-buku ini.
sumber
Kemungkinan besar ya. Meskipun Anda bisa mendapatkan efek serupa dengan basis kode yang agak bagus dan bersih ketika persyaratan cukup berubah
Selain berhenti untuk bekerja pada kode warisan, Anda tidak dapat saya takut. Tetapi yang Anda bisa adalah menggunakan metode yang menghindari efek tidak memiliki basis kode kerja selama berhari-hari, berminggu-minggu, atau bahkan berbulan-bulan.
Metode itu bernama "Metode Mikado" dan kerjanya seperti ini:
tuliskan tujuan yang ingin Anda capai di selembar kertas
buat perubahan paling sederhana yang membawa Anda ke arah itu.
periksa apakah ia berfungsi menggunakan kompiler dan suite pengujian Anda. Jika tidak melanjutkan dengan langkah 7. jika tidak, lanjutkan dengan langkah 4.
pada kertas Anda perhatikan hal-hal yang perlu diubah untuk membuat perubahan Anda saat ini berfungsi. Gambarlah panah, dari tugas Anda saat ini, ke yang baru.
Kembalikan perubahan Anda Ini adalah langkah penting. Ini kontra intuitif dan secara fisik sakit pada awalnya, tetapi karena Anda baru saja mencoba hal sederhana, sebenarnya tidak seburuk itu.
pilih salah satu tugas, yang tidak memiliki kesalahan keluar (tidak ada dependensi yang diketahui) dan kembali ke 2.
melakukan perubahan, mencoret tugas di atas kertas, memilih tugas yang tidak memiliki kesalahan keluar (tidak ada dependensi yang diketahui) dan kembali ke 2.
Dengan cara ini Anda akan memiliki basis kode yang berfungsi dalam interval pendek. Di mana Anda juga dapat menggabungkan perubahan dari anggota tim lainnya. Dan Anda memiliki representasi visual dari apa yang Anda tahu masih harus Anda lakukan, ini membantu untuk memutuskan apakah Anda ingin melanjutkan dengan endevour atau jika Anda harus menghentikannya.
sumber
Refactoring adalah disiplin terstruktur, berbeda dari pembersihan kode yang Anda inginkan. Anda perlu memiliki unit test tertulis sebelum memulai, dan setiap langkah harus terdiri dari transformasi spesifik yang Anda tahu tidak boleh membuat perubahan fungsionalitas. Tes unit harus lulus setelah setiap perubahan.
Tentu saja, selama proses refactoring, Anda secara alami akan menemukan perubahan yang harus diterapkan yang dapat menyebabkan kerusakan. Dalam hal itu, cobalah yang terbaik untuk mengimplementasikan shim kompatibilitas untuk antarmuka lama yang menggunakan kerangka kerja baru. Secara teori, sistem harus tetap bekerja seperti sebelumnya, dan unit test harus lulus. Anda dapat menandai shim kompatibilitas sebagai antarmuka yang sudah usang, dan membersihkannya pada waktu yang lebih sesuai.
sumber
Seperti yang dikatakan @Jules, Refactoring dan menambahkan fitur adalah dua hal yang sangat berbeda.
... tapi memang, kadang-kadang Anda perlu mengubah pekerjaan dalam untuk menambahkan barang-barang Anda, tapi saya lebih suka menyebutnya memodifikasi daripada refactoring.
Di situlah segalanya menjadi berantakan. Antarmuka dimaksudkan sebagai batasan untuk mengisolasi implementasi dari cara penggunaannya. Segera setelah Anda menyentuh antarmuka, semua yang ada di kedua sisi (mengimplementasikan atau menggunakannya) juga harus diubah. Ini dapat menyebar sejauh yang Anda alami.
Antarmuka yang satu itu membutuhkan perubahan yang terdengar bagus ... yang menyebar ke yang lain menyiratkan bahwa perubahan menyebar lebih jauh. Kedengarannya seperti beberapa bentuk input / data yang diperlukan untuk mengalir ke bawah rantai. Apakah itu masalahnya?
Pembicaraan Anda sangat abstrak, sehingga sulit untuk dipecahkan. Contoh akan sangat membantu. Biasanya, antarmuka harus cukup stabil dan independen satu sama lain, sehingga memungkinkan untuk memodifikasi bagian dari sistem tanpa merusak sisanya ... berkat antarmuka.
... sebenarnya, cara terbaik untuk menghindari Cascading modifikasi kode yang tepat baik antarmuka. ;)
sumber
Saya pikir Anda biasanya tidak bisa kecuali Anda bersedia untuk menjaga hal-hal sebagaimana adanya. Namun, ketika situasi seperti situasi Anda, saya pikir hal terbaik adalah memberi tahu tim dan memberi tahu mereka mengapa harus ada refactoring yang dilakukan untuk melanjutkan pengembangan yang lebih sehat. Saya tidak akan hanya pergi dan memperbaiki sendiri. Saya akan membicarakannya selama pertemuan Scrum (dengan asumsi kalian memilikinya), dan mendekatinya secara sistematis dengan pengembang lain.
sumber