1 poin oleh GN⁺ 2024-03-01 | 1 komentar | Bagikan ke WhatsApp
  • 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
  • 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 char hanya 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.sh dan test.sh untuk 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
    • Periksa apakah program contoh dari dependensi ikut dibangun dan dijalankan
      • mbedtls juga 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_TEST dinonaktifkan secara default dan hanya diaktifkan saat pengembangan langsung
    • Periksa apakah seluruh dependensi pihak ketiga dibangun padahal yang dipakai hanya sebagian kecil
      • mbedtls menyediakan 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
  • 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
    • Gunakan linter seperti cppcheck untuk 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
  • 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, cppcheck bisa 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
  • 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

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
    • 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
    • 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_subdirectory di CMake atau git 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 Code direkomendasikan 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

 
GN⁺ 2024-03-01
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 -Wall akan mengungkap kode buruk, perilaku tak terdefinisi, dan bug; setelah itu, saat saya memasukkan kode yang mencurigakan, saya bisa langsung melihat warning
    Memeriksa error baca/tulis sejak awal dengan tool seperti valgrind juga merupakan keuntungan mudah dari sisi stabilitas, dan sebelum memahami keseluruhan struktur, lebih aman menjaga refactoring tetap lokal

    • Jika menginginkan eksplisitas dan reproduksibilitas, sebaiknya hindari memulai dari Docker. Jika tidak sangat diperhatikan, Docker hanya memberi bentuk reproduksibilitas yang paling encer, dan justru bisa memberi ilusi aman
      Dockerfile 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 berbeda
      Solusi seperti Bazel atau Nix memang lebih sulit dipelajari, tetapi memberi fondasi yang jauh lebih baik
    • Tahap 0 adalah build yang dapat direproduksi, dan tahap 1 adalah menjalankan semua test untuk menandai test yang tidak stabil
      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
    • Docker cukup oke untuk microservice, tetapi tidak selalu semua hal bisa di-deploy dengan Docker. Terutama jika ada orang yang ingin memakai aplikasi di dalam Docker; Docker-in-Docker adalah situasi yang seharusnya tidak ada
      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/lib terpasang dengan versi yang tepat
    • Build yang bersih dengan -Wall itu baik, tetapi saya sangat menentang memasukkan -Wall -Werror ke build produksi
      Sebagian warning compiler lebih mirip opini, dan ketika versi compiler baru menambahkan warning baru, kode yang sebelumnya bersih bisa tiba-tiba ditolak
      Jika -Werror diperlukan, lebih baik pakai hanya pada debug build
  • Saya 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

    • Saya punya cukup banyak pengalaman bekerja di agensi dan masuk untuk menyelamatkan codebase; hal pertama yang saya lakukan adalah menjalankan unit test dan memeriksa coverage
      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
    • Sebaliknya, auto-formatting merusak riwayat versi, dan membuat analisis kapan serta mengapa baris ini ditambahkan menjadi sulit
    • Saya sama sekali tidak menyarankan menerapkan auto-formatting pada codebase legacy
      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
    • Di sini CI berbeda dari item lainnya. Langkah pertama adalah membuat test harness jalur normal yang setidaknya berjalan dengan hasil yang dapat direproduksi dan berjalan pada setiap commit; ini juga membantu memahami codebase
      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
    • Secara rinci, cakupan yang di artikel asli disarankan untuk dibongkar terbatas pada dead code yang dipandu oleh warning compiler dan arsitektur yang tidak didukung
      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...

    • Sulit dipercaya bertemu seseorang di dunia nyata yang benar-benar pernah memakai Source Navigator
      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::cmake ke pustaka standar akan menjadi game changer, rasanya perut saya langsung mual
    Saya 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

    • Hal-hal yang dicantumkan penulis biasanya bukan integrasi IDE. Di lingkungan pengembangan C++, saya belum pernah melihat cpplint atau clang-tidy maupun fuzzer terintegrasi ke IDE, dan semuanya terlalu lambat untuk dijalankan otomatis pada setiap ketikan
      Hanya 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
    • Menurut saya, alasan bahwa satu orang memakai VIM, yang lain emacs, QTCreator, dan VSCode sudah bukan jawaban yang dapat diterima
      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
    • Pemformatan otomatis juga bisa dilakukan di luar editor. Siapkan pre-commit dan jalankan di CI; itu hal sepele
      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
    • Sepertinya Anda melewatkan memo bahwa boleh saja menolak begitu saja persyaratan dasar yang diminta atasan. Orang yang membayar gaji Anda itu, maksudnya
    • Hook pre-commit yang bersifat opsional di lokal harus ditopang oleh langkah wajib di CI
      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

    • Saya tidak akan menjadikan itu langkah pertama. Kalau begitu, kemungkinan besar Anda hanya membuang-buang waktu mereka
      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
    • Mungkin lebih baik menelusuri codebase secara cepat lebih dulu untuk menemukan WTF terbesar, baru kemudian bertanya
      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
    • Kalau pernah bertanya kepada maintainer sebelumnya tentang kode legacy, Anda akan tahu bahwa meskipun serah terimanya baru kemarin, mereka hampir tidak bisa memberi tahu hal berguna yang mereka lakukan lebih dari 6 bulan lalu
      Itu skenario terbaik; yang lebih umum adalah setiap pertanyaan hanya dijawab, “waktu saya menerimanya juga sudah begitu”
    • Saya pernah bertugas menerapkan perangkat lunak di jaringan tertutup militer di atas OS kustom lama. Programnya tidak besar, sekitar 50 ribu baris
      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
    • Tidak semudah kedengarannya. Langkah ke-0 saya adalah memasukkannya ke alat UML untuk menghasilkan diagram kelas dan diagram lain
      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

    • Tulisan aslinya menentang refactoring besar. Pekerjaan bertahap yang disarankan arahnya membuat kode semakin mudah ditangani
      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
    • Jawabannya bergantung pada masa depan. Saya pernah mengerjakan kode C++ yang penggantinya sudah ada di pasar, tetapi kode versi lama masih harus dirilis beberapa kali lagi
      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 beberapa kasus yang beruntung, fitur bisa langsung ditempelkan begitu saja
      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

    • Saya penasaran apakah ada tips software yang membantu mengotomatiskan analisis semacam ini
  • 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

    • Sebenarnya, dengan menyingkirkan C++ yang tidak memory-safe dan memaksakan pedoman saja, tujuan ini bisa dicapai di dalam codebase C++
      “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?

    • Sebagai penulis, topik ini akan saya bahas di artikel kedua
      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
    • Ini artikel tentang C++, kata “Rust” bahkan tidak muncul sekali pun, dan yang disebut hanya bahasa “memory safe” yang jumlahnya puluhan, tetapi menarik bagaimana meme Rust tetap diselipkan untuk merendahkannya
      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