Mert Özen Her Satırda Daha İleri
Yazılım Tasarımı Clean Code — #01

Clean Code Nedir? Okunabilirlik, Değişim Maliyeti ve 2025'teki İkinci Baskı

Mert Özen 7 Kas 2026 16 dk 4 görüntülenme
Clean Code Nedir? Okunabilirlik, Değişim Maliyeti ve 2025'teki İkinci Baskı

Clean Code, çalışan kodun ucuza değiştirilebilir ve okunabilir olmasıdır: Bookstore'un legacy OrderService'inde aynı sepet Türkçe I tuzağı, kopyalanıp ayrışmış fiyat hesabı ve DateTime.Now yüzünden dört farklı tutar üretiyor. 2025'te çıkan ikinci baskı kitabı Code, Design, Architecture ve Craftsmanship kısımlarıyla yeniden yazdı; seri bu 609 satırlık dosyayı adım adım temizleyecek.

Sürüm notu: Bu yazıdaki kodlar C# 14 ve .NET 10 ile derlenip denenmiştir.

Önce Tahmin Et

Bookstore'un sipariş modülü 2017'den beri canlıda. Kimse ona dokunmak istemiyor, çünkü "çalışıyor". Aşağıdaki program aynı müşteri için aynı sepeti iki kez işliyor: bir kez makinenin dili İngilizce (en-US), bir kez Türkçe (tr-TR) iken. Her seferinde önce sepet sayfasının gösterdiği fiyatı (GetQuote), sonra müşteriden gerçekten çekilen tutarı (ProcessData) yazdırıyor. Müşteri 1 VIP (Very Important Person; burada en değerli müşteri segmenti), kupon yok, kargo tipi 1 (standart).

using System.Globalization;
using Bookstore.Legacy;

var cart = new Dictionary<string, int>
{
    ["978-0441013593"] = 1, // Dune, basılı kitap
    ["978-0553380163"] = 1  // A Brief History of Time, sesli kitap
};

foreach (string culture in new[] { "en-US", "tr-TR" })
{
    CultureInfo.CurrentCulture = new CultureInfo(culture);
    Db.Reset();
    var service = new OrderService();

    decimal quote = service.GetQuote(1, cart, null, 1);
    int orderId = service.ProcessData(1, cart, null, false, false, 1);
    Order order = service.GetOrder(orderId);

    Console.WriteLine($"{culture}  cart page: {quote.ToString(CultureInfo.InvariantCulture)}  charged: {order.Total.ToString(CultureInfo.InvariantCulture)}");
}

Console.WriteLine($"Run on: {DateTime.Now.DayOfWeek}");

Dört sayı göreceksin. Kaç tanesi birbirine eşit olmalı? Kaç tanesi gerçekten eşit çıkar? Bir de şunu düşün: bu programı salı günü çalıştırsaydın sonuç değişir miydi?

Aynı Sepet, Dört Farklı Rakam

Gerçek çıktı:

en-US  cart page: 731.50  charged: 690.52
tr-TR  cart page: 770.0  charged: 727.65
Run on: Saturday

Mantıken tek bir rakam olmalıydı. Dört farklı rakam var ve hiçbiri bir exception, bir uyarı ya da bir log satırı üretmedi. Sebepleri koda bakınca tek tek çıkıyor, ama her biri farklı bir yerde saklanıyor:

VIP indirimi c.Type.ToUpper() == "VIP" ile kontrol ediliyor. Müşterinin tipi veritabanında "vip" olarak duruyor. ToUpper() parametresiz çağrılınca o anki culture'ı kullanır ve Türkçede küçük "i"nin büyüğü "İ"dir. Sonuç "VİP" olur, karşılaştırma false döner ve VIP müşteri Türkçe ayarlı sunucuda yüzde 5 indirimini sessizce kaybeder. Türkçe I tuzağını C# Temelleri serisinin String'ler yazısında görmüştük; burada canlı bir iş kuralını bozuyor.

Sepet sayfasının kullandığı GetQuote, ProcessData'nın fiyat hesabının bir kopyası. Kopya yıllar içinde orijinalinden ayrılmış: sesli kitaba yüzde 10 indirimi ve hafta sonu indirimini bilmiyor. Bu yüzden müşteri sepette 731.50 görüyor, kartından 690.52 çekiliyor.

Hafta sonu indirimi DateTime.Now'a bakıyor. Bu çıktıyı cumartesi aldım; salı günü aynı program farklı rakamlar basar. Kodun davranışı takvime bağlı ve bunu ancak method'u satır satır okuyan biri fark edebilir.

Bir ayrıntı daha: 675 − 47.25 = 627.75, bunun yüzde 10 vergili hâli 690.525. Çıktıda 690.52 görüyorsun, çünkü Math.Round varsayılan olarak banker's rounding (MidpointRounding.ToEven) kullanır. Bu bir hata değil, ama kodda bunu bilinçli seçtiğini gösteren hiçbir iz yok.

Bu dört rakam serinin konusunu özetliyor. Kod çalışıyor: derleniyor, sipariş alıyor, para çekiyor. Ama ne yaptığını anlamak pahalı, değiştirmek riskli. Clean Code (Robert C. Martin'in 2008'de yayımlanan, 2025'te ikinci baskısı çıkan kitabı ve adını verdiği yaklaşım) bu ikinci problemle ilgilenir.

Kod Yazıldığından Çok Okunur

Robert C. Martin ("Uncle Bob" lakabıyla bilinen yazılımcı, Agile Manifesto'nun imzacılarından), Clean Code'un 2008 baskısında okumaya harcanan zamanın yazmaya harcanandan on kattan fazla olduğunu söyler. Bu bir ölçüm değil, kendi gözlemi; ama günlük hayatta doğrulaması kolay. Yeni bir kupon eklemek istediğinde önce kuponların nerede işlendiğini bulman gerekir:

$ grep -n 'WELCOME10\|SUMMER25' OrderService.cs
85:                            if (strCoupon.ToUpper() == "WELCOME10")
87:                            else if (strCoupon.ToUpper() == "SUMMER25")
185:                if (coupon.ToUpper() == "WELCOME10") tmp2 = tmp * 0.1m;
186:                if (coupon.ToUpper() == "SUMMER25") tmp2 = 25;

İki yer çıktı. Ama ikisi aynı şeyi yapmıyor: biri geçersiz kuponda -4 döndürüp siparişi durduruyor, öteki geçersiz kuponu sessizce yok sayıyor. Yeni kuponu eklemek bir satır, ama hangi davranışın doğru olduğuna karar vermek için iki method'u, onların çağrıldığı yerleri ve muhtemelen ürün sahibini okuman gerekiyor. Değişiklik maliyetinin büyük kısmı yazmak değil, anlamak.

İkinci baskı da bu fikirden vazgeçmiyor: ilk bölümde "We Read More Than We Write" başlıklı bir alt bölüm ve Boy Scout Rule (kampı bulduğundan temiz bırak) yine yer alıyor. Kitabın asıl iddiası şu: kod bir sonraki okuyucu için yazılır ve bir sonraki okuyucu çoğu zaman altı ay sonraki sensin.

"Çalışıyor" ile "Değiştirilebilir" Arasındaki Fark

Martin, Clean Architecture'da (Clean Architecture: A Craftsman's Guide to Software Structure and Design, 2017) yazılımın iki değerinden bahseder: bugün istenen davranışı göstermesi ve yarın istenecek davranışa ucuza dönüşebilmesi, yani yapısı. İkinci baskının mimari kısmı da "The Two Values of Software" başlıklı bir bölümle açılıyor. Clean Code ikinci değerin kod seviyesindeki karşılığı. OrderService birinci değeri yıllardır veriyor. İkincisini kaybetmiş durumda: bir kupon eklemek iki method'u, bir indirim kuralını değiştirmek takvimi ve sunucunun dil ayarını düşünmeyi gerektiriyor.

01-cost-of-change-tr.png

Görseldeki eğri şematik, bir ölçüm değil. Martin Fowler (Refactoring ve Patterns of Enterprise Application Architecture kitaplarının yazarı) bu fikri 2007'de Design Stamina Hypothesis adıyla yazdı ve özellikle "hipotez" dedi: tasarım kalitesi de üretkenlik de objektif olarak ölçülemediği için kanıtlanamıyor. Fikir şu: tasarıma ve okunabilirliğe hiç zaman ayırmayan kod başta daha hızlı ilerler. Her değişiklik bir sonrakinin okunmasını biraz daha zorlaştırdıkça bu avantaj erir. Bir noktadan sonra, Fowler'ın design payoff line dediği çizgiden sonra, temiz tutulan kod hem daha hızlı değişir hem daha az hata üretir. Martin de 2008 baskısında aynı şeyi "dağınıklığın toplam maliyeti" olarak anlatır: ekip büyüdükçe üretkenlik düşer, çünkü her yeni satır eski dağınıklığın üstüne yazılır.

Bu eğriden çıkan pratik ders şu: kısa ömürlü bir script için temizliğe yatırım yapmak gerekmeyebilir, çünkü çizgiye hiç ulaşmayacaksın. Yıllarca yaşayacak bir sipariş modülü ise çizgiyi çoktan geçmiştir. OrderService için soru "temizlemeye değer mi" değil, "her gün ne kadar faiz ödüyoruz".

Clean Code Neyi Kastediyor?

Martin, 2008 baskısının ilk bölümünde tanımı tek başına yapmaz; Bjarne Stroustrup (C++'ın yaratıcısı), Grady Booch (UML (Unified Modeling Language) dilinin mimarlarından), Dave Thomas (OTI (Object Technology International) şirketinin kurucusu), Michael Feathers (Working Effectively with Legacy Code kitabının yazarı), Ron Jeffries ve Ward Cunningham'ın (ikisi de XP (Extreme Programming) yaklaşımının kurucularından; Cunningham wiki'nin de mucidi) cevaplarını yan yana koyar. Ortak noktalar şunlar: kod okuyana sürpriz yapmaz, her parça tek bir işi iyi yapar, tekrar içermez, testleri vardır ve yazanın "özen gösterdiği" belli olur. Hiçbiri satır sayısından, süslü parantezin yerinden ya da belirli bir dil özelliğinden bahsetmez.

Bu yüzden Clean Code bir stil kılavuzu değil. Girinti, satır uzunluğu ve isim büyük-küçük harfleri ekip kararıdır ve bir araçla zorlanır (bunu ilerideki Coding Conventions ve Ekip Standartları serisinde konuşacağız). Clean Code ise daha zor soruları sorar: bu method kaç iş yapıyor, bu isim niyeti anlatıyor mu, bu hata nerede ele alınmalı, bu class neden değişir?

Bir de şunu baştan söyleyeyim: Clean Code bir kurallar listesi olarak okunduğunda yanlış anlaşılmaya çok açık. "Fonksiyon 4 satırı geçmesin" gibi cümleler bağlamından koparılınca, okunması daha zor kod üretebilir. 2020'de qntm'nin (yazılımcı ve bilim kurgu yazarı Sam Hughes'un takma adı) yazdığı "It's probably time to stop recommending Clean Code" eleştirisi tam olarak buna, kitaptaki bazı örnek kodların kendi kurallarıyla çeliştiğine odaklanıyor. 2024–2025'te John Ousterhout (Stanford profesörü, Tcl dilinin ve A Philosophy of Software Design kitabının yazarı) ile Martin arasındaki uzun yazışma da method uzunluğu ve comment'ler üzerine. Bu seride kuralları gerekçesi ve sınırıyla anlatacağım; tartışmanın tamamını 13. yazıda ayrıca ele alacağız.

2008'den 2025'e: İkinci Baskıda Ne Değişti?

İlk baskı Ağustos 2008'de çıktı (telif yılı 2009), 464 sayfa ve 17 bölüm. Örnekler neredeyse tamamen Java'ydı; son bölümler ise SerialDate gibi gerçek kodların adım adım temizlendiği vaka çalışmalarıydı. İkinci baskı 2025 sonbaharında Addison-Wesley'den çıktı. Yayıncının kendi sayfalarında 30 Eylül ve 8 Ekim 2025 olmak üzere iki tarih geçiyor, telif yılı 2026; InformIT (Pearson'ın online kitapçısı) 672 sayfa veriyor. Yayıncı kitabı "ilk baskının kapsamlı bir yeniden yazımı" olarak tanıtıyor.

Konu1. baskı (2008)2. baskı (2025)
Yapı17 bölüm; son dördü vaka çalışmaları ve Smells and Heuristics4 kısım: Code, Design, Architecture, Craftsmanship
DillerAğırlıkla JavaJava, JavaScript, Go, Python, Clojure, C# ve C
FonksiyonlarTek bölüm: FunctionsClean Functions, Function Heuristics, The Clean Method, One Thing, Be Polite
Error handlingAyrı bölümFunction Heuristics içinde "Prefer Exceptions to Returning Error Codes"
BoundariesKod seviyesinde ayrı bölümArchitecture kısmında Clean Boundaries
TestUnit TestsTesting Disciplines, Clean Tests, Acceptance Testing
TasarımEmergence (Simple Design)Simple Design, SOLID (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion), Component Principles, Continuous Design
Yeni—AI (Artificial Intelligence) ve LLM'lerle (Large Language Model) programlama bölümü, ek olarak Ousterhout tartışması

Tablodaki başlıklar Pearson'ın yayımladığı içindekiler listesinden. Birkaç şey dikkat çekiyor. Fonksiyonlar ve okunma sırası tek bölümden beş bölüme çıkmış; kitabın ağırlık merkezi hâlâ method seviyesi. SOLID ve component prensipleri artık aynı kitabın içinde; Martin bunları daha önce ayrı kitaplarda anlatmıştı. "AIs, LLMs, and God Knows What" başlıklı bir bölüm var. Ve kitabın sonunda, Ousterhout ile yaptığı yazışma "The Clean Code Debate" adıyla ek olarak duruyor; yani eleştiri kitabın içine alınmış. Ousterhout tartışmasında Martin, method'ları ne kadar bölmek gerektiği konusunda ikinci baskıda "daha dengeli" davrandığını söylüyor.

Seri boyunca bölüm numarası değil kavram adı kullanacağım. Perakendecilerin içindekiler listelerinde numaralar birbirini tutmuyor ve bir sonraki baskıda yine değişebilir; "Command Query Separation" ise değişmez.

Serinin Hastası: Legacy OrderService

Seri boyunca tek bir dosyayı temizleyeceğiz: Bookstore'un OrderService.cs'i. 609 satır; OrderService class'ının kendisi 462 satır, içinde 20 method var. Dosyada ayrıca 11 tip daha duruyor: Order, OrderItem, Book, Customer, Address, City, verileri bellekte tutan static Db, e-postayı konsola yazan Mailer, hiç kullanılmayan bir generic interface ve ödeme sağlayıcısının SDK'sını (Software Development Kit) taklit eden Vendor.Payments.PaymentClient ile ChargeResult. Kod #nullable disable ile başlıyor, block-scoped namespace kullanıyor ve .NET 10'da hiç uyarı vermeden derleniyor.

Dosyanın ana karakteri 136 satırlık ProcessData. Tamamı burada; acele etmeden oku ve kafanda "bu method kaç iş yapıyor" sorusunu say:

// Siparişi işler
// 03.05.2019 A.K. - kupon eklendi
// 17.11.2020 M.T. - express kargo eklendi
// 09.02.2022 A.K. - VIP indirimi eklendi
// 21.06.2023 S.Y. - hafta sonu indirimi eklendi
public int ProcessData(int cId, Dictionary<string, int> d, string strCoupon, bool flag, bool sendMail, int t)
{
    LastError = null;
    Customer c = null;
    foreach (var x in Db.Customers)
    {
        if (x.Id == cId)
        {
            c = x;
        }
    }
    if (c != null)
    {
        if (d != null && d.Count > 0)
        {
            if (c.IsBlocked == false)
            {
                Order o = new Order();
                o.Id = Db.NextOrderId++;
                o.Customer = c;
                o.Date = DateTime.Now;
                o.Status = 0;
                o.Items = new List<OrderItem>();
                decimal tmp = 0;
                foreach (var kv in d)
                {
                    Book b = Db.Books.FirstOrDefault(x => x.Isbn == kv.Key);
                    if (b == null)
                    {
                        LastError = "Book not found: " + kv.Key;
                        return -2;
                    }
                    if (b.Stock < kv.Value)
                    {
                        LastError = "Not enough stock: " + b.Title;
                        return -3;
                    }
                    OrderItem oi = new OrderItem();
                    oi.Book = b;
                    oi.Qty = kv.Value;
                    // fiyatı hesapla
                    if (b.Type == "ebook")
                        oi.Price = b.Price * 0.8m;
                    else if (b.Type == "audio")
                        oi.Price = b.Price * 0.9m;
                    else
                        oi.Price = b.Price;
                    tmp += oi.Price * oi.Qty;
                    o.Items.Add(oi);
                }
                // indirim
                decimal tmp2 = 0;
                if (strCoupon != null && strCoupon != "")
                {
                    if (strCoupon.ToUpper() == "WELCOME10")
                        tmp2 = tmp * 0.1m;
                    else if (strCoupon.ToUpper() == "SUMMER25")
                        tmp2 = 25;
                    else
                    {
                        LastError = "Invalid coupon";
                        return -4;
                    }
                }
                if (c.Type.ToUpper() == "VIP")
                {
                    tmp2 += tmp * 0.05m;
                }
                if (o.Items.Count > 5) tmp2 += 15;
                if (DateTime.Now.DayOfWeek == DayOfWeek.Saturday || DateTime.Now.DayOfWeek == DayOfWeek.Sunday)
                    tmp2 += tmp * 0.02m;
                // kargo
                decimal k = KargoUcretiHesapla(tmp - tmp2, t, c.Address.City.Name);
                if (flag) k += 12.5m; // hediye paketi
                o.Discount = tmp2;
                o.Shipping = k;
                o.Total = Math.Round((tmp - tmp2) * 1.1m + k, 2); // %10 vergi
                // ödeme
                try
                {
                    var r = pc.Charge(c.CardToken, (long)(o.Total * 100), "TRY");
                    if (r.Code != "00")
                    {
                        LastError = "Payment failed: " + r.Message;
                        o.Status = 9;
                        Db.Orders.Add(o);
                        return -5;
                    }
                    o.PaymentRef = r.TransactionId;
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.Message);
                    return -99;
                }
                // stok düş
                foreach (var oi in o.Items) { oi.Book.Stock = oi.Book.Stock - oi.Qty; }
                o.Status = 1;
                Db.Orders.Add(o);
                cnt++;
                // puan ekle
                c.Points = c.Points + (int)(o.Total / 10);
                if (c.Type == "vip") c.Points += 50;
                if (sendMail)
                {
                    StringBuilder sb = new StringBuilder();
                    sb.Append("Dear " + c.Name + ",\n");
                    sb.Append("Your order #" + o.Id + " has been received.\n");
                    foreach (var oi in o.Items)
                        sb.Append(" - " + oi.Book.Title + " x" + oi.Qty + " = " + (oi.Price * oi.Qty).ToString("0.00") + "\n");
                    sb.Append("Shipping: " + o.Shipping.ToString("0.00") + "\n");
                    sb.Append("Total: " + o.Total.ToString("0.00") + " TRY\n");
                    Mailer.Send(c.Email, "Order #" + o.Id, sb.ToString(), false);
                }
                log.AppendLine(DateTime.Now + " order " + o.Id + " total " + o.Total);
                if (debug) Console.WriteLine(log.ToString());
                return o.Id;
            }
            else
            {
                LastError = "Customer blocked";
                return -6;
            }
        }
        else
        {
            LastError = "Empty cart";
            return -1;
        }
    }
    else
    {
        LastError = "Customer not found";
        return -7;
    }
}

Diğer üyelerin imzaları ve uzunlukları:

public class OrderService
{
    public static string LastError;
    public static int cnt = 0;
    private PaymentClient pc = new PaymentClient("merchant-001", 30);
    private Dictionary<string, object> cache = new Dictionary<string, object>();
    StringBuilder log = new StringBuilder();
    public bool debug = false;

    public int ProcessData(int cId, Dictionary<string, int> d, string strCoupon, bool flag, bool sendMail, int t)  // 136 satır
    public decimal GetQuote(int cId, Dictionary<string, int> d, string coupon, int t)                            // 24
    public decimal KargoUcretiHesapla(decimal amount, int t, string city)                                        // 20
    public bool CancelOrder(int id, bool refund, bool notify)                                                    // 25
    public Order GetOrder(int id)                                                                                // 6
    public List<Order> GetOrders(int cId)                                                                        // 6
    public bool IsValid(Order o)                                                                                 // 11
    public bool CheckStock(string isbn, int qty)                                                                 // 11
    public bool TryCalc(Order o, out decimal subtotal, out decimal tax, out string err)                          // 8
    public bool ShipOrder(int id, bool express)                                                                  // 22
    public int CalcPoints(Customer c)                                                                            // 8
    public int CreateCustomer(string name, string email, string street, string city, string zip,
        string country, string phone, string type, bool newsletter)                                              // 18
    public string Report(DateTime from, DateTime to, bool csv)                                                   // 16
    public string FaturaOlustur(int orderId)                                                                     // 22
    public void UpdateStatus(int id, int s)                                                                      // 20
    public int ImportBooks(string path)                                                                          // 30
    private decimal OldCalcDiscount(decimal total, int itemCount)                                                // 5
    public void SendMail(string to, string subject, string body, bool html)                                      // 8
    public object GetFromCache(string key)                                                                       // 5
    public void ClearAll()                                                                                       // 4
}

Bu kod bilerek abartılı yazılmadı. Her parçası gerçek projelerde gördüğüm alışkanlıklardan geliyor: hata kodu olarak negatif sayı dönen method'lar, static bir LastError, 0'dan 9'a kadar anlamı yalnız bir comment'te yazan status değerleri, bool parametrelerle davranış seçen method'lar, Türkçe ve İngilizce karışık isimler, kopyalanıp ayrışmış iş kuralları. Kimse bunu tek seferde böyle yazmadı; dosyanın başındaki tarihli comment'ler her ekleyenin "küçük bir değişiklik" yaptığını gösteriyor.

Ölçüm de aynı şeyi söylüyor. SonarAnalyzer'ın (SonarSource şirketinin C# için yazdığı Roslyn analyzer paketi) S3776 (Cognitive Complexity) kuralı ve .NET'in CA1502 kuralı (CA: Code Analysis) açıkken derleme şunları raporladı:

OrderService.cs(31,20): warning S3776: Refactor this method to reduce its Cognitive Complexity from 92 to the 15 allowed.
OrderService.cs(31,20): warning CA1502: 'ProcessData' has a cyclomatic complexity of '27'. Rewrite or refactor the code to decrease its complexity below '26'.

Cognitive Complexity 92, varsayılan eşik 15. Bu sayının nasıl hesaplandığını fonksiyonlar yazısında açacağım; şimdilik iç içe geçmiş dört if seviyesinin okuyucuya ne kadar yük bindirdiğinin bir göstergesi olarak not et.

Bu Seride Neyi Nerede Temizleyeceğiz?

Dosyadaki her problem bir yazının konusu:

  • İsimler (d, tmp2, k, t, strCoupon, KargoUcretiHesapla): naming yazısı.
  • 136 satırlık method, dört seviyeli if pyramid'i, flag ve sendMail parametreleri, CheckStock'un gizli side effect'i: fonksiyonlar yazısı.
  • Changelog comment'leri, commented-out ProcessDataV2, "silme!" uyarısı: comment'ler yazısı.
  • Tek dosyada on iki tip, tek satırlık LINQ (Language Integrated Query) zinciri: formatting yazısı.
  • string Type, int Status, public field'lar, o.Customer.Address.City.Name: object ve data structure yazısı.
  • -1...-99 return code'ları, yutulan exception'lar, null dönen method'lar: error handling yazısı.
  • PaymentClient ve ChargeResult.Code == "00" her yere sızmış: boundary'ler yazısı.
  • 462 satırlık tek class: class'lar yazısı. Sonra testler, simple design, smell kataloğu, tartışma ve son yazıda hepsinin birleştiği adım adım temizlik.

Bir uyarı: her yazı kendi konusuna odaklanacak ve örneklerini kendi içinde çalışır tutacak. OrderService'in son hâlini, characterization test'lerle (Michael Feathers'ın terimi: kodun bugünkü davranışını olduğu gibi sabitleyen testler) korunarak ve her adımı ayrı commit olarak, 14. yazıda göreceğiz.

Sık Düşülen Tuzaklar

Clean Code'u stil kılavuzu sanmak. Süslü parantez tartışması on dakikada biter ve bir araçla kapanır. GetQuote'un ProcessData'dan ayrışması ise hiçbir formatter'ın yakalamayacağı bir tasarım problemi. Code review'da enerjini ikinciye harca.

Kuralları dogmaya çevirmek. "Method'lar en fazla 5 satır" gibi bir kural takımda hedef hâline gelirse, insanlar bir işi on küçük parçaya böler ve okuyucu dosyada zıplamaktan yorulur. Kuralın amacı okuyucunun yükünü azaltmak; yükü artırıyorsa kural yanlış yerde uygulanıyordur.

Her şeyi baştan yazmak istemek. Dağınık kodu görünce ilk refleks "bunu sıfırdan yazalım" olur. Ama OrderService'in içindeki banker's rounding, İstanbul'a 10 lira kargo indirimi gibi kurallar hiçbir yerde yazılı değil; yeniden yazan ekip bunları ancak müşteriler şikâyet edince tekrar keşfeder. Bu serinin yöntemi önce test ile davranışı sabitlemek, sonra küçük adımlarla temizlemek.

Boy Scout Rule'u büyük bir temizlik PR'ı (pull request) sanmak. Kural "dokunduğun yeri biraz daha temiz bırak" diyor, "dokunduğun dosyayı baştan formatla" demiyor. Bin satırlık bir formatting PR'ı review edilemez ve git blame geçmişini gömer.

Temiz kodu performansın düşmanı sanmak ya da tam tersine, performansı hiç düşünmemek. Casey Muratori'nin (performans odaklı programlama üzerine yazan oyun geliştiricisi) 2023'teki "Clean Code, Horrible Performance" yazısı, polymorphism ve küçük virtual çağrıların sıcak döngülerde ciddi maliyeti olabileceğini ölçerek gösteriyor. Bu, sipariş modülünde değil, saniyede milyonlarca kez dönen kodda önemli. Nerede ölçüp kuralı bilinçli esneteceğini 13. yazıda konuşacağız.

Ne Zaman Bu Kadar Uğraşma

Bir kere çalışıp atılacak bir veri taşıma script'i, bir fikri denemek için yazılan prototip ya da bir hackathon kodu design payoff line'a hiç ulaşmaz. Okunabilir isimler yine bedava, ama bu kodu katmanlara bölmek, testle sarmak, her method'u küçültmek yatırımın geri dönmeyeceği bir iş. Dikkat etmen gereken tek şey prototipin sessizce canlıya taşınması: OrderService de muhtemelen 2017'de "şimdilik böyle kalsın" diye başladı.

Küçük Bir Deneme

★☆☆ Programı tr-TR yerine InvariantCulture ile çalıştır ve VIP indiriminin geri geldiğini gör. Sonra ToUpper() çağrılarını string.Equals(c.Type, "vip", StringComparison.OrdinalIgnoreCase) ile değiştir; iki culture'da da aynı "charged" değerini aldığını doğrula.

★★☆ ProcessData'yı okuyup yaptığı işleri sırayla bir listeye yaz (müşteriyi bul, sepeti doğrula, fiyatla…). Kaç madde çıktı? Her madde için "bu iş değişirse hangi satırlara dokunurum" sorusunu cevapla.

★★★ GetQuote ile ProcessData'nın aynı sepet için hangi koşullarda farklı sonuç verdiğini bulan küçük bir döngü yaz: dört kitabı, üç müşteriyi, iki kuponu ve üç kargo tipini dolaş, farkları yazdır. Bulduğun her fark, ileride yazacağımız characterization test'lerin bir satırı olacak.

Bir sonraki yazıda ilk temizliğe isimlerden başlıyoruz: d, tmp2 ve k gibi isimleri domain diliyle yeniden adlandıracak, .NET'in naming guideline'larına bakacak ve KargoUcretiHesapla ile FaturaOlustur'un yanında ProcessData duran bir kod tabanında Türkçe mi İngilizce mi sorusuna cevap arayacağız.