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

Clean Code'da Naming: Niyet Anlatan İsimler, .NET Naming Guideline'ları ve Türkçe-İngilizce Karışımı

Mert Özen 7 Kas 2026 14 dk 5 görüntülenme
Clean Code'da Naming: Niyet Anlatan İsimler, .NET Naming Guideline'ları ve Türkçe-İngilizce Karışımı

İyi isim, okuyucunun kodu açmadan soracağı soruları cevaplar: legacy OrderService'teki d, tmp2, k ve int t isimlerini domain diliyle ve sabitlerle değiştirmek davranışı değiştirmeden (beş senaryoda aynı toplam) fiyat hesabını okunur kılıyor. Yazı .NET naming kurallarını, Hungarian notation'ı, Türkçe-İngilizce karışık isimleri ve ToLower()'ın tr-TR'de Id'yi ıd yapan Turkish-I tuzağını gerçek çıktılarla anlatıyor.

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

Önce Tahmin Et

Bir önceki yazıda tanıştığımız legacy OrderService'i çağıran bir controller'da şu satır duruyor:

int result = service.ProcessData(1, cart, "WELCOME10", true, false, 2);

Method'un imzası da şöyle:

public int ProcessData(int cId, Dictionary<string, int> d, string strCoupon, bool flag, bool sendMail, int t)

Dosyanın içini açmadan cevaplamaya çalış: true ne demek, false ne demek, 2 ne demek? d sözlüğünün key'i ne, value'su ne? Dönen int bir sipariş numarası mı, bir hata kodu mu? Sonra method'un gövdesinde geçen tmp, tmp2 ve k değişkenlerinin neyi tuttuğunu tahmin et.

İsim Bir Contract'tır

Cevaplar method'u okuyunca çıkıyor: true hediye paketi (12.5 lira ekler), false "e-posta gönderme", 2 express kargo. d'nin key'i ISBN (International Standard Book Number), value'su adet. Dönen int pozitifse sipariş numarası, negatifse yedi farklı hata kodundan biri. tmp indirimsiz ara toplam, tmp2 toplam indirim, k kargo ücreti. Bunları öğrenmek için 136 satırlık method'u okumak zorunda kaldın; çağrının yapıldığı satır sana hiçbir şey söylemedi.

Robert C. Martin'in (Clean Code kitabının yazarı) naming kuralının özü budur: isim, okuyucunun koda girmeden sorması gereken soruları cevaplamalı. Bu değişken neden var, ne yapıyor, nasıl kullanılıyor? İsmin bir comment'e ihtiyacı varsa, isim işini yapmıyor demektir. İkinci baskıda da Meaningful Names bölümü "Use Intention-Revealing Names" ile açılıyor; kural on yedi yılda değişmedi.

ProcessData'nın fiyat hesaplayan kısmı bugün şöyle:

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

Aynı mantığı, yapısına dokunmadan yalnız isimleri ve sabitleri değiştirerek yeniden yazdım. Tek fark, geçersiz kupon için -4 yerine exception fırlatması; return code'ları error handling yazısında konuşacağız.

using Bookstore.Legacy;

namespace Bookstore.Ordering;

public enum ShippingMethod
{
    Standard = 1,
    Express = 2,
    StorePickup = 3
}

public sealed class OrderPricing
{
    private const string EBookFormat = "ebook";
    private const string AudiobookFormat = "audio";
    private const decimal EBookPriceFactor = 0.8m;
    private const decimal AudiobookPriceFactor = 0.9m;

    private const string WelcomeCouponCode = "WELCOME10";
    private const decimal WelcomeCouponRate = 0.1m;
    private const string SummerCouponCode = "SUMMER25";
    private const decimal SummerCouponAmount = 25m;
    private const decimal VipDiscountRate = 0.05m;
    private const int BulkOrderMinLineCount = 6;
    private const decimal BulkOrderDiscount = 15m;
    private const decimal WeekendDiscountRate = 0.02m;

    private const decimal GiftWrapFee = 12.5m;
    private const decimal TaxRate = 0.10m;

    private const decimal FreeShippingThreshold = 500m;
    private const decimal StandardShippingFee = 39.90m;
    private const decimal IstanbulStandardShippingDiscount = 10m;
    private const decimal ExpressShippingFee = 89.90m;

    public decimal CalculateTotal(
        Customer customer,
        IReadOnlyDictionary<string, int> quantitiesByIsbn,
        string? couponCode,
        bool includeGiftWrap,
        ShippingMethod shippingMethod)
    {
        decimal subtotal = 0;
        foreach ((string isbn, int quantity) in quantitiesByIsbn)
        {
            Book book = Db.Books.First(b => b.Isbn == isbn);
            decimal unitPrice = book.Type switch
            {
                EBookFormat => book.Price * EBookPriceFactor,
                AudiobookFormat => book.Price * AudiobookPriceFactor,
                _ => book.Price
            };
            subtotal += unitPrice * quantity;
        }

        decimal discount = 0;
        if (!string.IsNullOrEmpty(couponCode))
        {
            discount = couponCode.ToUpper() switch
            {
                WelcomeCouponCode => subtotal * WelcomeCouponRate,
                SummerCouponCode => SummerCouponAmount,
                _ => throw new ArgumentException($"Invalid coupon: {couponCode}", nameof(couponCode))
            };
        }

        // davranış aynı kalsın diye ToUpper() bilerek duruyor; düzeltmesi ayrı commit
        if (customer.Type.ToUpper() == "VIP") discount += subtotal * VipDiscountRate;
        if (quantitiesByIsbn.Count >= BulkOrderMinLineCount) discount += BulkOrderDiscount;
        DayOfWeek today = DateTime.Now.DayOfWeek;
        if (today is DayOfWeek.Saturday or DayOfWeek.Sunday) discount += subtotal * WeekendDiscountRate;

        decimal discountedSubtotal = subtotal - discount;
        decimal shippingFee = CalculateShippingFee(discountedSubtotal, shippingMethod, customer.Address.City.Name);
        if (includeGiftWrap) shippingFee += GiftWrapFee;

        return Math.Round(discountedSubtotal * (1 + TaxRate) + shippingFee, 2);
    }

    private static decimal CalculateShippingFee(decimal discountedSubtotal, ShippingMethod method, string cityName) =>
        method switch
        {
            ShippingMethod.Standard when discountedSubtotal >= FreeShippingThreshold => 0m,
            ShippingMethod.Standard when cityName == "Istanbul" => StandardShippingFee - IstanbulStandardShippingDiscount,
            ShippingMethod.Standard => StandardShippingFee,
            ShippingMethod.Express => ExpressShippingFee,
            ShippingMethod.StorePickup => 0m,
            _ => throw new ArgumentOutOfRangeException(nameof(method), method, "Unknown shipping method")
        };
}

Method'u bölmedim, sırasını değiştirmedim, hiçbir kuralı düzeltmedim. Yine de okuması belirgin biçimde kolaylaştı: discountedSubtotal * (1 + TaxRate) satırı "vergi ara toplamın indirimli hâline ekleniyor" cümlesini kendisi söylüyor. Eski koddaki (tmp - tmp2) * 1.1m için bunu çıkarmak zorundaydın.

İsim değiştirmek davranış değiştirmemeli. Bunu iddia etmek yerine ölçtüm: aynı beş senaryoyu legacy ProcessData ve yeni CalculateTotal ile çalıştıran küçük bir program yazdım:

customer 1, Standard    legacy:  214.04  renamed:  214.04
customer 2, Standard    legacy:  233.94  renamed:  233.94
customer 2, Express     legacy:  268.94  renamed:  268.94
customer 1, Express     legacy: 1156.92  renamed: 1156.92
customer 2, StorePickup legacy: 1245.09  renamed: 1245.09

İsimlendirme bir şeyi daha ortaya çıkardı. Legacy kodda if (o.Items.Count > 5) tmp2 += 15; yazıyor. Sabite isim verirken ne saydığını yazmak zorunda kaldım ve BulkOrderMinLineCount oldu: indirim 5'ten fazla adette değil, 5'ten fazla farklı kitapta veriliyor. Aynı kitaptan on adet alan müşteri bu indirimi alamıyor. Bu bir hata mı, iş kuralı mı? Bilmiyorum; ama artık sorulacak soru görünür hâlde. İyi bir isim bazen kodun gizli varsayımlarını kâğıda döker.

VIP kontrolündeki ToUpper()'ı da bilerek bıraktım. Rename commit'i ile bug fix commit'i ayrı olmalı; birini review eden kişi diğerini aramak zorunda kalmamalı.

Disinformation ve Noise Word'ler

Kötü isim yalnız eksik bilgi vermez, bazen yanlış bilgi verir. Legacy dosyadan birkaç örnek:

IsValid(Order o) bir soru gibi görünüyor ama siparişin Status'ünü 2 yapıyor ve log yazıyor. Bir view'da "bu sipariş geçerli mi" diye çağıran kişi farkında olmadan siparişi onaylamış oluyor.

GetOrders(int cId) adı bir liste vaat ediyor, sipariş yoksa null dönüyor. Çağıran foreach yazdığı anda NullReferenceException alır.

cnt bir sayaç, ama neyi sayıyor? Kodu okuyunca yalnız başarılı siparişleri saydığını görüyorsun; ödeme hatası alanlar sayılmıyor.

flag hediye paketi demek. flag kelimesi yalnız tipini söylüyor, anlamını değil.

Martin 2008 baskısında bunlara "disinformation" der: okuyucuyu yanlış yöne iten isimler. Kitaptaki klasik örnek, aslında HashSet olan bir değişkene accountList demek. .NET'te sık gördüğüm hali orderList adında bir IEnumerable<Order> sorgusu: isim materialize edilmiş bir liste vaat ediyor, ama her foreach'te sorgu baştan çalışıyor.

Martin'in "make meaningful distinctions" kuralı da aynı aileden. ProcessData ve yorum satırına alınmış ProcessDataV2 arasındaki fark ne? Order, OrderInfo ve OrderData diye üç class görürsen hangisini kullanacağını isimden çıkaramazsın. Data, Info, Manager, Processor, Helper gibi kelimeler (Martin bunlara noise word der) isme bir şey eklemez; bir ismi "farklı görünsün" diye uzatır. ProcessData aslında sipariş veriyor; doğru adı PlaceOrder.

Searchable Name'ler ve Magic Number'lar

Vergi oranını yüzde 10'dan yüzde 20'ye çıkarmak gerekse legacy kodda neyi değiştirirdin? Önce aramak lazım:

$ grep -n "0\.1m\|1\.1m" OrderService.cs
86:                                tmp2 = tmp * 0.1m;
107:                        o.Total = Math.Round((tmp - tmp2) * 1.1m + k, 2); // %10 vergi
185:                if (coupon.ToUpper() == "WELCOME10") tmp2 = tmp * 0.1m;
191:            return Math.Round((tmp - tmp2) * 1.1m + k, 2);
286:            tax = (subtotal - o.Discount) * 0.1m;
378:            sb.AppendLine("Tax: " + ((ara - o.Discount) * 0.1m).ToString("0.00"));

Altı satır çıktı ve hepsi aynı şey değil. 86 ve 185 numaralı satırlardaki 0.1m vergi değil, WELCOME10 kuponunun yüzde 10 indirimi. Vergi ise iki yerde 1.1m, iki yerde 0.1m olarak yazılmış. Arama ya fazla buluyor ya eksik; hangisinin vergi olduğunu yine satır satır okuyarak ayırıyorsun. TaxRate adında tek bir sabit olsaydı, arama tek satır döndürürdü ve değişiklik tek satır olurdu.

Magic number için kural basit: anlamı olan her literal'e bir isim ver. Sabit bir değerse const, bir değerler kümesinden biriyse enum. Legacy'deki int t parametresi 1, 2, 3 alıyor; bunların ne olduğunu ancak KargoUcretiHesapla'yı okuyunca öğreniyorsun. ShippingMethod enum'ı ile çağrı ShippingMethod.Express olur ve derleyici geçersiz bir değeri yazmanı zorlaştırır.

Her sayıya isim vermek de gerekmez. Döngüdeki 0, bir koleksiyonun ilk element'i, * 100 gibi açık bir dönüşüm genellikle kendini anlatır. Sorulacak soru "bu sayı değişirse kaç yere dokunurum ve onları bulabilir miyim".

.NET'in Naming Kuralları

Clean Code isimlerin anlamıyla ilgilenir; büyük-küçük harf ve önekler ise platformun kuralıdır. .NET'te iki kaynak var: Framework Design Guidelines (Krzysztof Cwalina ve Brad Abrams'ın aynı adlı kitabının 2008 tarihli 2. baskısından Microsoft Learn'e aktarılan bölümler) ve .NET runtime ekibinin coding style dokümanı. Microsoft Learn'deki C# identifier names sayfası ikincisini benimsiyor.

NeKuralÖrnek
Type, method, property, event, public fieldPascalCaseOrderPricing, CalculateTotal
Parametre, local değişkencamelCasecouponCode, discountedSubtotal
private/internal instance field_ + camelCase_shippingFees
private/internal static fields_ + camelCase (runtime stili)s_defaultCulture
Sabit (const)PascalCase, private olsa daTaxRate
InterfaceI önekiIPaymentGateway
Awaitable dönen methodAsync sonekiPlaceOrderAsync
bool propertyOlumlu ifade, gerekirse Is/Has/CanIsBlocked, CanShip
Generic type parametresiT ya da T önekli açıklayıcı isimTOrder

s_ öneki için not: Framework Design Guidelines public static field'larda önek kullanma der, runtime stili ise private static field'lar için s_ önerir. İkisi çelişmez, farklı görünürlükler için. bool'larda dikkat edilecek nokta olumsuz isimden kaçınmak: IsNotBlocked gibi bir isim !IsNotBlocked yazdığında okuyucuyu çift olumsuzla uğraştırır. Async soneki yalnız awaitable dönen method'lar içindir; senkron bir method'a Async eklemek ya da async bir method'dan çıkarmak çağıranı yanıltır.

Bu kuralların çoğunu ekip .editorconfig'te naming rule olarak tanımlayıp IDE'ye (Integrated Development Environment) zorlatabilir; bunun ayrıntısı ilerideki Coding Conventions serisinin konusu.

Hungarian Notation Neden Öldü?

Legacy'deki strCoupon bir Hungarian notation kalıntısı: değişkenin tipini isminin başına yazmak (str, i, b, lst). Tip sisteminin zayıf olduğu ve editörlerin tip göstermediği dönemlerde anlamı vardı. Bugün IDE tipi fareyle üstüne gelince gösteriyor, derleyici yanlış tipi kullanmanı engelliyor. Önek ise bir bakım yükü olarak kalıyor: strCoupon'u yarın bir Coupon value object'ine çevirdiğinde isim yalan söylemeye başlar ve kimse onu değiştirmeyi hatırlamaz. Framework Design Guidelines bunu açıkça yasaklar: Hungarian notation kullanma. İsme tip değil anlam yaz: couponCode.

Türkçe mi İngilizce mi?

Legacy dosyada ProcessData, GetQuote gibi İngilizce isimlerin yanında KargoUcretiHesapla, FaturaOlustur, faturaNo ve ara (ara toplam) duruyor. Türkiye'deki projelerin çoğunda bu karışım var ve genelde bir karar sonucu değil, kodu kimin yazdığına göre oluşuyor. Bedeli şu: bir kavramı aramak için iki kelimeyi denemen gerekir (Invoice mı Fatura mı), aynı kavram iki isimle iki yerde yaşar ve yeni gelen biri hangi dilin kural olduğunu bilemez.

İki makul seçenek var:

  • A: Kod tamamen İngilizce, yanında bir glossary (terim sözlüğü). Fatura → Invoice, İrsaliye → DeliveryNote, Kargo → Shipment, Sepet → Cart, KDV (Katma Değer Vergisi) → Vat (Value Added Tax). Kazancı: .NET API'leriyle (Application Programming Interface) aynı dil, yabancı bir geliştirici ya da kütüphane dokümanıyla sürtünme yok. Bedeli: çeviri tartışması ve Türkiye'ye özgü kavramlarda (e-Arşiv fatura, Vergi Kimlik Numarası) doğal bir İngilizce karşılığın olmaması. Bu durumda tek bir isim seçip (EArchiveInvoice, TaxIdNumber) sözlüğe ve XML (Extensible Markup Language) doc comment'e Türkçe aslını yazmak işe yarar.
  • B: Domain kavramları Türkçe, teknik kelimeler İngilizce: FaturaService, KdvOrani. Kazancı: iş birimiyle aynı kelimeyi kullanırsın, ürün sahibinin "e-Arşiv" dediği şey kodda da öyledir. Bedeli: her isim iki dilin karışımı olur ve ASCII'ye (American Standard Code for Information Interchange) çevirme kararları (Ucret mi Ücret mi) bitmez.

Ben A'yı öneriyorum ve bu seride de öyle yapacağız; ama asıl kural "bir seçim yap ve sözlüğe yaz". Domain-Driven Design'daki (DDD; Eric Evans'ın 2003 tarihli kitabıyla tanınan yaklaşım) ubiquitous language fikri de bunu söyler: iş birimiyle aynı kavramları kullan ve her kavramın kodda tek bir adı olsun. Hangi dilde olduğu ikinci planda.

Türkçe Karakterli Identifier'lar ve Turkish-I

C# identifier'larında Unicode harflere izin verir, yani kargoÜcreti derlenir. Ama Türkçe karakterin asıl tehlikesi identifier'da değil, string'e dönüşen isimlerde. Property isimlerinden CSV (Comma-Separated Values) başlığı üreten şu kod Türkçe culture'da çalışıyor:

using System.Globalization;

CultureInfo.CurrentCulture = new CultureInfo("tr-TR");

// CSV başlıklarını property isimlerinden üret
string header = string.Join(";", typeof(BookRow).GetProperties().Select(p => p.Name.ToLower()));
Console.WriteLine(header);

string invariantHeader = string.Join(";", typeof(BookRow).GetProperties().Select(p => p.Name.ToLowerInvariant()));
Console.WriteLine(invariantHeader);

// Türkçe karakterli identifier derlenir
decimal kargoÜcreti = 39.90m;
Console.WriteLine($"{nameof(kargoÜcreti)} = {kargoÜcreti}");

public sealed record BookRow(int Id, string Isbn, string Title, decimal Price);
ıd;ısbn;title;price
id;isbn;title;price
kargoÜcreti = 39,90

Id ve Isbn küçültülünce noktasız "ı" ile başlıyor, çünkü Türkçede büyük "I"nin küçüğü "ı". Bu başlıkla üretilen CSV'yi okuyan sistem isbn kolonunu bulamaz. ToLowerInvariant() ile aynı kod doğru çalışıyor. Aynı problem bir önceki yazıdaki "vip".ToUpper()'da ters yönde ortaya çıkmıştı.

Bunu code review'da yakalamak zor, ama .NET analyzer'ları yakalıyor. CA1304 ve CA1311 (CA: Code Analysis) varsayılan olarak kapalı; .editorconfig'te warning yapınca derleme şunları verdi:

Program.cs(6,85): warning CA1311: Specify a culture or use an invariant version to avoid implicit dependency on current culture
Program.cs(6,78): warning CA1304: The behavior of 'string.ToLower()' could vary based on the current user's locale settings. Replace this call in '<top-level-statements-entry-point>' with a call to 'string.ToLower(CultureInfo)'.

Son satırdaki 39,90 de aynı ailenin üyesi: decimal culture'a göre virgülle yazıldı. Kod içi isimlerde Türkçe karakterden kaçınmak çoğu ekipte zaten kural; asıl alışkanlık, isimden string üreten her yerde invariant culture kullanmak. Karşılaştırma içinse string.Equals(a, b, StringComparison.OrdinalIgnoreCase).

Sık Düşülen Tuzaklar

Public bir ismi değiştirmenin contract'ı kırdığını unutmak. IDE'nin rename'i derleyicinin gördüğü her yeri günceller, ama JSON'daki (JavaScript Object Notation) alan adını, reflection ile okunan property'yi ya da başka bir servisin gönderdiği payload'ı göremez. Basit bir record'da Qty'yi Quantity yapınca:

using System.Text.Json;
using System.Text.Json.Serialization;

Console.WriteLine(JsonSerializer.Serialize(new OrderLineV1("978-0441013593", 2)));
Console.WriteLine(JsonSerializer.Serialize(new OrderLineV2("978-0441013593", 2)));
Console.WriteLine(JsonSerializer.Serialize(new OrderLineV3("978-0441013593", 2)));

// Eski client hâlâ "Qty" gönderiyor
OrderLineV2? fromOldClient = JsonSerializer.Deserialize<OrderLineV2>("""{"Isbn":"978-0441013593","Qty":2}""");
Console.WriteLine(fromOldClient);

public sealed record OrderLineV1(string Isbn, int Qty);
public sealed record OrderLineV2(string Isbn, int Quantity);
public sealed record OrderLineV3(string Isbn, [property: JsonPropertyName("Qty")] int Quantity);
{"Isbn":"978-0441013593","Qty":2}
{"Isbn":"978-0441013593","Quantity":2}
{"Isbn":"978-0441013593","Qty":2}
OrderLineV2 { Isbn = 978-0441013593, Quantity = 0 }

Son satır en tehlikelisi: eski client Qty göndermeye devam ediyor ve sipariş satırı hiçbir hata vermeden 0 adetle oluşuyor. İç isim temizlenebilir, ama dışarıya açık isim için JsonPropertyName ile eski adı koru ya da versiyonla.

Çok uzun isimler. CalculateTotalPriceIncludingTaxAndShippingForCustomer her şeyi söylüyor ama okunmuyor. İsmin uzunluğu scope'una uygun olmalı: üç satırlık bir lambda'da b yeterli, class seviyesinde bir field'da _shippingFees gerekli. Context zaten bir şey söylüyorsa tekrarlama: OrderPricing içindeki method'un adı CalculateOrderPricingTotal değil, CalculateTotal.

Kısaltmalar. Calc, Mgr, Cust, Qty yazanın kafasında açık, okuyanın kafasında tahmin. Framework Design Guidelines kısaltma ve yaygın olmayan acronym kullanmamayı söyler; Isbn, Vat, Http gibi herkesin bildiği acronym'ler istisna.

Rename'i davranış değişikliğiyle aynı commit'e koymak. Yüzlerce satırlık rename diff'inin içinde bir > işaretinin >= olduğunu kimse görmez. Önce rename, testler yeşil; sonra ayrı commit'te düzeltme.

İsmi kodla senkron tutmamak. Bir method'un işi değişir ama adı kalır. CheckStock bir zamanlar gerçekten yalnız kontrol ediyordu; bugün stoğu düşüyor. İsim de koddur ve kodla birlikte refactor edilir.

Küçük Bir Deneme

★☆☆ Legacy CancelOrder(int id, bool refund, bool notify)'ı çağıran bir satır yaz, sonra aynı çağrıyı C#'ın named argument'larıyla (refund: true, notify: false) tekrar yaz. Hangisini okumak daha kolay? Named argument isim problemini çözüyor mu, yoksa yalnız örtüyor mu?

★★☆ Legacy'deki CreateCustomer, Report ve ShipOrder method'larındaki her magic number ve magic string'i listele. Hangilerine isim verirsin, hangilerini olduğu gibi bırakırsın? ShipOrder'daki "Turkey" karşılaştırması için bir isim yeter mi, yoksa başka bir şeye mi ihtiyaç var?

★★★ Bir .editorconfig'e CA1304, CA1309 ve CA1311 kurallarını warning olarak ekle ve legacy OrderService.cs'i derle. Kaç uyarı çıkıyor? Her birini invariant ya da ordinal karşılığıyla düzelt ve bir önceki yazıdaki programla VIP (Very Important Person) indiriminin tr-TR'de geri geldiğini doğrula.

Bir sonraki yazıda isimleri düzelttiğimiz bu fiyat hesabını bölmeye başlıyoruz: 136 satırlık ProcessData'yı guard clause'larla düzleştirecek, flag ve sendMail gibi bool parametrelerin aslında iki ayrı method olduğunu görecek ve Cognitive Complexity'nin 92'den nereye indiğini ölçeceğiz.