Anda baru saja mewarisi codebase C++ lawas, sekarang apa yang harus dilakukan?
(gaultier.github.io)- Jika Anda menangani codebase C++ lawas yang besar dan kompleks, tujuannya bukan “kode yang bersih”, melainkan meningkatkan keamanan, pengalaman developer, ketepatan, dan performa ke tingkat yang dapat diterima
- Titik awalnya adalah mendapatkan kesepakatan organisasi dan batas waktu, menuliskan platform yang didukung di README, lalu memastikan build dan test lokal dapat lolos secara stabil
- Peningkatan kecepatan build·test harus dimulai dari perbaikan berkesulitan rendah yang diukur, seperti menghapus test dependensi, merapikan target yang tidak perlu, atau mencoba linker cepat seperti
mold - Setelah mengurangi kode yang tidak digunakan dan kode untuk platform yang tidak didukung, perlu dibuat loop verifikasi otomatis dengan
clang-tidy,cppcheck,clang-format,-fsanitize=address,undefined, dan CI - Rewrite atau adopsi standar C++ terbaru bukanlah tujuan, melainkan sarana, dan penulisan ulang ke bahasa yang aman terhadap memori juga layak dipertimbangkan hanya jika ada alasan yang sangat kuat
Pada awalnya, rapikan orang dan proses sebelum kode
- Meningkatkan codebase C++ lawas seharusnya bukan pekerjaan yang dipaksakan sendirian sepulang kerja atau “death march” jangka panjang, melainkan pekerjaan rekayasa perangkat lunak yang berkelanjutan
- Anda harus menjelaskan masalah, solusi, dan batas waktu kepada atasan, rekan kerja, dan anggota nonteknis dengan bahasa yang mudah dipahami
- Jika karyawan baru membutuhkan 3 minggu untuk build lokal dan kontribusi pertama, Anda bisa mengusulkan target untuk menurunkannya menjadi beberapa menit
- Jika konfigurasi fuzzing sederhana membuat aplikasi crash 253 kali hanya dalam beberapa detik, itu menjadi dasar untuk menjelaskan risiko di lingkungan produksi
- Jika deployment bergantung pada satu build server FreeBSD 9 yang sudah 8 tahun tidak didukung, maka kegagalan server itu akan memblokir deployment
- Jika linter standar industri langsung menangkap undefined behavior yang menjadi penyebab bug produksi, ada alasan kuat untuk menjalankannya setiap kali ada perubahan
- Jika karena library kriptografi yang disalin dan dimodifikasi secara manual Anda tidak bisa mengetahui dampak suatu kerentanan, maka perapian dependensi dan notifikasi otomatis diperlukan
- Pendekatan yang harus dihindari juga jelas
- Tidak ada test, tetapi melakukan upgrade menyeluruh ke standar C++ terbaru selama 2 minggu
- Membuat perubahan besar di branch terpisah selama berbulan-bulan dan berharap suatu hari bisa di-merge
- Memulai rewrite total dari awal dengan asumsi akan selesai dalam beberapa minggu
- Memulai “perbaikan codebase” tanpa tahu apa yang akan dikerjakan dan sampai kapan
Cara memperbaiki dengan aman
- Semua perubahan harus kecil dan bertahap, dan aplikasi harus tetap berjalan sebelum dan sesudah perubahan, sementara test dan linter tetap lolos
- Perbaikan bug mendesak harus tetap bisa dilakukan seperti biasa, dan tidak boleh terhambat oleh pekerjaan perbaikan
- Setiap perubahan harus merupakan peningkatan yang terukur dan bisa dijelaskan atau didemonstrasikan bahkan kepada orang nonspesialis
- Bahkan jika seluruh pekerjaan dihentikan di tengah jalan karena prioritas atau anggaran, harus tetap ada keuntungan bersih yang terukur dibanding sebelum memulai
Nyatakan platform yang didukung di README
- README harus mencantumkan pasangan
<architecture>-<operating-system>yang didukung secara resmi- Contoh:
x86_64-linux,aarch64-darwin
- Contoh:
- Daftar ini menjadi patokan untuk memeriksa apakah build berhasil di semua platform yang didukung, dan nantinya menjadi dasar untuk menghapus kode untuk platform yang tidak lagi didukung
- Jika perlu, versi arsitektur seperti ARMv6 atau ARMv7 juga bisa dinyatakan
- Daftar platform yang didukung membantu menjawab pertanyaan berikut
- Apakah bisa bergantung pada floating point, SIMD, atau dukungan perangkat keras SHA256
- Apakah dukungan 32-bit masih dibutuhkan
- Apakah dijalankan di platform big-endian
- Apakah perlu mempertimbangkan kemungkinan
charhanya 7-bit
- Workstation developer juga wajib termasuk dalam daftar ini
Stabilkan build dan test lokal terlebih dahulu
- Bahkan codebase C++ inti dari produk yang sukses pun sering kali tidak bisa dikompilasi secara stabil, dan targetnya bukan “build yang kadang berhasil”, melainkan build yang konsisten berhasil di semua platform yang didukung
- Kondisi terbaik adalah bisa membangun dan menjalankan langsung di mesin developer
- Jika proyek terlalu besar dan RAM tidak cukup, Anda bisa menyewa server besar khusus untuk build
- Jika perlu API khusus platform seperti
io_uring, Anda bisa membuat shim atau build di dalam mesin virtual pada workstation - Meski begitu, build lokal langsung tetap pilihan terbaik
- Jika belum ada test, maka test harus ditulis terlebih dahulu sebelum mengubah kode
- Titik mulai termudah adalah membuat test end-to-end dengan menangkap input dan output program yang benar-benar dijalankan di dunia nyata
- Test seperti ini tidak menjamin perilaku lama sudah benar, tetapi membantu mengurangi regresi saat perubahan dilakukan
- Jika sudah ada test suite tetapi sebagian gagal, nonaktifkan dulu yang gagal dan buat kondisi di mana seluruh test suite bisa lolos, meskipun memerlukan waktu berjam-jam
Dokumentasikan cara build dan test
- README harus menjelaskan cara build dan test aplikasi
- Bentuk idealnya adalah satu perintah build dan satu perintah test
- Jika prosedurnya rumit di awal, Anda bisa membungkus perintah-perintah itu di
build.shdantest.shuntuk menyembunyikan kompleksitas - Tujuannya adalah orang yang bukan ahli C++ pun bisa menjalankan build dan test tanpa perlu bertanya
- Dokumentasi struktur proyek atau arsitektur sebaiknya dilakukan nanti setelah kode yang tidak perlu dihapus
Kurangi waktu build dan test dengan perbaikan berkesulitan rendah
- Alih-alih mengganti seluruh sistem build atau melakukan optimasi heroik, mulailah dengan mengukur perbaikan berkesulitan rendah
- Hal-hal yang perlu dicek terlebih dahulu
- Periksa apakah test dari dependensi ikut dibangun dan dijalankan setiap kali
- Ada kasus saat
unittest++digunakan sebagai subproyek CMake, test milik framework test itu sendiri juga ikut dibangun dan dijalankan setiap saat
- Ada kasus saat
- Periksa apakah program contoh dari dependensi ikut dibangun dan dijalankan
mbedtlsjuga memungkinkan build contoh dimatikan lewat variabel CMake
- Periksa apakah proyek Anda, saat dimasukkan sebagai subproyek ke proyek lain, membangun test dan contoh secara default
- Disarankan agar variabel build seperti
MYPROJECT_TESTdinonaktifkan secara default dan hanya diaktifkan saat pengembangan langsung
- Disarankan agar variabel build seperti
- Periksa apakah seluruh dependensi pihak ketiga dibangun padahal yang dipakai hanya sebagian kecil
mbedtlsmenyediakan banyak flag waktu kompilasi untuk mematikan bagian yang tidak dibutuhkan
- Periksa apakah dependensi target salah sehingga perubahan kecil memicu rebuild seluruh proyek
- Banyak sistem build bisa menampilkan graf dependensi
- Cobalah linker cepat seperti
mold - Jika memungkinkan, bandingkan juga compiler
- Pada beberapa proyek, clang 2 kali lebih cepat daripada gcc, sementara pada proyek lain tidak ada perbedaan
- Periksa apakah test dari dependensi ikut dibangun dan dijalankan setiap kali
- Hal lain yang juga bisa dicoba, meski manfaatnya bisa kecil atau bahkan negatif
- Mematikan/mengaktifkan/thin LTO
- Memisahkan informasi debug
- Membandingkan Make dan Ninja
- Jenis dan konfigurasi file system
- Jika build memakan waktu terlalu lama, mengubah kode itu sendiri menjadi tidak realistis
Hapus kode yang tidak perlu
- Ada kasus di mana lebih dari 30% codebase merupakan dead code sepenuhnya, dan kode seperti ini terus menambah biaya kompilasi dan refactoring
- Cara menghapusnya
- Gunakan warning compiler
-Wunused-xxx- Contoh:
-Wunused-function - Dalam banyak kasus cukup hapus lalu build ulang dan jalankan test, tetapi kadang bisa menjadi gejala bug karena fungsi yang salah dipanggil, jadi otomatisasi penuh harus dilakukan dengan hati-hati
- Contoh:
- Gunakan linter seperti
cppcheckuntuk mencari fungsi atau field class yang tidak digunakan- Pada inheritance dan virtual function mungkin ada banyak false positive, tetapi tetap bisa menemukan elemen tak terpakai yang lolos dari compiler
- Ada juga pendekatan di mana linker menaruh tiap fungsi di section terpisah lalu mengeluarkan section yang tidak dipakai, tetapi noise dari fungsi library standar bisa terlalu besar sehingga tidak praktis
- Membandingkan assembly yang dihasilkan dengan source code tidak terlalu cocok untuk virtual function
- Gunakan warning compiler
- Manfaatkan daftar platform yang didukung untuk menghapus kode platform yang tidak didukung
- Kode dukungan Solaris lama pada proyek yang nyatanya hanya berjalan di FreeBSD
- Kode dengan generator bilangan acak buatan sendiri padahal platform nyata selalu punya generator acak
- Kode antisipasi untuk POSIX 2001 yang tidak didukung, padahal program hanya berjalan di Linux dan macOS modern
- Kode yang memeriksa CPU big-endian lalu melakukan byte swap
- Kode yang masuk bertahun-tahun lalu tetapi tidak pernah berujung menjadi fitur nyata
- PR yang menghapus kode dalam jumlah besar bisa sekaligus menunjukkan penurunan waktu build dan penurunan biaya pemeliharaan
Tambahkan linter, formatter, dan sanitizer
- Jangan langsung menyalakan terlalu banyak aturan linter; masukkan dulu beberapa aturan dasar ke dalam siklus hidup pengembangan
clang-tidy,cppcheckbisa berguna, tetapi juga bisa lambat dan banyak noise- Kondisi tanpa linter sama sekali bukan pilihan; saat pertama kali dijalankan, sering kali ia menemukan banyak masalah nyata yang tidak tertangkap hanya oleh warning compiler
- Formatting kode sebaiknya diterapkan sekaligus pada waktu yang tepat
- Tunggu sampai tidak ada branch aktif agar bisa menghindari konflik merge yang mengerikan
- Jangan habiskan waktu untuk debat gaya; format seluruh codebase tanpa pengecualian menggunakan alat seperti
clang-format - Commit juga konfigurasinya
- Sanitizer diperlukan untuk menangkap bug yang sulit ditemukan tetapi bisa berdampak nyata di produksi
- Default yang direkomendasikan adalah
-fsanitize=address,undefined - Umumnya hampir tidak ada false positive, jadi jika terdeteksi harus diperbaiki
- Jalankan test juga dengan sanitizer aktif untuk menemukan masalah
- Jika anggaran performa memungkinkan, Anda juga bisa mempertimbangkan menjalankan produksi tertentu dengan sebagian sanitizer aktif
- Default yang direkomendasikan adalah
- Meskipun compiler untuk deployment tidak mendukung sanitizer, pada pengembangan dan pengujian Anda tetap bisa memakai compiler seperti clang
- Mengaktifkan sanitizer bisa mengungkap bug dan memory leak yang lama tersembunyi, dan perbaikannya mungkin memerlukan banyak kerja serta refactoring
- Jika memungkinkan, saat test, kompilasikan dependensi pihak ketiga juga dengan sanitizer aktif agar bisa menemukan masalah di dalam dependensi
Otomatiskan dengan CI
- CI mengotomatiskan linter, formatting, test, dan hal lain yang sudah disiapkan sejauh ini di lingkungan bersih
- Setiap perubahan harus bisa menghasilkan binary produksi
- Sebagian besar sistem CI mendukung matrix multi-platform, sehingga Anda bisa memeriksa apakah daftar platform yang didukung di README benar-benar bisa di-build
- Pipeline umum bisa sesederhana
make all test lint fmt - Masalah yang dilaporkan linter dan sanitizer harus menyebabkan pipeline gagal; jika tidak, tidak akan ada yang memperbaikinya
Sederhanakan kode secara bertahap
- Setelah ada loop build·test·verifikasi yang stabil, Anda bisa mulai menyederhanakan kode sedikit demi sedikit
- Ada kasus di mana class kompleks ternyata hanya bertugas mengalokasikan pointer dan memeriksa apakah nilainya null, sehingga pada dasarnya bisa diganti dengan boolean
- Pada tahap ini sulit menentukan batas waktu karena semakin disederhanakan, semakin banyak titik lain yang terbuka untuk disederhanakan, jadi perlu penilaian yang konservatif
- Targetnya harus berupa nilai konkret seperti keamanan, ketepatan, atau performa, dan hindari standar subjektif seperti “clean code”
- Upgrade standar C++ bukan tujuan, melainkan sarana
- Misalnya, itu bisa membantu mengubah kode kenaikan iterator manual menjadi loop
for (auto x : items) - Jika yang dibutuhkan hanya
std::clamp, mungkin lebih baik menuliskannya sendiri
- Misalnya, itu bisa membantu mengubah kode kenaikan iterator manual menjadi loop
Penulisan ulang ke bahasa yang aman terhadap memori perlu penilaian terpisah
- Menulis ulang sebagian ke bahasa yang aman terhadap memori adalah pilihan yang mungkin, tetapi ada banyak hal yang perlu diwaspadai
- Ini sebaiknya dilakukan hanya jika ada alasan yang kuat
Manajemen dependensi lebih baik berpusat pada build dari source
- C++ tidak memiliki manajemen dependensi yang konsisten, dan banyak proyek menggunakan package manager sistem
- Masalah dari pendekatan dependensi package sistem
- Instruksi instalasi bergantung pada OS, distribusi, dan versi distribusi
- Saat berpindah dari Ubuntu 20.04 ke 22.04, versi package bisa berubah sehingga Anda mungkin harus meng-upgrade 100 dependensi sekaligus
- Dependensi pihak ketiga yang tidak tersedia sebagai package pada akhirnya tetap harus di-build dari source
- Package belum tentu dibangun dengan flag yang Anda butuhkan
- Sanitizer, LTO,
-march, informasi debug, frame pointer, atau perbedaan ABI C++ bisa menjadi masalah
- Sanitizer, LTO,
- Sulit melihat source dari versi tepat yang sedang dipakai saat audit, pengembangan, atau debugging
- Sulit mem-patch dependensi lalu membangunnya kembali
- Sulit menggunakan versi package yang persis sama di macOS, Ubuntu, dan FreeBSD
- Menjadi sulit membuat BOM otomatis
- Bentuk package yang dibutuhkan, seperti static/dynamic library, mungkin tidak tersedia
- Package manager C++ seperti Conan dan vcpkg juga bisa menjadi perbaikan, tetapi tetap punya batasan
- CI bisa menjadi rumit dan lambat karena dependensi eksternal
- Tidak semua versi package tersedia
- mbedtls di Conan misalnya melompat dari
2.16.12ke2.23.0
- mbedtls di Conan misalnya melompat dari
- Bisa jadi tidak mendukung OS atau arsitektur yang Anda perlukan
- Pendekatan yang direkomendasikan adalah mengambil dependensi dengan git submodule lalu mengompilasikannya dari source
- Sederhana
- Dibanding vendoring manual, Anda bisa memanfaatkan riwayat git dan diff
- Anda bisa mengetahui versi dependensi secara tepat pada tingkat commit
- Upgrade satu dependensi dapat dilakukan dengan
git checkout - Berfungsi di semua platform
- Anda bisa memilih flag kompilasi dan compiler untuk tiap dependensi
- Bahkan developer tanpa pengalaman C++ pun biasanya mengenal git
- Bekerja secara rekursif
- Tiap submodule bisa di-build dengan
add_subdirectorydi CMake ataugit submodule foreach make - Jika submodule terasa sulit, pendekatan seperti Neovim yang memakai satu script untuk mengambil dan membangun dependensi juga memungkinkan
- Jika graf dependensi sangat besar, mungkin dibutuhkan sistem build hibrida lokal·remote seperti Buck2 serta penggunaan ulang artefak build
- Package manager di bahasa kompilasi seperti Go dan Rust mengambil pendekatan build dari source
Hal-hal yang diperkaya dari saran pembaca
- Test perlu lebih ditekankan, dan test suite C++ harus dijalankan di bawah sanitizer agar tidak memberi rasa aman palsu
- vcpkg bisa menjadi pilihan yang lebih baik daripada git submodule jika memenuhi kebutuhan dan cross compilation Anda
- Nix bisa berperan sebagai manajer dependensi C++, tetapi kompleksitas dan kelambatannya menjadi masalah
- Jika Anda hanya memperbaiki satu bug per tahun, investasi refactoring besar menjadi soal penilaian, tetapi penghapusan dead code dan sanitizer tetap bernilai bahkan untuk perbaikan yang jarang
- Penghapusan kode sebaiknya difokuskan pada kasus di mana analisis statis bisa memastikan kode itu tidak pernah dipanggil; jika ragu, lebih aman untuk tidak menghapusnya
- Virtual method menentukan target panggilan saat runtime, sehingga sulit dihapus dengan analisis statis
- Percakapan 15 menit dengan sales, manajer produk, atau pengguna tentang apakah suatu fitur atau platform benar-benar dipakai bisa sangat mengurangi pekerjaan teknis
- Memasukkan kode ke LLM lalu mengajukan pertanyaan bisa legal dan berguna jika aman, sebaiknya dijalankan secara lokal jika memungkinkan, dan hasilnya harus diterima dengan hati-hati
- Ada juga gagasan memakai alat analisis kode untuk membuat diagram dan hubungan antarkelas agar struktur keseluruhan lebih mudah dipahami
- Jika belum ada source control system, maka tahap 0 adalah memasukkan kode ke VCS
- Ada juga pendapat bahwa CI harus dianggap sebagai tahap 1; ini sudut pandang yang valid meski lokal lebih cepat
- Menghapus platform yang hampir tidak pernah digunakan bisa mengurangi kompleksitas kombinatorial dan memungkinkan penyederhanaan besar
- Build yang sepenuhnya reproducible mungkin tidak realistis untuk codebase C++ pada umumnya, tetapi build yang dapat dipercaya tetap realistis
- Commit formatting seluruh kode bisa diimbangi dengan mengatur git agar mengabaikan commit tertentu saat blame, sehingga beban penelusuran riwayat berkurang
- Ada pula ide memakai statistik riwayat VCS untuk mencari bagian yang sering berubah dan file yang sering berubah bersama
- Pendekatan ini tidak hanya berlaku untuk C++, tetapi juga untuk codebase lawas lain; cukup kecualikan bagian yang spesifik C++ seperti sanitizer
Working effectively with Legacy Codedirekomendasikan sebagai buku yang berisi saran terkait- Fokus harus tetap pada nilai nyata, tetapi dari sudut pandang keamanan, codebase C++ besar bisa menyimpan banyak kerentanan, dan itu merupakan nilai sebagai pengurangan risiko, bukan semata keuntungan finansial
1 komentar
Opini Hacker News
Ada saran yang bagus, dan ada juga saran yang agak kontroversial. Setelah mewarisi beberapa proyek C++ raksasa, beberapa pekerjaan yang jelas memberi keuntungan besar di awal tampaknya adalah ini:
Mulailah dengan membuat build yang dapat direproduksi, lalu bungkus lingkungan build dengan Docker atau alat packaging pilihan agar tool dan dependensi menjadi eksplisit serta dapat direproduksi
Membuatnya bisa di-build tanpa warning dengan
-Wallakan mengungkap kode buruk, perilaku tak terdefinisi, dan bug; setelah itu, saat saya memasukkan kode yang mencurigakan, saya bisa langsung melihat warningMemeriksa error baca/tulis sejak awal dengan tool seperti
valgrindjuga merupakan keuntungan mudah dari sisi stabilitas, dan sebelum memahami keseluruhan struktur, lebih aman menjaga refactoring tetap lokalDockerfile yang menunjuk tag image yang berubah tanpa hash integritas, atau memanggil
apt-get, itu umum; keduanya menyisakan banyak ruang untuk berakhir pada keadaan yang sedikit berbedaSolusi seperti Bazel atau Nix memang lebih sulit dipelajari, tetapi memberi fondasi yang jauh lebih baik
Tahap 2 adalah menjalankan semua test di bawah sanitizer untuk menandai item yang gagal, tahap 3 adalah memperbaiki semua kegagalan sanitizer itu, dan tahap 4 adalah pekerjaan lainnya
Container itu bagus, tetapi buruk sekali jika dipakai sebagai cara berpura-pura bahwa tidak ada masalah
Semua dependensi harus dibundel, dan hindari keadaan yang hanya bisa berjalan jika miliaran hal di
/usr/libterpasang dengan versi yang tepat-Wallitu baik, tetapi saya sangat menentang memasukkan-Wall -Werrorke build produksiSebagian warning compiler lebih mirip opini, dan ketika versi compiler baru menambahkan warning baru, kode yang sebelumnya bersih bisa tiba-tiba ditolak
Jika
-Werrordiperlukan, lebih baik pakai hanya pada debug buildSaya akan menukar urutan nomor 2 dan 3. Menyiapkan CI, linting, auto-formatting, dan sejenisnya lebih prioritas daripada membongkar sesuatu
Karena kita belum tahu apa yang harus dihapus, atau apa akibatnya jika dihapus
Linter dan tool analisis statis memberi banyak insight tentang bagian program mana yang perlu dikerjakan
Bagian yang hari ini ditandai tool analisis statis sering kali nantinya menjadi tempat untuk menghapus fungsi, class, atau bahkan seluruh file yang mengimplementasikan ulang konsep STL
Library iterator buatan sendiri, smart pointer buatan sendiri, penggunaan fungsi string C, dan sebagainya bisa diganti dengan algoritma STL, smart pointer sungguhan, dan class string C++
Namun sebelum memindai kode, hal-hal itu sulit terlihat; dan sebelum mendorong ke arah build test cepat atau deployment, hasilnya juga sulit dievaluasi
Untuk bagian yang kosong, saya menambahkan smoke test dasar
Pekerjaan ini bukan membuat lambat, justru mempercepat. Setelah coverage yang memadai tersedia, kita bisa bergerak jauh lebih cepat saat refactoring, jadi imbalannya besar untuk investasi kecil
Pada proyek C++ besar, selain skrip code generation, biasanya juga ada skrip yang mem-parsing kode untuk mengumpulkan data bagi code generation
Auto-formatting bisa merusaknya, dan saya bahkan pernah melihat proyek terkutuk di mana pengguna mem-parsing file header publik dengan skrip yang rapuh
Kemungkinan besar Anda akan menyentuh banyak tempat, dan ketika perubahan lokal menumpuk, Anda bisa tanpa sengaja membuat commit dengan concern yang tercampur
Saat itu CI menjadi penyelamat
Jika mengikuti jalan itu, saya menyarankan setidaknya jangan langsung menghapusnya; komentari dulu agar diff tetap sederhana sampai branch siap di-squash lalu di-merge
Tool dan teknik untuk memahami kode belum dibahas. Dulu saya memakai tool bernama Source Navigator yang ditulis dengan Tcl/Tk, dan itu sangat bagus untuk mengindeks codebase
Kita bisa melihat hierarki pemanggilan dari method saat ini, lalu menggunakannya untuk membuat diagram sequence UML
Ada juga tool serupa, Source Insight, di bawah ini
Dan catatan itu penting. Kuncinya adalah menulis seolah sedang mengajari seseorang
Selama bertahun-tahun saya menjadi cukup baik dalam memahami kode, dan pada suatu masa saya sendirian secara aktif mendukung serta mengembangkan codebase trading algoritmik Java yang memperdagangkan sekitar 200 juta dolar per hari di 4–5 bursa
Dokumentasi untuk kode itu mencapai 35 MB, dan kalau mengesampingkan risiko personel kunci, tanggung jawabnya terasa bagus
Sejujurnya, sebagian besar codebase besar punya terlalu banyak over-engineering dan duplikasi
[1] References in "Source Insight" https://d4.alternativeto.net/6S4rr6_0rutCUWnpHNhVq7HMs8GTBs6...
Di kelas arsitektur komputer universitas, kami dipaksa memakai artefak ini, karena ia punya kumpulan fitur aneh yang tidak ada di tempat lain serta dukungan emulator ARM
Kami memakainya untuk pemrograman assembly ARM bare-metal
Pada bagian yang mengatakan bahwa memasukkan
std::cmakeke pustaka standar akan menjadi game changer, rasanya perut saya langsung mualSaya paham nasihat untuk membongkar habis dengan gergaji mesin apa pun yang tidak mutlak diperlukan untuk menyediakan fitur yang diiklankan dan dijual oleh perusahaan atau proyek open source, tetapi itu sangat berbahaya
Pada dasarnya ini adalah masalah pagar Chesterton, jadi kalau tidak sepenuhnya memahami mengapa sesuatu masuk ke perangkat lunak dan bagaimana perangkat lunak itu saat ini digunakan, kita tidak bisa menghapusnya
Dalam skenario terburuk, ketika rilis sekitar sebulan kemudian, pengguna menemukan bahwa fitur penting rusak secara halus, lalu Anda menghabiskan berhari-hari melacak persis bagaimana kerusakannya
Menambahkan CI, linter, fuzzing, pemformatan otomatis, dan sebagainya adalah ide bagus, tetapi juga sulit
Kalau satu orang terutama memakai VIM, satu lagi emacs, yang lain QTCreator, dan yang lain lagi VSCode, sangat sulit membuat semua orang berada di halaman yang sama
Jika pemasangan alat baru adalah langkah opsional yang diperlukan, dalam praktiknya itu tidak akan terjadi, dan linter juga tidak membantu kalau begitu proyek dibuka langsung muncul lebih dari 2000 peringatan
cpplintatauclang-tidymaupun fuzzer terintegrasi ke IDE, dan semuanya terlalu lambat untuk dijalankan otomatis pada setiap ketikanHanya pemformatan otomatis yang kadang terintegrasi
Hal-hal seperti ini bisa dilakukan dari command line, terlepas dari lingkungan pengembangan masing-masing, jadi jangan menyerah sejak awal hanya karena ada dua editor teks yang dipakai; setidaknya cobalah
Kalau dalam C++ tim tidak mau memasang alat apa pun, jalan penderitaan terbuka lebar. Membuat alat-alat itu dalam container agar mudah digunakan juga layak dipertimbangkan
Kita harus bekerja secara profesional, dan kalau pekerjaan menuntut IDE sampai toolchain tertentu disiapkan, kita harus mempelajari dan memakainya
Sesuatu yang dibangun dan berjalan di komputer saya, IDE saya, dengan cara favorit saya, lebih mirip kerajinan tangan daripada perangkat lunak
Meski pemasangan hook lokal bersifat opsional, kalau PR dibuat gagal di CI, pada akhirnya orang akan melakukannya
Ini benar-benar hal dasar, tetapi tampaknya ada kesenjangan pengetahuan yang cukup besar dalam menangani CI dan infrastruktur dengan benar
Pengembang harus bisa menjalankan pengujian, lint, fuzzing, pemformatan, validasi format YAML, pemeriksaan baris baru EOF yang hilang, dan sebagainya secara lokal agar terbantu mencegah kegagalan CI sebelum push
Jika linter mengeluarkan ribuan peringatan saat proyek dibuka, pengembang yang menambahkan linter harus memastikan peringatannya menjadi 0 sebelum perubahan itu digabungkan
Ini bisa dicapai dengan menonaktifkan peringatan atau file tertentu, memperbaiki sebagian, atau kombinasi keduanya
Menurut saya langkah pertama adalah menghubungi maintainer sebelumnya, menemui mereka, membelikan teh atau bir, dan pada akhirnya membicarakan codebase itu
Para penyihir yang disebut mantan maintainer bisa memberi tahu banyak hal
Saran-saran lainnya, misalnya membuatnya berjalan di berbagai platform atau membuat pengujian lolos, menurut saya adalah stress test yang bagus menuju kekokohan dan pemahaman
Meski begitu, saya pasti akan lebih dulu memetik buah yang rendah: berbicara dengan orang-orang yang pernah melewatinya
Jauh lebih produktif jika Anda lebih dulu mencoba mengerjakan sendiri sedikit, tersangkut di beberapa tempat, lalu berbicara dengan maintainer sebelumnya
Mereka juga akan menghargai usaha itu
Jika Anda mewarisi codebase tanpa pengujian, build gagal satu dari dua kali, informasi dependensi tidak jelas, dan hanya bisa dibangun di satu server dengan OS yang sangat tua, belum tentu maintainer sebelumnya benar-benar penyihir
Perlu juga dipertimbangkan apakah semua masalah itu muncul karena kurang waktu, atau karena “penyihir” yang sengaja membiarkannya rusak demi keamanan kerja atau karena tidak mau mempelajari hal baru
Itu skenario terbaik; yang lebih umum adalah setiap pertanyaan hanya dijawab, “waktu saya menerimanya juga sudah begitu”
Saat bekerja, saya menemui beberapa bug dan masalah, lalu mencoba menghubungi para pengembang yang menulis perangkat lunak khusus untuk pemberi kerja saya itu
Ternyata perangkat lunak itu ditulis oleh satu kontraktor, dan ia sudah meninggal beberapa tahun sebelumnya
Di industri pertahanan, hal seperti ini umum. Banyak barang sekali pakai kustom yang dibuat untuk sistem tertentu, dan terutama di sisi hardware, tidak jarang insinyur yang membuat peralatan itu sudah lama pergi atau pensiun
Ini sangat membantu
Codebase C++ yang punya pengujian? Menurut saya itu optimistis
Saya kurang paham kenapa fokusnya begitu besar pada refactoring atau perbaikan. Kalau fitur bisa ditambahkan dengan menempelkannya ke kode, lakukan saja begitu tanpa menyentuh yang lain
Kalau perubahannya cukup besar, keluarkan hal yang diperlukan dari kode legacy lewat pemanggilan fungsi eksternal, pengenalan lapisan jaringan, atau memisahkan kode yang sama menjadi library, lalu kerjakan sisanya di lingkungan baru
Saya tidak akan mencoba refactoring besar kecuali ke depannya banyak orang harus mengerjakan kode itu dan grup tersebut membutuhkan asumsi serta standar tertentu agar mudah berkolaborasi
Pendekatan menempelkan tambahan memang berhasil sampai titik tertentu, tetapi suatu saat perubahan akan tiba-tiba merusak sesuatu, dan karena sudah terlalu berantakan, penyebabnya butuh waktu jauh lebih lama dari seharusnya untuk ditemukan
Inti tulisan aslinya lebih dekat ke: hindari penulisan ulang besar, tetapi lakukan beres-beres agar kohesi tetap terjaga dan perubahan lebih mudah dilakukan
Kadang fitur yang sama harus ditambahkan ke kedua versi
Cara memperlakukan kode yang Anda tahu rilis terakhirnya akan segera datang sangat berbeda dari kode yang akan dipelihara dan ditambahi fitur selama puluhan tahun ke depan
Dalam kenyataan, fitur baru sering kali merupakan perubahan terhadap perilaku yang sudah ada, dan tiba-tiba Anda harus melakukan refactoring berat di banyak tempat
Ini thread dengan banyak saran bagus. Sebagai tambahan yang tidak terbatas pada C/C++, kalau Anda punya kelonggaran untuk memakai sistem kontrol versi, manfaatkan nilainya sepenuhnya
Banyak tim hanya memakainya sebagai alat kolaborasi sederhana, padahal bisa lebih dari itu
Ambil riwayatnya dan buat basis data sederhana. Tidak harus RDB; bisa mulai dari file JSON atau spreadsheet
Dengan pendekatan berbasis data saja, Anda hampir langsung bisa mengekstrak banyak informasi berguna
File dan fungsi yang sering berubah kemungkinan besar akan menjadi hotspot pekerjaan ke depan, jadi jika ingin memperkenalkan unit test atau mengurangi konflik merge, fokuslah ke sana
Jika file-file yang tampaknya saling jauh sering berubah bersama, itu bisa mengisyaratkan struktur implisit yang tidak terlihat hanya dari membaca kode
Model kepemilikan nyata tiap modul juga bisa disimpulkan dari riwayat, dan kepemilikan yang tidak jelas bisa menjadi sinyal perlunya refactoring
Di C/C++, peningkatan waktu build juga perlu difokuskan pada modul penting secara berbasis data. Daripada sembarang menghapus dependensi file acak, pecah modul yang sering berubah, lalu gabungkan dengan dependensi header untuk memberi skor dampak waktu build yang nyata
Jika VCS diintegrasikan dengan alat pengembangan lain, lebih banyak hal bisa dilakukan, dan di era LLM, sepertinya kita juga bisa memasukkan riwayat proyek serta metadata ke model lalu menanyakan insight yang menarik
Untuk melakukannya tanpa jendela konteks raksasa mungkin diperlukan rekayasa model khusus, tetapi saya merasa itu layak dicoba
Saran untuk menambahkan CI, linter, fuzzing, pemformatan otomatis, dan sebagainya perlu diuraikan lebih lanjut
CI harus memastikan kode bisa dibuild bukan hanya di komputer saya, tetapi juga di tempat lain, sehingga mencegah regresi berbasis kompilasi
Peringatan compiler dan analyzer statis umumnya lebih pintar daripada saya, jadi jika ada peringatan menakutkan bahwa pointer melakukan hal aneh, itu sinyal kuat untuk diperiksa
Unit test harus memverifikasi bahwa kode penting benar-benar melakukan hal yang diharapkan sampai level rendah, dan karena besar kemungkinan kenyataannya tidak demikian, Anda perlu memahami alasannya
Jika Anda memperbaiki sesuatu, hal lain bisa pecah, karena kode lama mungkin ditulis dengan mengasumsikan perilaku yang sebenarnya bug
Pemformatan otomatis bukan prioritas, dan menurut saya lebih baik mengikuti gaya maintainer yang sudah ada
Gagasan bahwa tahap terakhir dari mewarisi codebase C++ adalah menulis ulang dengan bahasa memory-safe juga kemungkinan besar kurang cocok
Sulit mendapatkan sumber daya kerja tambahan untuk sesuatu yang tidak rusak, Anda jadi membutuhkan pengetahuan bukan hanya C++ tetapi juga bahasa tambahan, dan pengujian bisa menjadi lebih rumit
Ada kemungkinan besar kode itu juga tidak cocok ditulis dalam beberapa bahasa karena batasan memori atau performa, dan fakta bahwa Anda mewarisi codebase legacy sejak awal hampir merupakan pengakuan bahwa sumber daya seperti waktu, uang, dan pengetahuan tidak cukup untuk menulisnya ulang
“Tulis ulang dengan X” hanya menambah kompleksitas karena sedang tren
Jika Anda memang sudah menulis ulang bagian besar codebase dalam C++, lebih baik ikuti subset C++ yang lebih terbatas, dan menurut saya High Integrity C++ cukup bagus
Jika bisa mendapatkan standar MISRA terbaru, itu juga kemungkinan besar baik
Daripada membuat seluruh tim mempelajari lagi sisi-sisi tajam bahasa baru, lebih baik memakai bahasa yang sudah dikenal tetapi memaksakan pedoman untuk menghindari jebakan yang sudah diketahui
Aneh bahwa setelah banyak mengkritik otomatisasi BOM, manajemen versi paket, asal-usul dependensi, dan lain-lain, penulis menyarankan git submodules sebagai pilihan yang lebih baik daripada package manager
Sebelum melontarkan kritik seperti ini, sebaiknya coba vcpkg dulu
Memang ada sedikit bagian yang tajam, tetapi hampir semuanya bisa dipenuhi secara intuitif dengan vcpkg
Pembaruan dependensi sedikit lebih sulit daripada git submodules, tetapi menurut saya ini lebih mirip fitur daripada bug. Dependensi dibuild di sandbox masing-masing lalu diinstal ke direktori yang ditentukan
vcpkg bisa mempertahankan sifat vendored-in dengan menetapkan repositori internal sebagai registry alih-alih repositori resmi, melakukan chainload toolchain agar semuanya dikompilasi dengan set flag tetap, serta mengizinkan kustomisasi per port
Abstraksi seperti inilah yang membuat package manager populer, dan tidak semua orang perlu menangani sendiri string tak berujung berisi flag kompilasi, makro, peringatan, dan sebagainya
Artikelnya menarik dan ada hal yang bisa dipelajari. Namun saya penasaran bahasa apa yang dimaksud orang-orang saat mengatakan “menulis ulang dengan bahasa yang aman terhadap memori”
Apakah maksudnya menulis ulang sebagian dengan Go, Java, C#, atau sindiran semacam menulis ulang dengan Rust sambil tetap menyisakan kemungkinan untuk menyangkal?
Kesimpulannya sepenuhnya bergantung pada tim dan batasannya. Misalnya, apakah garbage collection memungkinkan, jika ya apakah Go pilihan yang baik, apakah keamanan adalah prioritas utama, dan sebagainya
Sebagian besar developer C++ pada umumnya bisa memakai Rust dengan mudah dan memperoleh performa yang setara, menurut saya
Namun ada juga kasus ketika sejak awal tidak ada alasan kuat proyek itu harus memakai C++, dan saya juga pernah melihat kasus penulisan ulang yang berhasil dengan Java
Apple sedang menulis ulang sebagian kode C++ ke Swift
Bahasa yang bisa ditangani dengan nyaman oleh tim atau perusahaan adalah patokan praktis yang baik
Sekarang kita sampai pada tahap mencurigai penulis sebagai programmer Rust terselubung yang jahat. Mungkin karena Rust memang tidak muncul sejak awal, jadi tidak bisa dikeluhkan secara langsung