1 puan yazan GN⁺ 2024-03-01 | 1 yorum | WhatsApp'ta paylaş
  • 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, mold gibi 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,undefined ve 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
  • 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_uring gibi 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.sh iç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
      • mbedtls de 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_TEST gibi 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
    • mold gibi 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
  • 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-xxx uyarı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
    • Kullanılmayan fonksiyonları veya sınıf alanlarını bulmak için cppcheck gibi 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
  • 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, cppcheck yararlı 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-format gibi 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
  • 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 fmt kadar 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::clamp ise kendiniz yazmanız daha iyi olabilir

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
    • 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
    • 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 checkout ile 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_subdirectory komutuyla veya git submodule foreach make ile 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

 
GN⁺ 2024-03-01
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.
    -Wall ile 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 valgrind gibi 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.

    • Açıklık ve tekrarlanabilirlik istiyorsanız işe Docker ile başlamak pek iyi değildir. Çok dikkat etmezseniz Docker tekrarlanabilirliğin en sulandırılmış hâlini verir ve hatta sahte bir güvenlik hissi yaratabilir.
      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.
      1. adım tekrarlanabilir build’dir; 1. adım tüm testleri çalıştırıp kararsız testleri işaretlemektir.
      2. adım tüm testleri sanitizer altında çalıştırıp başarısız olanları işaretlemek, 3. adım bu sanitizer hatalarının hepsini düzeltmek, 4. adım ise geri kalan işlerdir.
    • Mikroservisler için Docker fena değildir, ama her şeyi her zaman Docker ile dağıtamazsınız. Özellikle uygulamayı Docker içinde kullanmak isteyen kişiler için durum böyledir; Docker-in-Docker ise hiç olmaması gereken bir durumdur.
      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/lib altında milyarlarca şeyin doğru sürümde kurulu olmasını gerektiren durumdan kaçınmalısınız.
    • -Wall ile temiz build almak sorun değil, ancak production build’lerine -Wall -Werror koymaya 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.
      -Werror gerekiyorsa 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.

    • Ajans işi yaparken kod tabanlarını kurtarmaya girdiğim epey deneyim oldu; ilk yaptığım şey unit test’leri çalıştırıp coverage’a bakmak olur.
      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.
    • Buna karşılık otomatik formatlama sürüm geçmişini bozar ve bu satırın ne zaman, neden eklendiğini analiz etmeyi zorlaştırır.
    • Legacy bir kod tabanına otomatik formatlama uygulamayı kesinlikle önermem.
      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.
    • Burada CI diğer maddelerden farklı. İlk adım, en azından tekrarlanabilir sonuçlarla çalışan ve her commit’te koşan düzgün yol test harness’i oluşturmaktır; bu, kod tabanını anlamaya da yardımcı olur.
      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.
    • Ayrıntıya girersek, orijinal metinde sökülüp atılması söylenen kapsam, derleyici uyarıları ve desteklenmeyen mimarilerle yönlendirilen ölü kod ile sınırlı.
      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...

    • Source Navigator’ı gerçekten kullanmış birine doğal ortamında rastladığıma inanamıyorum.
      Ü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.

    • Yazarın listeledikleri genelde IDE entegrasyonu değildir. C++ geliştirme ortamında cpplint ya da clang-tidy veya 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.
    • Birinin VIM, diğerlerinin emacs, QTCreator, VSCode kullanması artık kabul edilebilir bir cevap değil bence.
      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.
    • Otomatik biçimlendirme editörün dışında da yapılabilir. pre-commit ayarlanır ve CI'da çalıştırılır; önemsiz bir iştir.
      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.
    • Patronun istediği temel şeyleri öylece reddedebileceğinize dair notu kaçırmış gibiyim. Hani maaşınızı ödeyen kişi.
    • Yerelde isteğe bağlı olan pre-commit hook'ları, CI'da zorunlu bir adımla desteklenmeli.
      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.

    • Bunu ilk adım yapmazdım. O zaman onların zamanını boşa harcama ihtimali yüksek.
      Ö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.
    • Önce kod tabanına hızlıca göz atıp en büyük WTF'leri bulduktan sonra soru sormak daha iyi olabilir.
      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.
    • Eski kodda önceki bakımcıya soru sorduysanız bilirsiniz: devir teslim daha dün yapılmış olsa bile 6 aydan daha önce yaptığı faydalı bir işi neredeyse hiç anlatamaz.
      Bu en iyi durumdur; yaygın olan ise her soruya “ben devraldığımda da zaten böyleydi” cevabının dönmesidir.
    • Bir zamanlar askeri kapalı ağda, eski bir özel işletim sistemi üzerinde yazılım dağıtma işi üstlenmiştim. Büyük bir program değildi, yaklaşık 50 bin satırdı.
      Ç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.
    • Söylemesi kolay. Benim 0. adımım bunu bir UML aracına koyup sınıf diyagramları ve başka diyagramlar çıkarmaktır.
      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

    • Orijinal metin büyük refactoring’e karşı çıkıyor. Önerdiği kademeli işler, kodu giderek daha ele alınabilir hale getirmeye yönelik
      Ü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
    • Doğru cevap geleceğe bağlı. Yerine geçecek ürün piyasada zaten varken, eski sürüm kodu birkaç kez daha yayınlanması gereken C++ kodu üzerinde çalışmıştım
      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ı
    • Şanslı bazı durumlarda özelliği sadece ekleyebilirsiniz
      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

    • Bu tür analizleri otomatikleştirmeye yardımcı olacak yazılım önerileri olup olmadığını merak ediyorum
  • 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

    • Aslında bellek güvenli olmayan C++ kullanımını ortadan kaldırıp yönergeleri zorunlu kılmak bile bu hedefe C++ codebase içinde ulaşabilir
      “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

    • Yazar olarak söyleyeyim, bu konuyu ikinci yazıda ele almayı planlıyorum
      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
    • C++ hakkında bir yazıydı; “Rust” kelimesi bir kez bile geçmedi ve onlarcası bulunan “memory safe” dillerden yalnızca söz edildi. Buna rağmen Rust meme’ini araya sıkıştırıp küçümseme biçimi ilginç
      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