Penyembunyian dan Transfer Kompleksitas dalam "Optimasi Pemeliharaan"
Ketika kompleksitas dipindahkan begitu saja dari fungsi besar ke hierarki kelas, konfigurasi, dan rantai panggilan, sistem biasanya tidak dapat dikelola dengan lebih baik.
Ketika banyak tim melakukan “optimasi pemeliharaan”, langkah pertama adalah membongkar kodenya.
Fungsi 300 baris dibagi menjadi 12 kelas; proses dengan banyak cabang diubah menjadi “strategi + pabrik + konfigurasi”; logika yang dapat dipahami dengan mengikuti panggilan diubah menjadi acara, pelanggan, tabel aturan, dan beberapa direktori yang tampak bersih.
Kodenya memang kurang ramai, dan satu file lebih pendek. Saat mengulasnya, bahkan membuat orang merasa “ini sudah canggih”.
Namun penilaian saya adalah: **Banyak yang disebut optimasi pemeliharaan tidak mengurangi kompleksitas, namun hanya mengubah kompleksitas dari terlihat sebagian menjadi tersebar, tidak menentu, dan tersembunyi. ** Akibat paling umum dari jenis perubahan ini adalah masalah lebih sulit ditemukan, perubahan lebih sulit dievaluasi, dan lebih sulit dipahami oleh orang baru.
Inti dari pemeliharaan adalah: **Ketika persyaratan berubah, kesalahan online terjadi, dan kondisi batas muncul, dapatkah tim dengan cepat melihat kendala sebenarnya dan melakukan modifikasi yang aman dalam lingkup terbatas? **
Kompleksitas tidak akan hilang karena perpecahan, ia hanya akan menetap di tempat lain.
Situasi yang umum adalah intuisi “pemeliharaan” terlalu visual.
Mereka merasa tidak nyaman ketika mereka melihat fungsi-fungsi besar, mereka merasa terbelakang ketika mereka melihat banyak if/else, dan mereka secara naluriah ingin memisahkannya ketika mereka melihat banyak penilaian bisnis dimasukkan ke dalam satu kelas. Jadi logika kompleks dipecah menjadi banyak file tipis, cabang kondisional diterjemahkan ke dalam tingkat objek, aturan bisnis dipindahkan ke konfigurasi, dan sedikit antarmuka dan penamaan ditambahkan, dan permukaan kode segera menjadi lebih bersih.
Masalahnya adalah meskipun 300 baris kode aslinya jelek, kerumitannya setidaknya tersebar di desktop. Membaca dari atas ke bawah, Anda dapat melihat bagaimana kondisi cabang, status bersama, penanganan pengecualian, dan hasil akhir terhubung.
Setelah kompleksitasnya dipecah, situasinya berubah:
- Anda perlu menelusuri 7 file di sepanjang rantai panggilan untuk mengetahui di mana bidang akhirnya diubah;
- Anda perlu memahami antarmuka, kelas implementasi, logika registrasi, dan perakitan runtime secara bersamaan untuk mengonfirmasi cabang mana yang Anda ambil;
- Di permukaan, tampaknya aturan bisnis ada di dalam kode, dan separuh hasilnya ada di YAML, separuh lagi di database, dan separuh lagi di tabel pemetaan yang dihasilkan saat startup.
Kompleksitasnya tidak berkurang, namun telah berubah dari “sedikit melelahkan saat membaca” menjadi “jauh lebih lambat saat menemukan masalah”.
Biaya pemeliharaan biasanya diselesaikan tiga bulan kemudian ketika seseorang memperbaiki logika yang salah, gagal online, dan memecahkan masalah tautan.
Kesalahan penilaian paling umum yang dilakukan tim adalah kesalahan “kebersihan sebagian” dengan “kemampuan pemeliharaan keseluruhan”
Jenis kesalahan penilaian ini umum terjadi karena banyak manfaat refactoring yang tampak nyata dalam jangka pendek.
Misalnya, fungsi yang berisi beberapa cabang pemrosesan pesanan dapat diubah menjadi struktur berikut:
Handler h = handlerFactory.get(order.type());
h.validate(order);
h.price(order);
h.persist(order);
h.notify(order);
Kode ini tentu saja terlihat lebih bersih daripada daftar cabang yang panjang.
Namun pertanyaan sebenarnya adalah:
- Bagaimana memutuskan implementasi mana yang akan digunakan untuk
handlerFactory; - Apakah ada prasyarat bersama antara
validate/price/persist/notify; - Apakah penyimpangan perilaku diperbolehkan di antara implementasi yang berbeda;
- Apakah perubahan persyaratan tertentu harus dilakukan di satu tempat, empat tempat, atau selusin tempat?
Jika masalah ini tidak dibatasi, maka “struktur elegan” semacam ini sering kali hanya menulis ulang perbedaan bisnis yang awalnya ditulis secara eksplisit di if/else menjadi perbedaan implisit yang tersebar di hierarki kelas.
Dari sudut pandang tinjauan, ini menjadi lebih bersih; dari perspektif pemeliharaan, ini menjadi lebih bergantung pada konteks.
**Kemampuan pemeliharaan mengacu pada apakah keseluruhan sistem lebih mudah untuk menjawab “Di mana pengaruh perubahan ini?” **
Yang paling menentukan biaya pemeliharaan biasanya ada empat hal
Saya lebih suka menggunakan empat pertanyaan berikut untuk menilai apakah pemfaktoran ulang membuat sistem lebih mudah dipelihara.
1. Jika terjadi masalah, apakah jalur lokasinya lebih pendek?
Saat melaporkan masalah secara online bahwa “jenis pesanan tertentu terkadang mengeluarkan kupon duplikat”, hal yang paling berharga adalah apakah teknisi dapat dengan cepat menemukan: di mana kondisi penilaiannya, di mana perlindungan idempotennya, dan di mana efek sampingnya dipicu.
Jika setelah pemisahan, jalur pemecahan masalah berubah dari “melihat fungsi” menjadi “melihat definisi antarmuka, menemukan kelas implementasi, memeriksa perakitan, menelusuri peristiwa, dan membalik konfigurasi”, maka biaya pemeliharaan sebenarnya akan meningkat.
2. Ketika persyaratan berubah, apakah cakupan modifikasinya lebih konvergen?
Abstraksi yang baik membuat perubahan tetap fokus. Abstraksi yang buruk memungkinkan perubahan menyebar.
Jenis pemfaktoran ulang yang paling buruk adalah membagi logika menjadi beberapa tanggung jawab di permukaan. Faktanya, setiap kali persyaratan berubah, persyaratan tersebut harus diubah secara bersamaan: definisi aturan, registrasi pabrik, konfigurasi default, sampel pengujian, dan titik pemantauan. File menjadi lebih kecil, namun area perubahannya menjadi lebih besar.
Sistem seperti ini terlihat modular, namun sebenarnya lebih rapuh, karena setiap kali Anda melakukan perubahan, Anda harus bertaruh bahwa Anda tidak melewatkan satu sudut pun.
3. Apakah kendala menjadi lebih terlihat dan tidak tersembunyi?
Alasan mengapa banyak logika bisnis sulit adalah karena di permukaan sepertinya kodenya jelek, namun sebenarnya lebih mendekati dan memiliki banyak prasyarat:
- Keadaan ini hanya dapat berpindah dari A ke B, tidak langsung ke C;
- Bidang ini hanya dapat diubah oleh jenis pelanggan tertentu;
- Tindakan ini harus berhasil dengan efek samping lain.
Jika setelah pemfaktoran ulang, batasan tersebut tidak lagi muncul di satu tempat, namun tersebar di beberapa kelas, anotasi, konfigurasi, atau pendengar, maka terdapat risiko amnesia.
4. Apakah umpan balik tes mendekati perilaku sebenarnya?
Banyak “optimasi pemeliharaan” yang dengan mudah akan menghasilkan sekumpulan pengujian tunggal yang mudah ditulis karena setiap kelas lebih kecil dan ketergantungannya dihilangkan.
Namun, peningkatan jumlah pengujian tunggal tidak berarti bahwa sistem ini lebih mudah untuk diperbaiki.
Jika pengujian hanya dapat membuktikan “kelas ini akan mengembalikan nilai yang diharapkan di dunia tiruan” tetapi tidak dapat mencakup hubungan perakitan, status bersama, dan batasan waktu dalam proses nyata, maka ini lebih tentang melindungi struktur daripada melindungi perilaku.
Kesalahpahaman umum: Untuk menghilangkan if/else, tulis ulang perbedaan bisnis ke dalam sistem tipe
Tentu saja if/else dapat ditulis dengan buruk, namun “menghilangkan if/else” bukanlah tujuan tersendiri.
Saya telah melihat banyak sistem yang awalnya hanya memiliki dua atau tiga cabang yang jelas dan semantik bisnis yang sangat stabil. Namun, untuk mendapatkan pola desain yang benar, mereka dipecah menjadi antarmuka kebijakan, kelas dasar abstrak, pusat pendaftaran, dan titik ekstensi. Setengah tahun kemudian, jumlah jenis meningkat dari 3 menjadi 9, namun semakin sulit bagi penelepon untuk menilai perbedaan mana yang merupakan perbedaan bisnis nyata dan mana yang hanya perbedaan struktural yang tersisa dari evolusi sejarah.
Dalam banyak kasus, memiliki banyak cabang tidak berarti model objek harus diadopsi; ini hanya berarti ada penilaian bisnis di sini. Hal pertama yang harus dilakukan adalah membedakan penilaian mana yang merupakan sumbu perubahan yang stabil dan mana yang hanya merupakan percabangan bersyarat dalam proses yang sama.
Jika itu hanya beberapa penilaian kondisional dalam suatu proses, maka memaksa mereka untuk menjadi “berorientasi objek” mungkin hanya akan menulis ulang kondisi yang dapat dilihat secara sekilas ke dalam beberapa lapisan pengiriman metode.
**Menyembunyikan kondisi ke dalam polimorfisme tidak akan membuat kondisi tersebut hilang, hanya akan membuat pembaca menyadari keberadaannya di kemudian hari. **
Kesalahpahaman umum lainnya: memperlakukan konfigurasi sebagai tempat sampah yang rumit
Pendekatan lain yang mudah disalahartikan sebagai “lebih mudah dikelola” adalah dengan mengonfigurasi aturan bisnis sebanyak mungkin.
Alasannya biasanya sangat bagus: tidak perlu mengubah kode di masa mendatang, pengoperasian dapat dikonfigurasi, dan perluasan lebih fleksibel.
Namun konfigurasi pada dasarnya tidak lebih murah, ia hanya memindahkan kompleksitas dari waktu kompilasi ke waktu proses.
Ketika konfigurasi aturan mulai mengambil terlalu banyak tanggung jawab, masalah berikut dapat muncul dengan cepat:
- Terdapat hubungan prioritas dan cakupan antar konfigurasi, namun tidak ada tempat dalam sistem di mana konfigurasi tersebut dapat dilihat sepenuhnya;
- Skenario mana yang terkena dampak perubahan hanya dapat diverifikasi secara online;
- Nilai konfigurasi hukum tidak berarti semantik yang benar, kesalahan akan terungkap saat runtime;
- Tinjauan kode menjadi “Saya tidak mengerti apa maksud JSON ini.”
Jika aturan sering berubah, namun perubahan tersebut masih memerlukan penilaian teknik, pengujian linkage, dan rencana rollback, maka pada dasarnya ini adalah masalah kode dan tidak akan tiba-tiba menjadi item pemeliharaan berbiaya rendah hanya karena aturan tersebut ditulis ke dalam konfigurasi.
Kerugian umum dari konfigurasi berlebihan adalah “tidak ada lagi yang berani menyentuh sistem”.
Contoh tandingan: Beberapa abstraksi memang akan membuat sistem lebih mudah dipelihara
Juga tidak dapat dikatakan sebagai “Jangan abstrak, jangan terpecah-belah.”
Ada situasi di mana abstraksi tidak hanya bermanfaat, tetapi juga perlu.
Misalnya:
- Menghadapi sumbu perubahan yang stabil dan jelas, seperti backend penyimpanan yang berbeda, saluran pembayaran yang berbeda, dan protokol serialisasi yang berbeda;
- Perubahan ini benar-benar perlu diganti pada saat runtime, bukan hanya khayalan “kemungkinan perluasan nanti”;
- Setiap implementasi dapat mematuhi serangkaian batasan kuat yang sama, dibandingkan tampak memiliki antarmuka yang sama namun sebenarnya memiliki semantik yang berbeda;
- Batasan tim juga mengikuti batas abstrak, dan modul yang berbeda dapat dikembangkan dan diuji secara mandiri.
Nilai abstraksi pada saat ini adalah bahwa hal itu benar-benar mengurangi gesekan terhadap perubahan di masa depan.
Demikian pula, jika fungsi panjang bertanggung jawab atas verifikasi parameter, pengambilan keputusan bisnis, orkestrasi efek samping, dan kompensasi pengecualian, biasanya tepat untuk membaginya menjadi beberapa langkah dengan batasan yang jelas. Premisnya adalah setelah pembongkaran, tulang punggung proses masih terlihat dan kendala utama tidak disembunyikan.
Jadi pertanyaannya adalah: setelah pembongkaran selesai, kompleksitas tersebut dapat diatasi, atau hanya dipindahkan ke sudut kognitif lain. **
Metode penilaian yang lebih praktis: lihat dulu modifikasi paling umum di masa depan, jangan lihat dulu kebersihan struktural saat ini
Jika saya menduga bahwa “refactor pemeliharaan” hanyalah penyempurnaan struktural, saya biasanya mulai dengan menanyakan tiga pertanyaan yang sangat praktis:
- Jika nanti produk mengubah persyaratan ini, perubahan apa yang paling mungkin dilakukan teknisi?
- Jika terjadi kesalahan saat online di lain waktu, tautan mana yang harus dilihat terlebih dahulu oleh petugas yang bertugas?
- Jika ada orang baru yang mengambil alih, dia perlu memahami terlebih dahulu aturan bisnis atau struktur kerangkanya.
Jika jawaban atas ketiga pertanyaan ini menjadi lebih rumit, kemungkinan besar pemfaktoran ulang ini tidak akan meningkatkan kemampuan pemeliharaan.
Pemeliharaan adalah untuk biaya modifikasi di masa depan, bukan untuk tangkapan layar kode saat ini.
Ringkasan
Alasan mengapa banyak “pengoptimalan pemeliharaan” berbahaya adalah karena di permukaannya terlihat seperti tidak berguna sama sekali, namun kenyataannya lebih mirip karena terlalu mudah untuk terlihat benar sebagian.
Kelasnya lebih banyak, fungsinya menjadi lebih pendek, direktori menjadi lebih rapi, dan proses review menjadi lebih lancar. Namun biaya perawatan sebenarnya berasal dari pemahaman, positioning, modifikasi dan verifikasi, bukan dari kerapian visual.
Jadi saran saya sangat sederhana: **Jangan bongkar logika yang rumit sampai tidak terlihat, bongkar dulu logika yang kompleks itu sampai bisa diubah. **
Jika pemfaktoran ulang hanya memindahkan kompleksitas dari file saat ini ke dalam rantai panggilan, lapisan konfigurasi, dan lapisan abstraksi, biasanya hal ini tidak meningkatkan kemampuan pemeliharaan, namun hanya menunda kesulitan pemecahan masalah berikutnya.
What to read next
Want more posts about Maintainability?
Posts in the same category are usually the best next step for reading more on this topic.
View same categoryWant to keep following #Engineering Practice?
Tags are useful for related tools, specific problems, and similar troubleshooting notes.
View same tagWant to explore another direction?
If you are not sure what to read next, return to the homepage and start from categories, topics, or latest updates.
Back home