Bir bug'ın production ortamında ortaya çıkması, geliştirme sürecindeki sıradan bir hatadan çok daha yorucudur. Çünkü artık olay yalnızca "kod çalışıyor mu?" meselesi değildir. Gerçek kullanıcılar etkilenir, loglar yetersiz kalabilir, hata yeniden üretilemeyebilir ve herkes aynı soruyu sorar: "Bu sorun neden testte görünmedi?"
İşin zor kısmı şudur: Production'daki bug'lar çoğu zaman büyük ve açık hatalar değildir. Tam tersine, küçük bir koşul birleşimi, nadir bir veri seti, yanlış zamanlama ya da dış bir sistemin beklenmedik davranışı yüzünden ortaya çıkar. Bu yüzden günlerce bulunamayan bug'lar genelde "gizli" değil, "çok iyi kamufle olmuş" bug'lardır.
Bu yazıda, production'da bir bug'ın neden günlerce bulunamadığını ve böyle durumlarda nasıl daha sistemli yaklaşılması gerektiğini anlatacağım.
Production neden bu kadar zor bir ortamdır?
Geliştirme ortamı ile production aynı şey değildir. Bu cümle basit görünür ama çoğu sorunun kökü buradadır.
Development ortamında:
- veri daha küçüktür,
- kullanıcı sayısı düşüktür,
- sistem daha az yük altındadır,
- test verileri daha düzenlidir,
- birçok kenar durum hiç oluşmaz.
Production ortamında ise:
- gerçek kullanıcı davranışı vardır,
- trafik dalgalanır,
- veri çeşitliliği çok fazladır,
- dış servisler devrededir,
- zamanlama farklılıkları ortaya çıkar,
- aynı kod, farklı koşullarda bambaşka davranabilir.
Burada önemli bir terim var: edge case. Türkçe olarak "uç durum" ya da "nadir senaryo" demektir. Yani normal akışta çok sık görmediğin ama sistemin bir yerinde problem çıkarabilen özel durumlar.
Production bug'larının çoğu tam da bu uç durumlarda yaşanır.
Bug neden testte görünmemiş olabilir?
Bir bug'ın testte çıkmaması, mutlaka testin kötü olduğu anlamına gelmez. Bazen sorun şu nedenlerden biri olur:
- Gerçek veri testte yoktur.
- Aynı kullanıcı akışı denenmemiştir.
- Sistem yük altındayken davranış değişiyordur.
- Zamanlama farkı vardır.
- Üçüncü parti bir servis farklı cevap veriyordur.
- Bir kültür, saat dilimi veya bölgesel ayar farkı bug'ı tetikliyordur.
Örneğin saat bazlı çalışan bir iş, yerel makinede sorunsuz görünür ama production'da farklı zaman diliminde bozulur. Ya da boş görünmeyen bir alan, gerçek veride beklenmeyen bir karakter içerdiği için patlar.
Bir başka kavram da race condition. Türkçesi kabaca "yarış durumu"dur. Aynı anda birden fazla işlemin birbirinin sonucunu etkilediği durumlarda ortaya çıkar. Bu tür hatalar çok zor yakalanır, çünkü her çalıştırmada oluşmaz.
En sık görülen senaryo: Sorun sadece bazen oluşur
Günlerce bulunamayan bug'ların büyük kısmı deterministik değildir. Deterministik demek, her zaman aynı sonucu veren anlamına gelir. Eğer bug her zaman oluşmuyorsa, onu yeniden üretmek çok zorlaşır.
Örneğin:
- her 100 isteğin 1'inde hata oluşuyorsa,
- yalnızca belirli veri gelince tetikleniyorsa,
- sadece bir kullanıcı türünde görülüyorsa,
- sadece yoğun saatlerde ortaya çıkıyorsa,
sorunu bulmak doğal olarak zorlaşır.
Bu yüzden "ben burada göremedim" ifadesi, sorunun olmadığı anlamına gelmez. Sadece o koşullarda oluşmadığı anlamına gelir.
Kötü loglama, bug'ı haftalarca görünmez yapabilir
Log, yani kayıt, bir sistemin içerde ne yaptığını anlamanın en güçlü araçlarından biridir. Ama log varsa faydalıdır. Eksik, düzensiz veya anlamsız loglar çoğu zaman hiçbir işe yaramaz.
Kötü loglamanın tipik sorunları:
- Hangi isteğin hangi kullanıcıya ait olduğu belli değildir.
- Hata oluştu ama bağlam yoktur.
- Sadece "error occurred" gibi genel mesajlar yazılmıştır.
- İlgili parametreler kaydedilmemiştir.
- İstekler birbiriyle ilişkilendirilememiştir.
Burada önemli bir kavram daha var: correlation id. Türkçesi "ilişkilendirme kimliği" gibi düşünülebilir. Bir isteğin sistem boyunca takip edilmesini sağlayan benzersiz bir değerdir. Bir bug günlerce bulunamıyorsa, çoğu zaman problem bu iz sürmenin eksik yapılmasıdır.
Örnek: Basit ama işe yarayan loglama
public class OrderService
{
private readonly ILogger<OrderService> _logger;
public OrderService(ILogger<OrderService> logger)
{
// Logger nesnesini dışarıdan alıyoruz
_logger = logger;
}
public void CreateOrder(int userId, decimal amount)
{
// Sipariş işlemi başladığını ve önemli verileri logluyoruz
_logger.LogInformation("Sipariş oluşturuluyor. UserId: {UserId}, Amount: {Amount}", userId, amount);
try
{
// Burada gerçek sipariş oluşturma işlemi yapılır
// Örneğin veritabanına kayıt atılır, ödeme doğrulanır, stok kontrol edilir
}
catch (Exception ex)
{
// Hata oluştuğunda detaylı kayıt alıyoruz
_logger.LogError(ex, "Sipariş oluşturulurken hata oluştu. UserId: {UserId}", userId);
throw;
}
}
}Bu örnekte önemli olan şey yalnızca hata kaydetmek değil, hata anındaki bağlamı da saklamaktır. Sonradan bakıldığında "hangi kullanıcı, hangi tutar, hangi akış" sorularına cevap verebilmek gerekir.
Reprodüksiyon yoksa teşhis de zor olur
Bir bug'ı bulmanın en büyük hızlandırıcısı, onu yeniden üretebilmektir. Buna reproduce etmek denir. Türkçesi "yeniden oluşturmak" ya da "tekrar üretmek" şeklinde açıklanabilir.
Eğer bir bug'ı üretemiyorsan:
- hangi koşulda çıktığını tam bilemezsin,
- çözümünü doğrulamak zorlaşır,
- yanlış yere müdahale etme ihtimalin artar.
Bu yüzden sağlam bir hata ayıklama süreci önce şu soruları sorar:
- Hangi kullanıcı etkileniyor?
- Hangi tarih ve saatte oluşuyor?
- Hangi veri ile tetikleniyor?
- Hangi ortamda görülüyor?
- Her seferinde mi oluyor, bazen mi?
Bu sorular netleşmeden yapılan her müdahale biraz tahmindir.
Production bug'larının en büyük düşmanı: varsayım
Varsayım, yazılımda çok pahalıya patlar. Çünkü çoğu bug, "böyle gelmez", "burası boş olmaz", "bu servis hep döner", "bu alan max 20 karakterdir" gibi kabuller yüzünden çıkar.
Oysa production'da gerçek hayat farklıdır.
Bir alan beklenmedik şekilde boş gelebilir. Bir servis kısa süreliğine yanıt vermeyebilir. Bir kullanıcı aynı butona iki kez basabilir. Bir cron job, beklenmeyen saatte tetiklenebilir.
Burada bir başka terim var: cron job. Türkçe olarak "zamanlanmış görev" diyebiliriz. Belirli aralıklarla otomatik çalışan işlerdir. Bu tip işlerde saat farkı, çakışma ve tekrar tetiklenme gibi sorunlar sık görülür.
Hatanın başka bir sistemden geliyor olması
Bazen bug senin kodunda değildir. Dış servislerde, API entegrasyonlarında, kuyruk sistemlerinde veya altyapıda olabilir.
Örneğin:
- ödeme servisi geç cevap veriyordur,
- bir API zaman zaman boş veri dönüyordur,
- mesaj kuyruğu gecikiyordur,
- DNS tarafında kısa süreli yönlendirme problemi vardır.
Bu durumda sorun senin ekranında görünür ama kök neden başka yerdedir.
Burada dependency kelimesi geçer. Türkçesi "bağımlılık"tır. Sistemin çalışması için ihtiyaç duyduğu dış parça anlamına gelir. Bir bug günlerce bulunamıyorsa, çoğu zaman bağımlılıkların davranışı yeterince izlenmemiştir.
İzolasyon yapılmadan çözüm aranırsa zaman kaybedilir
Sorunu bulmak için en doğru yöntemlerden biri izolasyondur. Yani sistemi küçük parçalara ayırıp hangi parçanın sorun çıkardığını görmek.
Örneğin:
- yalnızca input doğrulamasını kontrol et,
- yalnızca veritabanı çağrısını test et,
- yalnızca dış servis çağrısını incele,
- yalnızca ilgili branch'i çalıştır.
Sorunu bir anda tüm sistem üzerinden çözmeye çalışmak çoğu zaman kafa karıştırır.
Aşağıdaki basit yaklaşım, logları daha anlamlı hale getirmek için kullanılabilir:
public class PaymentService
{
private readonly ILogger<PaymentService> _logger;
public PaymentService(ILogger<PaymentService> logger)
{
// Logger'ı alıyoruz
_logger = logger;
}
public bool ProcessPayment(decimal amount, string orderNo)
{
// İşlem başlangıcını logluyoruz
_logger.LogInformation("Ödeme işlemi başladı. OrderNo: {OrderNo}, Amount: {Amount}", orderNo, amount);
if (amount <= 0)
{
// Geçersiz veri geldiğinde bunu açıkça yazıyoruz
_logger.LogWarning("Geçersiz ödeme tutarı. OrderNo: {OrderNo}", orderNo);
return false;
}
// Burada dış ödeme servisi çağrılıyor olabilir
// Dış sistem hatası burada yakalanmalı ve kaydedilmelidir
_logger.LogInformation("Ödeme işlemi başarıyla tamamlandı. OrderNo: {OrderNo}", orderNo);
return true;
}
}Bu tarz loglar sayesinde sorun üretimden sonra da izlenebilir hale gelir.
Hata aslında veri kaynaklı olabilir
Production bug'larının önemli kısmı koddan değil veriden çıkar. Çünkü gerçek veri:
- kirli olabilir,
- eksik olabilir,
- beklenmedik formatta gelebilir,
- eski sistemlerden taşınmış olabilir,
- manuel müdahaleler içerebilir.
Test ortamındaki temiz veri ile production'daki karmaşık veri aynı değildir. Bu yüzden bir alanın "string" olması, oraya her zaman düzgün metin geleceği anlamına gelmez. İçine farklı karakterler, uzun değerler veya bozuk kayıtlar girebilir.
Bu yüzden veri doğrulama, sadece ekran tarafında değil, uygulamanın kritik noktalarında da düşünülmelidir.
Monitoring ve observability farkı
Burada iki kavramı ayırmak gerekir.
Monitoring, sistemi izlemek demektir. CPU kullanımı, hata oranı, cevap süresi gibi metrikleri takip edersin.
Observability ise sistemin iç durumunu dışarıdan anlayabilme yeteneğidir. Yani sorun çıktığında "ne oldu?" sorusuna cevap verebilme kapasitesidir.
Basitçe söylemek gerekirse:
- Monitoring = "Bir sorun var mı?"
- Observability = "Sorun neden oldu?"
Günlerce bulunamayan bug'larda observability seviyesi zayıfsa iş çok uzar.
Küçük bir bug neden büyük etki yaratır?
Çünkü production'da küçük bir hata, etkisini kullanıcı sayısı ile çarpar. Geliştirme ortamında sadece birkaç kişi test eder. Production'da binlerce kullanıcı aynı anda etkilenebilir.
Örneğin:
- tek bir null değer yüzünden ekran çökebilir,
- tek bir timeout tüm akışı bozabilir,
- küçük bir tarih farkı raporlamayı yanlış gösterebilir,
- tek bir yanlış cache davranışı eski veriyi herkese yayabilir.
Burada null kavramını da netleştirelim. Null, "değer yok" demektir. Yazılımda en çok sorun çıkaran şeylerden biridir, çünkü beklenmeden geldiğinde akışı bozabilir.
Production bug'ını bulmak için nasıl düşünmelisin?
Sistemli ilerlemek gerekir. Panik yapmak yerine şu sırayla düşün:
- Sorunun etkisi ne?
- Kim etkileniyor?
- Hangi veri ile oluşuyor?
- Hangi saatlerde ortaya çıkıyor?
- Hangi servisler devrede?
- En son ne değişti?
- Sorun kodda mı, veride mi, altyapıda mı?
Özellikle "en son ne değişti?" sorusu çok değerlidir. Çünkü production bug'ları çoğu zaman son yapılan değişiklikle ilişkilidir, ama her zaman doğrudan onun yüzünden çıkmaz. Bazen sadece mevcut bir problemi görünür hale getirir.
Sonuç
Production'da yaşanan bir bug'ın günlerce bulunamamasının nedeni çoğu zaman tek bir hata değildir. Eksik loglama, zayıf izleme, yetersiz reprodüksiyon, yanlış varsayımlar, veri farkları ve dış bağımlılıklar birleşir. Problem büyüktür çünkü görünmezdir.
İyi haber şu: Daha iyi loglama, anlamlı correlation id kullanımı, doğru izolasyon, veri odaklı düşünme ve sistematik hata ayıklama ile sorunlar çok daha hızlı bulunabilir.
Asıl farkı yaratan şey daha çok kod yazmak değil, doğru soruları doğru sırayla sormaktır.