Kariyerimin otuzlu yaşlarına geldiğimde anladım ki, sistemlerin çökmesi, disklerin dolması ya da ağın kopması gibi olaylar, aslında en büyük sorunlarım değildi. Asıl katil, o sessiz sedasız biriken, “sonra hallederiz” diye geçiştirilen teknik borçtu. Bir projenin derinliklerinde sinsice büyüyen, her geçen gün daha fazla maliyet çıkaran bir canavardan bahsediyorum.
Bu durum, özellikle yıllarca sistem mimarisi, network yönetimi ve kurumsal yazılım geliştirme alanlarında çalışmış biri olarak benim için çok net bir gerçek. Gördüm ki, çoğu zaman yazılım mimarisi, kod satırlarından çok, organizasyonel akışların ve verilen anlık kararların bir yansıması oluyor.
”Evet” Demenin Gerçek Maliyeti
Bir üretim ERP’sinde çalışırken, yeni bir özellik ya da rapor istendiğinde “evet, yaparız” demek ne kadar kolaydı. Müşteri memnuniyeti, hızlı teslimat baskısı, bütçe kısıtları… O an için en hızlı, en kestirme çözümü arıyorduk. Örneğin, karmaşık bir üretim planlama algoritmasını entegre etmek yerine, elle girilen verilerle çalışan basit bir pivot tabloyu “şimdilik” devreye aldığımı hatırlıyorum. “Zaten operatörler idare eder, sonra AI ile entegre ederiz” dedik. O “sonra”, yıllarca gelmedi ve her üretim planlama döngüsü, manuel müdahalelerle dolu bir eziyete dönüştü.
Bu tür “geçici” çözümlerle, aslında birer kar topu gibi yuvarlanan borçlar biriktirdim. Bir süre sonra o üretim akışını optimize etmek için ciddi bir emek harcadığımı biliyorum. Satın alma, üretim, sevk ve faturalama gibi kritik iş akışlarında yapılan her kısa yol, zincirin diğer halkalarında beklenmedik aksaklıklara ve uyumsuzluklara yol açıyordu. Zamanında yapılan kısa bir iş, çok daha uzun süren bir eziyete dönüşüyordu.
Sessiz Katilin Belirtileri: Nereden Anlarsın?
Teknik borç, kendini öyle hemen göstermez. İlk başlarda her şey yolunda gibi görünür, sistem çalışır. Ancak zamanla sistemde garip aksaklıklar başlar. Yeni bir özellik eklemek gittikçe zorlaşır, “bu kod bloğuna dokunursak her yer yıkılır” sendromu ortaya çıkar. Tipik örnek: ufak bir konfigürasyon değişikliği sonrası systemd unit’lerinin tuhaf davranmaya başlaması. Root cause’u bulmak hayli zaman alabiliyor, çünkü alttaki cgroup limitlerini düşünmeden, “hızlıca” eklenmiş bir servisin yarattığı bir dependency karmaşası işin içine giriyor.
PostgreSQL’de WAL bloat alarmı düştüğünde, ya da Redis’in OOM eviction policy’si yüzünden kritik dataların uçtuğunu gördüğümde, hep o borçların faturasını ödüyorduk. Bir Nginx reverse proxy konfigürasyonunu, “ne olur ne olmaz” diyerek her şeye izin verecek şekilde yazıp, sonra rate limiting ve JWT/OAuth2 paternlerini entegre etmeye çalıştığımızda yaşanan sancıları unutamam. Her yeni deploy bir kumar haline gelmişti, çünkü alt katmanlar o kadar yamalı bohçaya dönmüştü ki, neresinden tutsan elinde kalıyordu.
Borçla Yaşamak mı, Ödemek mi? Trade-off’lar
Peki, madem bu kadar kötü, neden bu kadar çok birikiyor? Cevap basit: anlık fayda ve uzun vadeli maliyetin dengesizliği. Bir şirket, hızlıca pazara ürün çıkarmak istediğinde, mimari disiplinden ödün vermek kolaydır. “Şimdi çalışsın da, sonra düzeltiriz” düşüncesi, o anki baskıyı hafifletir. Ancak o “sonra” hiç gelmez. Veya geldiğinde, maliyet öyle büyümüştür ki, ödemek imkansız hale gelir.
Bir keresinde, bir kurumun iç platformunda, eski bir entegrasyon katmanını değiştirmemiz gerekiyordu. Mevcut yapı, yıllar boyunca biriken bir borcun ürünüydü. Her yeni entegrasyon, bu eski yapının üzerine yamalarla eklenmiş, katman katman bir labirente dönüşmüştü. O borcu ödemeye kalkışmak, projenin komple durması ve uzun sürecek bir yeniden yazım demekti. O anki tercih, borçla yaşamaya devam etmek oldu, ama her yeni talep, o labirentte bir kez daha kaybolmak demekti. Bu, X yapardık, ama Y yüzünden Z’yi seçtik denkleminin en acı örneklerinden biriydi.
Borçla Mücadele Yöntemlerim: Pragmatik Yaklaşımlar
Bu borçla mücadelede birkaç şey öğrendim. Birincisi, borcun farkında olmak. Ekibe sürekli “bu bir quick fix, teknik borç yaratıyoruz, ne zaman ödeyeceğiz?” sorusunu sormak ve bu konuyu şeffaf bir şekilde tartışmak. İkincisi, küçük adımlarla ödemek. Büyük bir refactor projesi yerine, her yeni feature eklendiğinde, ilgili modülün küçük bir kısmını temizlemek. Ben kendi yan ürünümün finansal hesaplayıcılarında, her yeni özellik eklediğimde, ilgili kısmın test coverage’ını artırıp, eski ve kötü yazılmış bir metodu küçük parçalara ayırmaya çalışırım.
Bir de, observability çok önemli. Metrikler, loglar, traceler… Borcun nerede biriktiğini, performansın nerede düştüğünü görmek için bunlar şart. Bir journald rate limit’i yüzünden kaybolan logların, aslında bir OOM-killed olmuş servisin son nefesleri olduğunu anlamak için doğru tool’lara ve alışkanlıklara ihtiyacımız var. Hatalarımı da hep şeffaf bir şekilde paylaştım. Sabit bir sleep ile bekleyip OOM-killed olan bir servisi polling-wait’e geçirdiğimde, bunun neden bir borç yarattığını ve nasıl düzelttiğimi ekiple paylaştım. Utanmadım, çünkü bu, öğrenme sürecimizin bir parçasıydı. Kernel module blacklist’leri takip etmek, fail2ban paternlerini sürekli güncellemek gibi güvenlik odaklı işlerde bile, biriken borçların nasıl yeni riskler yarattığını defalarca gördüm.
Son Söz: Borçsuz Sistem Yoktur, Yönetilmeyen Borç Katildir
Teknik borç, bir projenin gizli maliyetidir, geliştirici motivasyonunun katilidir ve sonunda müşteri memnuniyetini kemiren bir kurttur. Onu görmezden gelmek, uzun vadede çok daha büyük bedeller ödemek demektir. Benim kariyerimdeki en büyük derslerden biri, bu sessiz katilin fısıltılarına kulak vermek ve onunla proaktif bir şekilde mücadele etmek oldu. Borçsuz sistem yoktur, önemli olan onu yönetmektir.
Peki, senin kariyerindeki en büyük teknik borç hikayen neydi? Ya da bu borçla mücadele etmek için kullandığın en etkili yöntem ne oldu? Yorumlarda merakla bekliyorum.