Az önce devraldığınız eski C++ kod tabanı; şimdi ne yapmalı?
(gaultier.github.io)- Büyük ve karmaşık bir eski C++ kod tabanı devraldıysanız hedef “temiz kod” değil; güvenliği, geliştirici deneyimini, doğruluğu ve performansı kabul edilebilir bir seviyeye çıkarmaktır
- Başlangıç noktası, kurum içinde mutabakat ve zaman sınırı sağlamak; desteklenen platformları README’ye yazmak; ardından yerel derleme ve testlerin kararlı biçimde geçmesini sağlamaktır
- Derleme ve test hızını iyileştirirken bağımlılık testlerini kaldırma, gereksiz hedefleri temizleme,
moldgibi hızlı linker’ları deneme gibi düşük zorluklu iyileştirmelerden başlayıp ölçüm yaparak ilerlemek gerekir - Kullanılmayan kodu ve desteklenmeyen platform kodunu azalttıktan sonra
clang-tidy,cppcheck,clang-format,-fsanitize=address,undefinedve CI ile otomatik doğrulama döngüsü oluşturulmalıdır - Yeniden yazma ya da modern C++ standardına geçiş amaç değil araçtır; bellek güvenli bir dilde yeniden yazma da yalnızca güçlü bir gerekçe olduğunda değerlendirilmeye değerdir
Önce koddan çok insanları ve süreçleri düzene koyun
- Eski bir C++ kod tabanını iyileştirmek, mesai sonrası tek başınıza zorlayarak yaptığınız bir iş ya da uzun soluklu bir “ölüm yürüyüşü” değil; sürdürülebilir bir yazılım mühendisliği işi olmalıdır
- Yöneticilere, ekip arkadaşlarına ve teknik olmayan paydaşlara sorunu, çözümü ve zaman sınırını sade bir dille anlatmalısınız
- Yeni bir çalışanın yerel derleme yapıp ilk katkısını göndermesi 3 hafta sürüyorsa, bunu birkaç dakikaya indirmeyi hedef olarak önerebilirsiniz
- Basit bir fuzzing kurulumu uygulamayı birkaç saniyede 253 kez çökertiyorsa, bu üretim ortamındaki riski açıklamak için dayanak olur
- Dağıtım 8 yıldır desteklenmeyen tek bir FreeBSD 9 derleme sunucusuna bağlıysa, sunucu arızalandığında dağıtımın duracağı anlamına gelir
- Sektör standardı bir linter, üretim hatasına neden olan tanımsız davranışı hemen yakalıyorsa, her değişiklikte çalıştırmak için yeterli sebep vardır
- Elle kopyalanıp değiştirilmiş bir kriptografi kütüphanesi yüzünden güvenlik açıklarından etkilenip etkilenmediğinizi bilemiyorsanız, bağımlılıkların düzenlenmesi ve otomatik bildirimler gerekir
- Kaçınılması gereken yaklaşımlar da nettir
- Test yokken 2 hafta boyunca en yeni C++ standardına tam yükseltme yapmak
- Aylarca ayrı bir dalda büyük değişiklikler hazırlayıp bir gün birleşeceğini ummak
- Birkaç haftada biter varsayımıyla en baştan tüm sistemi yeniden yazmaya girişmek
- Ne yapılacağı ve ne zamana kadar yapılacağı belirsizken “kod tabanını iyileştirme” işine başlamak
Güvenli iyileştirme yöntemi
- Tüm değişiklikler küçük ve kademeli olmalı; değişiklikten önce ve sonra uygulama çalışmalı, testler ve linter’lar geçmelidir
- Acil hata düzeltmeleri eskisi gibi mümkün olmalı; iyileştirme çalışması bunları engellememelidir
- Her değişiklik ölçülebilir bir iyileştirme olmalı ve uzman olmayan kişilere de açıklanabilir ya da gösterilebilir olmalıdır
- Öncelik veya bütçe nedeniyle tüm çalışma durdurulsa bile, başlangıca kıyasla ölçülebilir net bir kazanç kalmalıdır
Desteklenen platformları README’de belirtin
- README’de resmen desteklenen
<architecture>-<operating-system>çiftleri yazılmalıdır- Örnek:
x86_64-linux,aarch64-darwin
- Örnek:
- Bu liste, tüm desteklenen platformlarda derlenip derlenmediğini kontrol etmenin ölçütüdür ve ileride desteklenmeyen platformlara ait kodu kaldırmak için dayanak olur
- Gerekirse ARMv6, ARMv7 gibi mimari sürümleri de belirtilebilir
- Desteklenen platform listesi şu sorulara yanıt verir
- Kayan nokta, SIMD, SHA256 donanım desteğine güvenilip güvenilemeyeceği
- 32 bit desteğinin gerekli olup olmadığı
- Big-endian platformlarda çalışıp çalışmadığı
char’ın 7 bit olma olasılığının dikkate alınıp alınmayacağı
- Geliştirici iş istasyonları da mutlaka bu listeye dahil edilmelidir
Önce yerel derleme ve testleri kararlı hale getirin
- Başarılı bir ürünün çekirdek C++ kod tabanı bile çoğu zaman kararlı biçimde derlenmez; hedef “ara sıra çalışan derleme” değil, tüm desteklenen platformlarda tutarlı çalışan derleme olmalıdır
- En iyi durum, geliştirici makinesinde doğrudan derleyip çalıştırabilmektir
- Proje RAM’in yetmeyeceği kadar büyükse derleme için büyük bir sunucu kiralanabilir
io_uringgibi platforma özgü API’ler gerekiyorsa bir shim uygulanabilir ya da iş istasyonundaki sanal makine içinde derleme yapılabilir- Yine de doğrudan yerel derleme en iyi seçenektir
- Test yoksa kodu değiştirmeden önce test yazılmalıdır
- Gerçekte çalışan programın girdilerini ve çıktılarını yakalayıp uçtan uca testler oluşturmak en kolay başlangıçtır
- Bu testler mevcut davranışın doğru olduğunu garanti etmez, ancak değişikliklerde regresyonları azaltır
- Test paketi varsa ama bazı testler başarısız oluyorsa bunları şimdilik devre dışı bırakıp, tüm testler saatler sürse bile geçen bir duruma getirmek gerekir
Derleme ve test yöntemini belgelendirin
- README’de uygulamanın nasıl derleneceği ve test edileceği yazılmalıdır
- İdeal biçim, tek bir derleme komutu ve tek bir test komutudur
- Başta süreç karmaşıksa komutları
build.sh,test.shiçine sararak karmaşıklık gizlenebilir - Hedef, C++ uzmanı olmayan birinin bile soru sormadan derleme ve test çalıştırabileceği durumdur
- Proje yapısını veya mimarisini belgelendirmeyi, gereksiz kodları kaldırdıktan sonraya bırakmak daha iyidir
Düşük zorlukla derleme ve test süresini azaltın
- Derleme sistemini baştan aşağı değiştirmeden veya kahramanca optimizasyonlara girişmeden, önce düşük zorluklu iyileştirmeler ölçülmelidir
- Önce kontrol edilecekler
- Bağımlılıkların testlerinin her seferinde derlenip çalıştırılıp çalıştırılmadığını kontrol edin
unittest++CMake alt projesi olarak kullanıldığında test çatısının kendi testlerinin bile her seferinde derlenip çalıştırıldığı bir örnek vardır
- Bağımlılıkların örnek programlarının derlenip çalıştırılıp çalıştırılmadığını kontrol edin
mbedtlsde CMake değişkeniyle örnek derlemelerini kapatabiliyordu
- Kendi projeniz başka bir projenin alt projesi olarak dahil edildiğinde testleri ve örnekleri varsayılan olarak derleyip derlemediğini kontrol edin
MYPROJECT_TESTgibi derleme değişkenlerini varsayılan olarak kapalı tutup yalnızca doğrudan geliştirme yaparken açmak önerilir
- Üçüncü taraf bağımlılığın tamamını derleyip gerçekte yalnızca küçük bir kısmını kullanıp kullanmadığınızı kontrol edin
mbedtls, ihtiyaç duyulmayan kısımları kapatmak için çok sayıda derleme zamanı bayrağı sunar
- Hedef bağımlılıkları hatalı olduğu için küçük değişikliklerde bile her şeyin yeniden derlenip derlenmediğini kontrol edin
- Birçok derleme sistemi bağımlılık grafiğini çıktı olarak verebilir
moldgibi hızlı linker’ları deneyin- Mümkünse derleyicileri de karşılaştırın
- Bazı projelerde clang’ın gcc’den 2 kat hızlı olduğu görülmüş, bazı projelerde ise fark olmamıştır
- Bağımlılıkların testlerinin her seferinde derlenip çalıştırılıp çalıştırılmadığını kontrol edin
- Ek olarak denenebilecek ama getirisi küçük ya da negatif olabilecek başlıklar
- LTO’yu kapatma/açma/thin
- Debug bilgisini ayırma
- Make ve Ninja karşılaştırması
- Dosya sistemi türü ve ayarları
- Derleme çok uzun sürüyorsa kodda değişiklik yapmak başlı başına gerçekçi değildir
Gereksiz kodu kaldırın
- Kod tabanının %30’dan fazlasının tamamen ölü kod olduğu örnekler vardır; bu tür kodlar derleme ve refactoring maliyetini sürekli artırır
- Kaldırma yöntemleri
- Derleyicinin
-Wunused-xxxuyarılarını kullanın- Örnek:
-Wunused-function - Çoğu durumda silme, yeniden derleme ve test yeterlidir; ancak nadiren yanlış fonksiyonun çağrıldığı bir hatanın belirtisi olabilir, bu yüzden tamamen otomatikleştirmeye dikkat etmek gerekir
- Örnek:
- Kullanılmayan fonksiyonları veya sınıf alanlarını bulmak için
cppcheckgibi linter’lar kullanın- Kalıtım ve sanal fonksiyonlarda çok sayıda false positive olabilir, ancak derleyicinin bulamadığı kullanılmayan öğeleri bulabilir
- Linker’ın her fonksiyonu ayrı bir section’a koyup kullanılmayan section’ların kaldırılmasını raporlamasını sağlama yöntemi de vardır; ancak standart kütüphane fonksiyonlarından gelen gürültü fazla olduğu için pratik olmayabilir
- Üretilen assembly ile kaynak kodu karşılaştırma yöntemi sanal fonksiyonlar için pek uygun değildir
- Derleyicinin
- Desteklenen platform listesini kullanarak desteklenmeyen platform kodunu kaldırın
- Gerçekte yalnızca FreeBSD’de çalışan bir projedeki eski Solaris destek kodu
- Gerçek platformda her zaman rastgele sayı üreteci varken kendi rastgele sayı üretecini bulunduran kod
- Yalnızca modern Linux ve macOS’ta çalışmasına rağmen POSIX 2001 desteği yokmuş gibi yazılmış kod
- Big-endian CPU olup olmadığını kontrol edip byte swap yapan kod
- Yıllar önce eklenmiş ama gerçek bir özelliğe dönüşmemiş kod
- Büyük kod silme PR’ları, derleme süresindeki azalmayı ve bakım maliyetindeki düşüşü aynı anda gösterebilir
Linter, formatter ve sanitizer ekleyin
- Linter’larda kuralları aşırı açmamak, temel birkaç tanesini geliştirme yaşam döngüsüne almak gerekir
clang-tidy,cppcheckyararlı olabilir; ancak yavaş ve gürültülü olabilirler- Hiç linter olmaması bir seçenek değildir; ilk çalıştırmada yalnızca derleyici uyarılarıyla yakalanmayan birçok gerçek sorun bulunabilir
- Kod formatlama uygun bir zamanda tek seferde uygulanmalıdır
- Korkunç merge çakışmalarından kaçınmak için aktif dal olmayan bir zamanı beklemek gerekir
- Stil tartışmalarına zaman harcamadan
clang-formatgibi bir araçla tüm kod tabanını istisnasız formatlayın - Ayarları da birlikte commit edin
- Sanitizer’lar, üretimi gerçekten etkileyebilecek ve bulunması zor hataları yakalamak için gereklidir
- Varsayılan olarak
-fsanitize=address,undefinedönerilir - Genellikle false positive üretmezler; tespit edilirse düzeltilmelidir
- Sorunları bulmak için testleri de sanitizer açıkken çalıştırın
- Performans bütçesi izin veriyorsa bazı sanitizer’ların açık olduğu üretim çalıştırmaları da değerlendirilebilir
- Varsayılan olarak
- Dağıtım için kullanılan derleyici sanitizer desteklemese bile geliştirme ve testte clang gibi bir derleyici kullanılabilir
- Sanitizer’ları açmak, uzun süredir saklı kalan hataları ve bellek sızıntılarını ortaya çıkarabilir; düzeltmek ciddi çalışma ve refactoring gerektirebilir
- Mümkünse test sırasında üçüncü taraf bağımlılıkları da sanitizer açıkken derleyip bağımlılıkların içindeki sorunları bulmak iyidir
CI ile otomatikleştirin
- CI, şimdiye kadar ayarlanan linter, formatlama, testler vb. işleri temiz bir ortamda otomatikleştirir
- Her değişiklikte üretim ikili dosyası oluşturulabilmelidir
- Çoğu CI sistemi birden çok platform matrisi destekler; böylece README’deki desteklenen platform listesinin gerçekten derlenip derlenmediği kontrol edilebilir
- Tipik bir pipeline
make all test lint fmtkadar basit olabilir - Linter ve sanitizer’ın raporladığı sorunlar pipeline’ın başarısız olmasına yol açmalıdır; aksi halde kimse düzeltmez
Kodu kademeli olarak sadeleştirin
- Kararlı bir derleme-test-doğrulama döngüsü oluştuğunda kodu yavaş yavaş sadeleştirebilirsiniz
- Karmaşık bir sınıfın gerçekte yalnızca pointer ayırıp null olup olmadığını kontrol ettiği durumlarda, fiilen boolean ile değiştirilebilir olduğu örnekler vardır
- Bu aşamada sadeleştirdikçe daha fazla sadeleştirme noktası açıldığı için zaman sınırı koymak zordur; muhafazakâr değerlendirmek gerekir
- Hedef, güvenlik, doğruluk, performans gibi somut değerler olmalı; “clean code” gibi öznel ölçütlerden kaçınılmalıdır
- C++ standardını yükseltmek amaç değil araçtır
- Örneğin elle iterator artıran kodu
for (auto x : items)döngüsüne çevirmeye yardımcı olabilir - İhtiyacınız olan tek şey
std::clampise kendiniz yazmanız daha iyi olabilir
- Örneğin elle iterator artıran kodu
Bellek güvenli bir dile yeniden yazmak ayrı bir değerlendirme gerektirir
- Bir kısmı bellek güvenli bir dilde yeniden yazmak mümkündür, ancak dikkat edilmesi gereken çok şey vardır
- Yalnızca güçlü bir gerekçe olduğunda yapılmalıdır
Bağımlılık yönetiminde kaynak koddan derlemeyi tercih edin
- C++’ta tutarlı bir bağımlılık yönetimi yoktur ve birçok proje sistem paket yöneticilerini kullanır
- Sistem paketlerine bağımlı olmanın sorunları
- Kurulum talimatları işletim sistemine, dağıtıma ve dağıtım sürümüne bağlıdır
- Ubuntu 20.04’ten 22.04’e geçerken paket sürümleri değiştiği için 100 bağımlılığı aynı anda yükseltmek gerekebilir
- Paket olarak bulunmayan üçüncü taraf bağımlılıklar sonuçta kaynak koddan derlenmek zorunda kalır
- Paket istenen bayraklarla derlenmemiştir
- Sanitizer, LTO,
-march, debug bilgisi, frame pointer, C++ ABI farkları sorun olabilir
- Sanitizer, LTO,
- Denetim, geliştirme ve hata ayıklama sırasında kullanılan tam sürümün kaynak kodunu görmek zordur
- Bağımlılığı patch’leyip yeniden derlemek zordur
- macOS, Ubuntu ve FreeBSD arasında tamamen aynı paket sürümünü kullanmak zordur
- Otomatik BOM oluşturmak zorlaşır
- Statik/dinamik kütüphane biçimi gibi gerekli paket varyantları olmayabilir
- Conan, vcpkg gibi C++ paket yöneticileri de iyileştirme olabilir, ancak kısıtları vardır
- Dış bağımlılıklar yüzünden CI karmaşık ve yavaş hale gelebilir
- Tüm paket sürümleri bulunmayabilir
- Conan’daki mbedtls için
2.16.12’den2.23.0’a atlayan bir örnek vardır
- Conan’daki mbedtls için
- Gerekli işletim sistemini veya mimariyi desteklemeyebilir
- Önerilen yöntem, bağımlılıkları git submodule ile getirip kaynak koddan derlemektir
- Basittir
- Elle vendoring’e kıyasla git geçmişinden ve diff’lerden yararlanılabilir
- Bağımlılık sürümü commit seviyesinde kesin olarak bilinir
- Tek bir bağımlılığı yükseltmek
git checkoutile mümkündür - Tüm platformlarda çalışır
- Her bağımlılık için derleme bayrakları ve derleyici seçilebilir
- C++ deneyimi olmasa bile geliştiriciler git’i bilir
- Özyinelemeli çalışır
- CMake’in
add_subdirectorykomutuyla veyagit submodule foreach makeile her submodule derlenebilir - Submodule zor geliyorsa Neovim gibi tek bir script’in bağımlılıkları getirip derlediği yöntem de mümkündür
- Bağımlılık grafiği çok büyükse Buck2 gibi hibrit yerel/uzak derleme sistemi ve derleme çıktılarının yeniden kullanımı gerekebilir
- Go, Rust gibi derlenen dillerin paket yöneticileri kaynak koddan derleme yaklaşımını kullanır
Okur önerileriyle güçlendirilen noktalar
- Testlerin daha fazla vurgulanması gerekir; C++ test paketi sanitizer altında çalıştırılmalıdır, yoksa sahte bir güvenlik hissine kapılabilirsiniz
- vcpkg, gereksinimleri ve cross-compilation’ı karşılıyorsa git submodule’dan daha iyi bir seçenek olabilir
- Nix, C++ bağımlılık yöneticisi rolü üstlenebilir; ancak karmaşıklık ve yavaşlık sorundur
- Yılda yalnızca bir hata düzeltiliyorsa büyük refactoring yatırımı bir muhakeme meselesidir; ancak ölü kod temizliği ve sanitizer’lar seyrek değişikliklerde de değerlidir
- Kod kaldırma, statik analizle çağrılmadığı doğrulanabilen kısımlara odaklanmalıdır; şüphe varsa kaldırmamak daha güvenlidir
- Sanal metotlarda çağrı hedefi çalışma zamanında belirlendiği için statik analizle kaldırmak zordur
- Satış, ürün yöneticileri ve kullanıcılara özellik/platform kullanımı hakkında sorulacak 15 dakikalık bir sohbet, teknik işi ciddi ölçüde azaltabilir
- LLM’ye kod verip soru sorma yöntemi hukuken güvenli olmalı ve mümkünse yerelde çalıştırılmalı; sonuçlar dikkatle ele alınmalıdır
- Kod analiz araçlarıyla diyagramlar ve sınıf ilişkileri üreterek genel yapıyı anlama fikri de vardır
- Kaynak kontrol sistemi yoksa 0. adım kodu VCS’ye koymaktır
- CI’ın 1. adım sayılması gerektiğini savunan görüşler de vardır; yerel ortam daha hızlı olsa bile bu geçerli bir bakış açısıdır
- Neredeyse hiç kullanılmayan platformları kaldırmak kombinasyon karmaşıklığını azaltır ve büyük sadeleştirme sağlayabilir
- Tamamen yeniden üretilebilir derleme sıradan C++ kod tabanlarında gerçekçi olmayabilir, ancak güvenilir derleme gerçekçidir
- Tüm kod formatlama commit’i, git’in belirli bir commit’i blame’de yok sayacak şekilde ayarlanmasıyla geçmiş takibi yükünü azaltabilir
- VCS geçmiş istatistikleriyle sık değişen bölgeleri ve birlikte değişen dosyaları bulma fikri de vardır
- Bu yaklaşım yalnızca C++ için değil, diğer eski kod tabanları için de uygulanabilir; sanitizer gibi C++’a özgü kısımlar hariç tutulabilir
Working effectively with Legacy Code, ilgili tavsiyeler içeren bir kitap olarak önerilir- Gerçek değere odaklanmak gerekir; ancak büyük bir C++ kod tabanına güvenlik açısından bakıldığında birçok zafiyet bulunabilir ve bunun değeri finansal kazanç değil risk azaltımıdır
1 yorum
Hacker News yorumları
İyi tavsiyeler de var, biraz tartışmalı tavsiyeler de. Birkaç devasa C++ projesi devralmış biri olarak, başlangıçta kesinlikle getirisi yüksek görünen birkaç iş var.
Önce tekrarlanabilir build oluşturun; Docker ya da tercih ettiğiniz paketleme aracıyla build ortamını sararak araçları ve bağımlılıkları açık ve tekrarlanabilir hâle getirmelisiniz.
-Wallile uyarısız build edilebilir hâle getirmek kötü kodu, tanımsız davranışı ve hataları ortaya çıkarır; ayrıca daha sonra şüpheli bir kod eklediğinizde uyarıyı hemen görmenizi sağlar.Başta
valgrindgibi araçlarla okuma/yazma hatalarını kontrol etmek de kararlılık açısından kolay bir kazançtır; tüm yapıyı anlamadan önce refactoring’i yerel tutmak daha güvenlidir.Bütünlük hash’i olmadan değişebilen image tag’lerine işaret eden ya da
apt-getçağıran Dockerfile’lar yaygındır; ikisi de sonucun ince farklarla farklı bir durumda bitmesine geniş alan bırakır.Öğrenmesi daha zor olsa da Bazel veya Nix gibi çözümler çok daha iyi bir temel sağlar.
Container’lar iyidir, ama sorun yokmuş gibi yapmak için kullanıldıklarında berbat bir yöntemdir.
Tüm bağımlılıkları paketlemeli;
/usr/libaltında milyarlarca şeyin doğru sürümde kurulu olmasını gerektiren durumdan kaçınmalısınız.-Wallile temiz build almak sorun değil, ancak production build’lerine-Wall -Werrorkoymaya kesinlikle karşıyım.Derleyici uyarılarının bazıları görüşe daha yakındır; yeni bir derleyici sürümü yeni bir uyarı eklediğinde, eskiden temiz olan kod birden reddedilebilir.
-Werrorgerekiyorsa bunu yalnızca debug build’lerde kullanmak daha iyidir.2 ve 3 numaralı adımların sırasını değiştirirdim. CI, linting, otomatik formatlama vb. önce kurulmalı; bir şeyleri söküp atmaktan önce gelmeli.
Çünkü henüz neyi kaldırmanız gerektiğini ya da kaldırınca ne sonuç doğacağını bilmiyorsunuz.
Linter’lar ve statik analiz araçları, programın hangi bölümlerinde çalışma gerektiğine dair çok içgörü sağlar.
Bugün statik analiz aracının işaretlediği yerler, ileride STL kavramlarını yeniden implemente eden fonksiyonları, sınıfları veya dosyaların tamamını kaldırabileceğiniz yerler olur çoğu zaman.
Ev yapımı iterator kütüphanesini, ev yapımı smart pointer’ları, C string fonksiyonlarının kullanımını STL algoritmalarıyla, gerçek smart pointer’larla ve C++ string sınıflarıyla değiştirebilirsiniz.
Ama kodu taramadan bunlar pek görünmez; hızlı test build’i veya dağıtım yönünde ilerlemeden sonuçları değerlendirmek de zordur.
Eksik yerlere temel smoke test’ler eklerim.
Bu iş sizi yavaşlatmaz, aksine hızlandırır. Makul bir coverage elde edince refactoring sırasında çok daha hızlı hareket edebilirsiniz; küçük yatırımın getirisi büyüktür.
Büyük C++ projelerinde kod üretim script’lerinin yanı sıra, genellikle kod üretimi için veri toplamak üzere kodu parse eden script’ler de bulunur.
Otomatik formatlama bunları bozabilir; hatta kullanıcıların public header dosyalarını kırılgan script’lerle parse ettiği lanetli projeler bile gördüm.
Muhtemelen pek çok yere dokunacaksınız; lokalde çok değişiklik biriktikçe, farklı konuları karıştıran commit’leri yanlışlıkla oluşturabilirsiniz.
O anda CI kurtarıcı olur.
O yoldan gidecekseniz, en azından branch’i squash edip merge etmeye hazır olana kadar doğrudan silmek yerine yorum satırına alarak diff’i basit tutmanızı öneririm.
Kod anlama araçları ve teknikleri eksik. Eskiden Tcl/Tk ile yazılmış Source Navigator adlı bir araç kullanmıştım; kod tabanını indekslemek için çok iyiydi.
Mevcut metodun çağrı hiyerarşisini görebiliyor, bunu kullanarak UML sequence diagram oluşturabiliyordunuz.
Benzer bir araç olarak aşağıdaki Source Insight da var.
Ayrıca notlar önemlidir. Kilit nokta, birine öğretir gibi yazmaktır.
Yıllar içinde kod anlamada epey iyi hâle geldim; bir dönem günde yaklaşık 200 milyon dolar hacmi 4-5 borsada işleyen bir Java algoritmik trading kod tabanını tek başıma aktif şekilde destekleyip geliştirmiştim.
O kodla ilgili 35 MB dokümantasyon vardı; kilit kişi riski bir yana, sorumluluk hissi iyiydi.
Açıkçası büyük kod tabanlarının çoğunda aşırı mühendislik ve tekrar çoktur.
[1] "Source Insight" içindeki referanslar https://d4.alternativeto.net/6S4rr6_0rutCUWnpHNhVq7HMs8GTBs6...
Üniversitedeki bilgisayar mimarisi dersinde bu kalıntıyı kullanmaya zorlanmıştık; çünkü başka yerde olmayan tuhaf bir özellik seti ve ARM emülatörü desteği vardı.
Biz bunu bare-metal ARM assembly programlama için kullanmıştık.
std::cmake'in standart kütüphaneye konmasının oyunun kurallarını değiştireceği kısmında içim kalktı.Bir şirketin ya da açık kaynak projesinin reklamını yaptığı ve sattığı özellikleri sunmak için kesinlikle gerekli olmayan her şeyi motorlu testereyle söküp atma tavsiyesini anlıyorum, ama bu son derece tehlikeli.
Özünde bu bir Chesterton'ın çiti meselesi; bir şeyin yazılıma neden girdiğini ve yazılımın şu anda nasıl kullanıldığını tam olarak anlamadan onu kaldıramazsınız.
En kötü durumda, yaklaşık bir ay sonra sürüm çıktığında kullanıcılar önemli bir özelliğin ince bir şekilde bozulduğunu fark eder ve tam olarak nasıl bozulduğunu izlemek için günlerinizi harcarsınız.
CI, linter, fuzzing, otomatik biçimlendirme vb. eklemek iyi fikir, ama aynı zamanda zor.
Biri ağırlıklı olarak VIM, biri emacs, bir başkası QTCreator, bir diğeri VSCode kullanıyorsa herkesi aynı noktaya getirmek çok zor.
Yeni araç kurulumunu gerektiren isteğe bağlı bir adım ise pratikte gerçekleşmez; linter da projeyi açar açmaz 2000'den fazla uyarı veriyorsa yardımcı olmaz.
cpplintya daclang-tidyveya fuzzer'ın IDE'ye entegre edildiğini görmedim; her tuş vuruşunda otomatik çalıştırmak için de çok yavaşlar.Sadece otomatik biçimlendirme bazen entegre olur.
Bunlar herkesin geliştirme ortamından bağımsız olarak komut satırından yapılabildiğine göre, iki farklı metin düzenleyici kullanılıyor diye başlamadan vazgeçmeyip en azından denemek gerekir.
C++'ta ekip hiçbir araç kurmayacaksa çileli bir yol açılır. Araçları container'laştırıp kolaylaştırmayı da düşünmeye değer.
Profesyonelce çalışmalıyız; iş IDE'den araç zincirine kadar bir düzen gerektiriyorsa öğrenip kullanmalıyız.
Benim bilgisayarımda, benim IDE'mde, benim sevdiğim şekilde derlenip çalışması yazılımdan çok el işi bir ürüne yakındır.
Yerel hook kurulumu isteğe bağlı olsa bile PR'ın CI'da başarısız olmasını sağlarsanız sonunda yapılır.
Bunlar gerçekten temel şeyler, ama CI ve altyapıyı düzgün kullanma konusunda ciddi bir bilgi açığı var gibi görünüyor.
Testler, lint, fuzzing, biçimlendirme, YAML biçim doğrulaması, EOF satır sonu eksikliği kontrolü vb. yerelde çalıştırılabilir olmalı ki geliştiriciler push etmeden önce CI hatalarını önleyebilsin.
Linter projeyi açarken binlerce uyarı veriyorsa, linter'ı ekleyen geliştirici bu değişikliği birleştirmeden önce uyarı sayısını 0'a indirmeli.
Bu, belirli uyarıları veya dosyaları devre dışı bırakarak, bir kısmını düzelterek ya da bunları birleştirerek başarılabilir.
İlk adımın önceki bakımcıyla iletişime geçip yanına gitmek, ona çay ya da bira ısmarlamak ve sonunda kod tabanı hakkında konuşmak olduğunu düşünüyorum.
Eski bakımcı denen sihirbazlar çok şey anlatabilir.
Kalan önerilerin, örneğin birden fazla platformda çalışır hale getirmek ya da testleri geçirmesini sağlamak gibi şeylerin, sağlamlığa ve anlayışa götüren iyi stres testleri olduğunu düşünüyorum.
Yine de önce sizden önce gelen insanlarla konuşma gibi kolay toplanan meyveyi mutlaka toplardım.
Önce biraz kendiniz çalışıp birkaç yerde takıldıktan sonra önceki bakımcıyla konuşmak çok daha verimli olur.
Onlar da bu çabayı takdir edecektir.
Testi olmayan, derlemesi iki denemeden birinde başarısız olan, bağımlılık bilgisi belirsiz ve yalnızca aşırı eski bir işletim sistemi kurulu tek bir sunucuda derlenen bir kod tabanını devraldıysanız önceki bakımcının gerçekten sihirbaz olduğundan emin olamazsınız.
Tüm sorunların zaman yetersizliğinden mi çıktığını, yoksa iş güvencesi için bilerek bozuk tutulmuş ya da yeni şeyler öğrenmek istemeyen bir “sihirbaz”dan mı kaynaklandığını da tartmak gerekir.
Bu en iyi durumdur; yaygın olan ise her soruya “ben devraldığımda da zaten böyleydi” cevabının dönmesidir.
Çalışma sırasında çeşitli hatalar ve sorunlarla karşılaşınca, işverenim için özel olarak yapılmış o yazılımı yazan geliştiricilere ulaşmaya çalıştım.
Meğer tek bir yüklenici yazmış ve o da birkaç yıl önce vefat etmiş.
Savunma sanayisinde bu tür şeyler yaygındır. Belirli sistemler için yapılmış çok sayıda özel, tek seferlik şey vardır; özellikle donanım tarafında ekipmanı yapan mühendisin çoktan ayrılmış ya da emekli olmuş olması nadir değildir.
Bu çok yardımcı olur.
Bir C++ kod tabanında testler olduğunu varsaymak iyimserlik bence.
Refactoring veya iyileştirmeye neden bu kadar odaklanıldığını pek anlamıyorum. Bir özelliği koda ekleyerek ilave edebiliyorsanız, başka hiçbir şeye dokunmadan öyle yapmak yeterli
Yeterince büyük bir değişiklikse, legacy koddan gerekenleri dış fonksiyon çağrısı, ağ katmanı ekleme ya da aynı kodu bir kütüphane olarak ayırma gibi yollarla çıkarıp, kalanını yeni ortamda yapmak yeterli
İleride o kod üzerinde birden fazla kişi çalışmayacaksa ve grubun kolayca işbirliği yapabilmesi için belirli varsayımlar ile standartlara ihtiyaç yoksa, büyük refactoring denemem
Üzerine ekleme yöntemi bir noktaya kadar işler; ama bir değişiklik yaptığınızda birden bir şeylerin bozulduğu ve ortalık fazla dağınık olduğu için nedeni gereğinden çok daha uzun süre bulamadığınız an gelir
Orijinal yazının özü, büyük yeniden yazımlardan kaçınırken bağdaşıklığı koruyup değişiklik yapmayı kolaylaştırmak için düzen toparlama yapmak gibi
Bazen aynı özelliği iki sürüme de eklemek gerekiyor
Son sürümün yakında geleceğini bildiğiniz kodla, önümüzdeki onlarca yıl daha bakımı yapılacak ve özellik eklenecek koda yaklaşım çok farklı
Gerçekte ise yeni özellik çoğu zaman zaten var olan davranışın değiştirilmesidir ve birden çok yerde ağır refactoring yapmak zorunda kalırsınız
Çok iyi tavsiyelerin olduğu bir thread. C/C++ ile sınırlı kalmadan eklemek gerekirse, sürüm kontrol sistemi kullanma imkânınız varsa değerinden tam yararlanmalısınız
Birçok ekip bunu yalnızca basit bir işbirliği aracı olarak kullanıyor, ama bundan fazlasını yapabilir
Geçmişi alıp basit bir veritabanı oluşturabilirsiniz. RDB olması gerekmez; JSON dosyası ya da spreadsheet ile de başlanabilir
Veri odaklı yaklaşım tek başına bile neredeyse anında çok fazla faydalı bilgi çıkarabilir
Sık değişen dosyalar ve fonksiyonlar, gelecekteki çalışmaların hotspot’ları olmaya adaydır; bu yüzden unit test eklemek ya da merge conflict’leri azaltmak istiyorsanız oraya odaklanabilirsiniz
Birbirinden uzak görünen dosyalar sık sık birlikte değişiyorsa, yalnızca koda bakınca görünmeyen örtük bir yapıya işaret edebilir
Her modülün gerçek sahiplik modeli de geçmişten çıkarılabilir; belirsiz sahiplik, refactoring ihtiyacını gösteren bir sinyal olabilir
C/C++’ta build süresi iyileştirmeleri de veri odaklı biçimde önemli modüllere odaklanmalı. Rastgele dosya bağımlılıklarını körlemesine kaldırmak yerine, sık değişen modülleri bölüp header bağımlılıklarıyla birleştirerek gerçek build süresi etkisini puanlayabilirsiniz
Diğer geliştirici araçlarını VCS ile entegre ederseniz daha fazlasını yapabilirsiniz; LLM çağında proje geçmişi ve metaverileri modele verip ilginç içgörüler sormak da mümkün olabilir gibi geliyor
Bunu devasa context window olmadan yapmak için özel model engineering gerekebilir, ama denemeye değer olduğuna dair bir sezgim var
CI, linter, fuzzing, otomatik formatlama vb. ekleme tavsiyesini daha ayrıntılı parçalara ayırmak gerekiyor
CI, yalnızca benim bilgisayarımda değil başka yerlerde de build edildiğini garanti ederek derleme temelli regresyonları önlemeli
Compiler warning’leri ve static analyzer’lar genelde benden daha akıllıdır; pointer’larla tuhaf bir şey yaptığınız için korkutucu bir uyarı çıkarsa bu, kontrol etmek için güçlü bir sinyaldir
Unit test’ler, kritik kodun düşük seviyeye kadar tam olarak beklenen işi yaptığını doğrulamalı; büyük olasılıkla gerçekten öyle değildir, bu yüzden nedenini anlamalısınız
Bir şeyi düzeltince başka bir şey patlayabilir; çünkü mevcut kod bug’lı bir davranış varsayımıyla yazılmış olabilir
Otomatik formatlama öncelik değil; mevcut bakımcıların stilini izlemek bence daha iyi
Devralınan bir C++ codebase’in son aşamasının bellek güvenli bir dile yeniden yazım olması da büyük ihtimalle pek uymayacaktır
Bozuk olmayan bir şey için ek çalışma kaynağı almak zordur; ayrıca C++’ın yanında ek dil bilgisi de gerekir ve testler daha karmaşık hale gelebilir
Bellek ya da performans kısıtları nedeniyle birden çok dilde yazmaya uygun olmama ihtimali de yüksektir; zaten bir legacy codebase devralmış olmak, zaman, para ve bilgi gibi kaynakların eksikliği yüzünden sıfırdan yazılamadığının kabulüne yakındır
“X ile yeniden yaz” yaklaşımı, moda olduğu için yalnızca karmaşıklık ekler
Codebase’in büyük bir bölümünü zaten C++ ile yeniden yazıyorsanız, daha kısıtlayıcı bir C++ alt kümesini izlemek daha iyi olur; High Integrity C++ bence fena değil
Güncel MISRA standardına erişebiliyorsanız o da muhtemelen iyi olur
Tüm ekibe yeni bir dilin keskin köşelerini yeniden öğretmektense, zaten bildiğiniz dili kullanıp bilinen tuzaklardan kaçınmayı sağlayan yönergeleri zorunlu kılmak daha iyi
Yazarın BOM otomasyonu, paket sürüm yönetimi, bağımlılıkların kaynağı vb. konuları epey eleştirdikten sonra git submodules’u paket yöneticilerinden daha iyi diye önermesi garip
Bu eleştirileri yapmadan önce vcpkg kullanması gerekirdi
Biraz keskin tarafları var, ama neredeyse tümü vcpkg ile sezgisel biçimde karşılanıyor
Bağımlılık güncellemek git submodules’a göre biraz daha zor, fakat bunun bug değil feature’a yakın olduğunu düşünüyorum. Bağımlılıklar ayrı sandbox’larda build edilip belirtilen dizine kuruluyor
vcpkg, resmi repository yerine dahili repository’yi registry olarak ayarlayıp vendored-in niteliğini koruyabilir; toolchain’i chainload ederek her şeyi sabit flag setiyle compile ettirebilir ve port bazlı özelleştirmelere de izin verir
Bu tür soyutlamalar faydalı olduğu için paket yöneticileri popüler; herkesin compile flag’leri, macro’lar, warning’ler vb. içeren bitmek bilmeyen string’lerle doğrudan uğraşması gerekmiyor
Yazı eğlenceliydi ve bir şeyler de öğrendim. Ancak insanların “bellek güvenli bir dilde yeniden yazma” derken hangi dili kastettiğini merak ettim
Bazı kısımları Go, Java, C# ile yeniden yazmaktan mı söz ediyorlar, yoksa reddedilebilirliği açık bırakarak Rust ile yeniden yazma gibi alaycı bir göndermeden mi, merak ediyorum
Sonuç tamamen ekibe ve kısıtlara bağlı. Örneğin garbage collection’ın mümkün olup olmadığı, mümkünse Go’nun iyi bir seçenek olup olmadığı, güvenliğin en öncelikli konu olup olmadığı gibi noktalar önemli
Çoğu C++ geliştiricisinin Rust’ı genel olarak kolayca kullanabileceğini ve eşdeğer performans elde edebileceğini düşünüyorum
Ama en başta projenin C++ olmak zorunda olması için iyi bir nedenin bulunmadığı durumlar da var; Java ile başarıyla yeniden yazılmış örnekler de gördüm
Apple, bazı C++ kodlarını Swift ile yeniden yazıyor
Ekibin ya da şirketin rahatça çalışabildiği dil iyi bir pratik kuraldır
Artık yazarın kötü niyetli, şifreli bir Rust programcısı olduğundan şüphelenme aşamasına gelmişiz. Muhtemelen baştan Rust geçmediği için doğrudan şikâyet edemediklerindendir