Mert Özen Her Satırda Daha İleri
Yazılım Tasarımı SOLID ve Design Principles — #02

Coupling ve Cohesion: İyi Tasarımın İki Ölçüsü, Connascence ve Law of Demeter

Mert Özen 7 Kas 2026 15 dk 5 görüntülenme
Coupling ve Cohesion: İyi Tasarımın İki Ölçüsü, Connascence ve Law of Demeter

İyi tasarım, parçalar arası bağın (coupling) zayıf, parçanın içindeki bütünlüğün (cohesion) yüksek olmasıdır; connascence bu bağı türüne göre derecelendirir, Law of Demeter ise object'lerin birbirinin içine uzanmasını sınırlar. Coupling türleri ve cohesion seviyeleri, altı parametreli constructor'da connascence of position, TimeSpan(10) tuzağı ve Tell, Don't Ask ile cüzdan bakiyesini korumak.

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

Önce Tahmin Et

Bookstore'a yeni bir müşteri kaydı düşüyor. Kargo etiketi yazdırılacak ve sipariş yurt içi mi diye kontrol edilecek; yurt dışı gönderiye ayrıca ücret ekleniyor. Kod derleniyor, uyarı yok. İki satırın çıktısı ne?

Customer ada = new("Ada", "Lovelace", "ada@example.com", "Ankara", "06100", "Turkey");

Console.WriteLine(ShippingLabel.For(ada));
Console.WriteLine(ShippingLabel.IsDomestic(ada));

public sealed class Customer(
    string firstName, string lastName, string email,
    string postalCode, string city, string country)
{
    public string FirstName { get; } = firstName;
    public string LastName { get; } = lastName;
    public string Email { get; } = email;
    public string PostalCode { get; } = postalCode;
    public string City { get; } = city;
    public string Country { get; } = country;
}

public static class ShippingLabel
{
    public static string For(Customer c) => $"{c.FirstName} {c.LastName}, {c.PostalCode} {c.City}, {c.Country}";

    // Türkiye posta kodları 5 hanelidir
    public static bool IsDomestic(Customer c) => c.Country == "Turkey" && c.PostalCode.Length == 5;
}

Derleyicinin Göremediği Bağ

Çıktı:

Ada Lovelace, Ankara 06100, Turkey
False

Etikette şehir ile posta kodu yer değiştirmiş, Ada'nın siparişi de yurt dışı sayılmış. Hata çağıran tarafta: constructor postalCode, city sırasını bekliyor, çağıran city, postalCode sırasıyla vermiş. İkisi de string olduğu için derleyicinin itiraz edecek bir şeyi yok. Bu kodu yazan kişinin aklında bir sıra var, constructor'ı yazanın aklında başka bir sıra; ikisini birbirine bağlayan şey yalnızca parametrelerin konumu.

Bu yazıda bu tür bağları ölçmenin üç yolunu konuşacağız. İlki 1970'lerden kalan klasik ikili: coupling (iki parça arasındaki bağın gücü) ve cohesion (bir parçanın içindekilerin birbirine ne kadar ait olduğu). İkincisi, Meilir Page-Jones'un (object-oriented tasarım üzerine kitaplarıyla bilinen yazar ve danışman) 1992'de önerdiği ve bağı türüne göre derecelendiren connascence. Üçüncüsü, object'lerin birbirinin içine ne kadar uzanabileceğini sınırlayan Law of Demeter ve onun yakın akrabası Tell, Don't Ask. Önceki yazıda gördüğümüz rigidity ve fragility'nin kökünde hep bu bağlar vardı; şimdi onlara isim koyuyoruz.

Coupling ve Cohesion: İki Eksen

Bu iki kavram Wayne Stevens, Glenford Myers ve Larry Constantine'in (structured design yaklaşımının öncüleri) 1974'te IBM'in (International Business Machines) Systems Journal dergisinde yayımlanan "Structured Design" makalesinden geliyor; daha sonra Edward Yourdon ve Constantine'in 1979 tarihli Structured Design kitabıyla yaygınlaştı. Tanımlar kısa: coupling, bir module'ün başka bir module'e ne kadar bağımlı olduğu; cohesion, bir module'ün içindeki parçaların aynı amaca ne kadar hizmet ettiği. İyi tasarımın klasik formülü "low coupling, high cohesion" diye bilinir.

İki eksen bağımsız olduğu için dört kombinasyon ortaya çıkar:

02-coupling-cohesion-tr.png

Sol üstte istediğimiz yer var: tek bir işe odaklı, dışarıya dar bir yüzey açan bir parça. Sağ altta herkesin tanıdığı god class: siparişi de, faturayı da, bildirimi de yapan 2.400 satırlık bir OrderService; içi dağınık, herkes ona bağlı. Diğer iki köşe daha az konuşulur ama en az onun kadar yaygındır. Sağ üstteki parça kendi içinde düzenlidir ama komşularının iç ayrıntılarına bağlıdır; birazdan göreceğimiz reflection örneği buraya düşer. Sol alttaki parça kimseye bağlı değildir ama içinde birbiriyle ilgisiz şeyler vardır: her projede bir tane bulunan BookstoreUtils. Bir şeyi coupling'i azaltmak için parçalara bölüp cohesion'ı yok ettiğinde de buraya düşersin; bağ kaybolmaz, yalnızca görünmez olur.

Coupling Türleri: Kötüden İyiye

Klasik sınıflandırma coupling'i en güçlüden en zayıfa sıralar: content, common, external, control, stamp ve data. Bookstore'dan birer örnekle gidelim.

Content coupling, bir module'ün başka bir module'ün iç ayrıntısına doğrudan uzanması. C#'ta bunun en tipik hâli reflection ile private bir field'a erişmek:

using System.Reflection;

Order order = new();
order.Add("978-0441013593");
order.Add("978-0141439518");

Console.WriteLine(OrderAudit.CountItems(order));

public sealed class Order
{
    // Geçen sprint _items olan adı _lines yapıldı
    private readonly List<string> _lines = [];

    public void Add(string isbn) => _lines.Add(isbn);
}

public static class OrderAudit
{
    // Başka bir class'ın private field'ına uzanmak: content coupling
    public static string CountItems(Order order)
    {
        FieldInfo? field = typeof(Order).GetField("_items", BindingFlags.NonPublic | BindingFlags.Instance);
        return field?.GetValue(order) is List<string> items
            ? $"{items.Count} items"
            : "field '_items' not found";
    }
}
field '_items' not found

Order'ı yazan kişi field'ın adını değiştirmekte tamamen haklıydı; private bir ayrıntıydı. Ama OrderAudit o ayrıntıya bağlıydı ve derleyici bunu göremedi. Bu kod hata da fırlatmıyor, sessizce yanlış bir cevap dönüyor. InternalsVisibleTo ile test project'ine açılan internal üyeler ve serialization için private field adlarına bağlanan konfigürasyonlar da aynı riskin daha yumuşak hâlleri.

Common coupling, birden fazla module'ün aynı global, değiştirilebilir veriyi paylaşması:

using System.Globalization;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

decimal paid = Checkout.Pay(listPrice: 100m);
Console.WriteLine($"Paid on Monday:    {paid}");

CampaignJob.StartBlackFriday();

decimal refund = Returns.RefundFor(listPrice: 100m);
Console.WriteLine($"Refund on Friday:  {refund}");

// Herkesin okuyup yazabildiği tek bir global değer
public static class StoreSettings
{
    public static decimal DiscountRate = 0.10m;
}

public static class Checkout
{
    public static decimal Pay(decimal listPrice) => listPrice * (1 - StoreSettings.DiscountRate);
}

public static class CampaignJob
{
    public static void StartBlackFriday() => StoreSettings.DiscountRate = 0.40m;
}

public static class Returns
{
    // İade, ödenen tutarı değil bugünkü indirimi kullanıyor
    public static decimal RefundFor(decimal listPrice) => listPrice * (1 - StoreSettings.DiscountRate);
}
Paid on Monday:    90.00
Refund on Friday:  60.00

Müşteri pazartesi 90 ödedi, cuma iade olarak 60 aldı. Checkout, CampaignJob ve Returns birbirini hiç çağırmıyor ama üçü de aynı static field'a bağlı ve biri değiştirdiğinde diğer ikisinin davranışı değişiyor. Bu tür bağlar code review'da görünmez, çünkü değişikliği yapan dosyada diğer iki class'ın adı geçmez. Microsoft Learn'deki "Architectural principles" sayfası da değiştirilebilir global state'i encapsulation'ın tam karşıtı olarak tanımlar. External coupling bunun dış dünyadaki karşılığı: iki module'ün aynı dosya formatına, aynı protokole ya da aynı veritabanı tablosuna bağlı olması.

Control, stamp ve data coupling günlük kodda çok daha sık:

Customer ada = new(1, "Ada", "ada@example.com", "+905550000001");

// Control coupling: çağıran, çağrılanın içindeki akışı seçiyor
OrderMailer.Send(ada, orderId: 1001, isReminder: true);

// Stamp coupling: koca bir Customer gidiyor, yalnız Phone kullanılıyor
SmsSender.SendTo(ada, "Your order has shipped.");

// Data coupling: yalnız gereken veri gidiyor
SmsSender.Send(ada.Phone, "Your order has shipped.");

public sealed record Customer(int Id, string Name, string Email, string Phone);

public static class OrderMailer
{
    public static void Send(Customer customer, int orderId, bool isReminder)
    {
        string subject = isReminder ? $"Reminder: order {orderId} is waiting" : $"Order {orderId} confirmed";
        Console.WriteLine($"[mail] {customer.Email}: {subject}");
    }
}

public static class SmsSender
{
    public static void SendTo(Customer customer, string text) => Send(customer.Phone, text);

    public static void Send(string phone, string text) => Console.WriteLine($"[sms] {phone}: {text}");
}
[mail] ada@example.com: Reminder: order 1001 is waiting
[sms] +905550000001: Your order has shipped.
[sms] +905550000001: Your order has shipped.

isReminder parametresi control coupling'in klasik işareti: çağıran taraf, çağrılan method'un içinde hangi dalın çalışacağını biliyor ve seçiyor. Yarın üçüncü bir e-posta türü geldiğinde bool yetmeyecek, bir enum'a dönecek ve her çağıran yeri etkileyecek. Çoğu zaman çözüm, iki ayrı method yazmak. SendTo(Customer, ...) ise stamp coupling: yalnızca telefon numarası gereken method'a koca bir Customer veriliyor. Bu hem method'u Customer'ın şekline bağlıyor hem de test ederken gereksiz bir object kurmayı gerektiriyor. En zayıf ve en sağlıklı tür, yalnızca gereken verinin parametre olarak geçtiği data coupling. Burada bir uyarı: stamp coupling her zaman kötü değil. On parametreyi tek tek geçmek yerine anlamlı bir record geçmek çoğu zaman daha iyidir; mesele, alıcı tarafın gerçekten o yapının bütününe mi, yoksa tek bir parçasına mı ihtiyaç duyduğu.

Cohesion Seviyeleri

Cohesion için de aynı makaleden gelen bir sıralama var. Bir class'ın içindeki method'ları neyin bir arada tuttuğuna bakar:

SeviyeParçaları bir arada tutanBookstore'dan örnek
CoincidentalHiçbir şey; tesadüfen aynı yerdelerBookstoreUtils: slug üretimi, KDV (katma değer vergisi) hesabı, Slack bildirimi
LogicalAynı kategoriye girmeleri; çağıran bir bayrakla seçerExport(kind): CSV (Comma-Separated Values), PDF (Portable Document Format), Excel aynı method'da
TemporalAynı anda çalışmalarıStartup.Init(): cache ısıtma, log klasörü açma, kur çekme
ProceduralBelirli bir sırayla çalışmalarıSepeti doğrula, sonra kuponu kontrol et, sonra log yaz
CommunicationalAynı veri üzerinde çalışmalarıBir siparişin özetini, toplamını ve kargo ağırlığını hesaplayan method'lar
SequentialBirinin çıktısının diğerinin girdisi olmasıCSV satırını parse et, doğrula, Book'a çevir
FunctionalHepsinin tek ve iyi tanımlı bir göreve hizmet etmesiPostalCode: biçim kontrolü, karşılaştırma, yazdırma

.NET'in kendisinde iyi bir functional cohesion örneği System.IO.Path: Combine, GetExtension, GetFileName, GetTempPath hep aynı kavram, dosya yolu üzerinde çalışır. Karşıt örnek, birçok kod tabanındaki Helpers klasörü. Coincidental cohesion'ın asıl bedeli şu: BookstoreUtils.Slugify'ı kullanmak isteyen her project, Slack client'ına ve KDV kurallarına da bağımlı hâle geliyor. Bir parçanın cohesion'ı düşükse, ona bağlanan herkesin coupling'i istemeden artar; iki eksen bu yüzden birbirinden bağımsız ama birbirini etkileyen iki ölçü.

Connascence: Bağı Derecelendirmek

Coupling türleri "ne kadar kötü" sorusuna kaba bir cevap verir. Meilir Page-Jones 1992'de Communications of the ACM'de (Association for Computing Machinery'nin dergisi) yayımlanan makalesinde daha ince bir dil önerdi: iki parça connascent'tir, eğer birini değiştirdiğinde sistemin doğru çalışmaya devam etmesi için diğerini de değiştirmen gerekiyorsa. Connascence türleri kabaca zayıftan güçlüye şöyle sıralanır. Kaynak koda bakarak görülebilen static türler: name, type, meaning (convention), position, algorithm. Yalnızca çalışma anında ortaya çıkan dynamic türler: execution (sıra), timing, value, identity.

Yazının başındaki hata connascence of position: çağıran ile constructor parametrelerin sırası üzerinde anlaşmak zorunda. Altı parametreli bir constructor'da altı kalemlik gizli bir anlaşma var. Çözümün yönü, güçlü connascence'ı daha zayıf bir türe çevirmek. Burada iki adım atıyoruz: posta kodunu kendi tipine taşıyıp connascence of position'ı connascence of type'a çeviriyoruz, kalan string'ler için de named argument kullanıp connascence of name'e iniyoruz:

Customer ada = new(
    FirstName: "Ada",
    LastName: "Lovelace",
    Email: "ada@example.com",
    Address: new Address(new PostalCode("06100"), City: "Ankara", Country: "Turkey"));

Console.WriteLine(ShippingLabel.For(ada));
Console.WriteLine(ShippingLabel.IsDomestic(ada));

public readonly record struct PostalCode
{
    public PostalCode(string value)
    {
        if (value.Length != 5 || !value.All(char.IsAsciiDigit))
            throw new ArgumentException($"'{value}' is not a valid postal code.", nameof(value));

        Value = value;
    }

    public string Value { get; }

    public override string ToString() => Value;
}

public sealed record Address(PostalCode PostalCode, string City, string Country);

public sealed record Customer(string FirstName, string LastName, string Email, Address Address);

public static class ShippingLabel
{
    public static string For(Customer c) =>
        $"{c.FirstName} {c.LastName}, {c.Address.PostalCode} {c.Address.City}, {c.Address.Country}";

    // Geçerlilik kontrolü artık PostalCode'un içinde
    public static bool IsDomestic(Customer c) => c.Address.Country == "Turkey";
}
Ada Lovelace, 06100 Ankara, Turkey
True

Şimdi biri Address'in argümanlarını yine yanlış sırayla verirse:

Address: new Address("Ankara", new PostalCode("06100"), "Turkey"));
Program.cs(5,26): error CS1503: Argument 1: cannot convert from 'string' to 'PostalCode'
Program.cs(5,36): error CS1503: Argument 2: cannot convert from 'PostalCode' to 'string'

Hata artık müşteride değil, build'de yakalanıyor. Aynı anda posta kodunun biçim kuralı da tek bir yere taşındı; IsDomestic artık "5 hane" bilgisini bilmek zorunda değil. Bu, connascence of meaning'i azaltmanın da bir yolu: "5 haneli string posta kodudur" gibi bir convention'ı birden fazla yerin bilmesi yerine, bunu bilen tek bir tip var.

.NET'in kendisinde de connascence örnekleri bolca var. En sinsi olanlardan biri TimeSpan'in tek parametreli constructor'ı:

TimeSpan timeout = new(10);
Console.WriteLine(timeout);

Console.WriteLine(new TimeSpan(0, 10, 0));
Console.WriteLine(TimeSpan.FromSeconds(10));
Console.WriteLine(TimeSpan.FromMinutes(10));
00:00:00.0000010
00:10:00
00:00:10
00:10:00

new TimeSpan(10) on saniye değil, on tick (bir mikrosaniye) demek. Parametrenin anlamı tamamen bir convention'a bağlı; okuyanın long ticks overload'unu hatırlaması gerekiyor. new TimeSpan(0, 10, 0) da saat, dakika, saniye sırasına bağlı bir connascence of position. TimeSpan.FromSeconds ve FromMinutes ise anlamı method adına taşıyor; bu yüzden timeout ve gecikme değerlerinde factory method'ları tercih etmek iyi bir alışkanlık.

Page-Jones'un önerdiği diğer iki ölçü de en az tür kadar önemli. Locality: connascent iki parça birbirine ne kadar yakın? Aynı method içindeki iki satır arasındaki connascence of algorithm kabul edilebilir; biri API'de (Application Programming Interface), diğeri Angular uygulamasında duran aynı hash algoritması ise kabul edilemez. Degree: kaç parça bu bağa katılıyor? Bir constructor'ı iki yerden çağırmak ile iki yüz yerden çağırmak aynı risk değil. Kural basit: güçlü connascence'ı kısa mesafede tut, sınırları aşan bağları zayıf türlere indir.

Law of Demeter ve Tell, Don't Ask

Law of Demeter, Northeastern Üniversitesi'ndeki Demeter projesinden çıktı; Karl Lieberherr ve Ian Holland 1989'da IEEE'nin (Institute of Electrical and Electronics Engineers) IEEE Software dergisinde "Assuring Good Style for Object-Oriented Programs" başlığıyla yayımladı. Özü şu: bir method yalnızca kendi object'inin üyeleriyle, kendisine parametre olarak gelen object'lerle, kendi oluşturduğu object'lerle ve object'inin doğrudan field'larıyla konuşmalı. Kısaca "yabancılarla konuşma" diye anılır. Ödeme ekranındaki şu satıra bakalım:

using System.Globalization;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

Customer ada = new("Ada", new Wallet(balance: 20m));
Order order = new(1001, ada, total: 25.98m);

// Ödeme ekranındaki kod: üç nokta ötedeki bir field'ı değiştiriyor
order.Customer.Wallet.Balance -= order.Total;

Console.WriteLine($"Balance: {ada.Wallet.Balance}");

public sealed class Wallet(decimal balance)
{
    public decimal Balance { get; set; } = balance;
}

public sealed class Customer(string name, Wallet wallet)
{
    public string Name { get; } = name;
    public Wallet Wallet { get; } = wallet;
}

public sealed class Order(int id, Customer customer, decimal total)
{
    public int Id { get; } = id;
    public Customer Customer { get; } = customer;
    public decimal Total { get; } = total;
}
Balance: -5.98

Bakiye eksiye düştü. Kimse "cüzdan eksiye düşebilir" demedi, ama kural hiçbir yerde yazılı değildi; olsa bile bu satır onu atlayabilirdi, çünkü Balance'ın public setter'ı var ve çağıran üç object öteye uzanıp doğrudan değiştiriyor. Bu satır ödeme ekranını Order'ın, Customer'ın ve Wallet'ın iç yapısına aynı anda bağlıyor. Yarın cüzdan müşteriden ayrılıp bir hesap servisine taşınırsa, bu zinciri kullanan her yer değişecek.

Çözüm, davranışı verinin sahibine taşımak. Andy Hunt ve Dave Thomas'ın (The Pragmatic Programmer kitabının yazarları) Tell, Don't Ask olarak yaygınlaştırdığı fikir bu: object'e durumunu sorup kararı dışarıda verme, ne istediğini söyle, kararı o versin.

using System.Globalization;

CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;

Customer ada = new("Ada", new Wallet(balance: 20m));
Order order = new(1001, ada, total: 25.98m);

PaymentResult result = order.PayWithWallet();
Console.WriteLine($"{result} | balance: {ada.WalletBalance}");

string[] titles = ["Dune", "Emma", "The Hobbit", "Dracula"];
// Zincir aynı abstraction üzerinde kalıyor: her adım yeni bir IEnumerable döner
string shortTitles = string.Join(", ", titles.Where(t => t.Length <= 5).OrderBy(t => t).Select(t => t.ToUpperInvariant()));
Console.WriteLine(shortTitles);

public enum PaymentResult { Paid, InsufficientFunds }

public sealed class Wallet(decimal balance)
{
    public decimal Balance { get; private set; } = balance;

    public bool TryDebit(decimal amount)
    {
        ArgumentOutOfRangeException.ThrowIfNegativeOrZero(amount);
        if (amount > Balance)
            return false;

        Balance -= amount;
        return true;
    }
}

public sealed class Customer(string name, Wallet wallet)
{
    private readonly Wallet _wallet = wallet;

    public string Name { get; } = name;
    public decimal WalletBalance => _wallet.Balance;

    public PaymentResult Pay(decimal amount) =>
        _wallet.TryDebit(amount) ? PaymentResult.Paid : PaymentResult.InsufficientFunds;
}

public sealed class Order(int id, Customer customer, decimal total)
{
    public int Id { get; } = id;
    public decimal Total { get; } = total;

    // Siparişe "öde" diyoruz; cüzdanın nerede durduğunu bilmiyoruz
    public PaymentResult PayWithWallet() => customer.Pay(Total);
}
InsufficientFunds | balance: 20
DUNE, EMMA

Artık bakiye kuralı Wallet'ın içinde ve atlanamıyor; setter private. Ödeme ekranı yalnızca Order'ı tanıyor. Cüzdanın nerede durduğu değişirse yalnızca Customer değişecek.

Aynı programın son satırları neden ihlal sayılmadığını da gösteriyor. titles.Where(...).OrderBy(...).Select(...) zincirinde de birçok nokta var, ama hiçbiri bir object'in iç yapısında gezinmiyor; her adım aynı abstraction'ı (IEnumerable<string>) alıp yeni bir tane döndürüyor. StringBuilder.Append zinciri ve ASP.NET Core'daki builder.Services.AddScoped(...) gibi fluent API'ler de aynı şekilde aynı object'e ya da aynı türden bir sonuca dönüyor. Law of Demeter nokta saymak değil, yapı bilgisinin sızmasını engellemek. Aynı mantıkla, davranışı olmayan saf veri yapıları (bir DTO (Data Transfer Object), bir record) için zincirli property erişimi de sorun değil; orada saklanacak bir davranış yok.

Martin Fowler'ın (Refactoring kitabının yazarı) Tell, Don't Ask hakkındaki notu da akılda tutulmalı: Fowler bu kuralı kendisi kullanmadığını, onu davranış ile veriyi bir arada tutma hedefine giden bir basamak olarak gördüğünü söyler ve bütün getter'ları silmeye çalışan bir aşırılığa karşı uyarır. Bir raporun siparişlerin toplamını okuması gayet meşru; sorun, okunan değerle kararın dışarıda verilip object'in durumunun dışarıdan değiştirilmesi.

Sık Düşülen Tuzaklar

Coupling'i sıfırlamaya çalışmak. Birbiriyle hiç konuşmayan parçalardan sistem kurulmaz; amaç bağı yok etmek değil, doğru yere ve doğru güçte koymak. Her şeyi event'lerle ya da string anahtarlı mesajlarla birbirinden "ayırmak" çoğu zaman static bağı (derleyicinin gördüğü) dynamic bağa (yalnızca çalışma anında ortaya çıkan) çevirir; bu bir iyileşme değil, kötüleşmedir.

Interface eklemeyi coupling'i azaltmak sanmak. IOrderService adında, OrderService'in bütün public method'larını kopyalayan bir interface coupling'i azaltmaz; client hâlâ aynı geniş yüzeye bağlı. Bunun nasıl daha iyi yapılacağını Interface Segregation yazısında konuşacağız.

Law of Demeter'i "tek nokta kuralı" sanmak. order.Customer.Name yazdığın her yeri bir wrapper method'a çevirmek, Order'ı onlarca GetCustomerName, GetCustomerEmail method'uyla doldurur ve cohesion'ı düşürür. Sorulacak soru, zincirin bir davranış kararını sahibinden dışarı taşıyıp taşımadığı.

Connascence'ı yalnızca kod içinde aramak. En pahalı connascence'lar genellikle sınırları aşar: API ile client arasında paylaşılan bir JSON (JavaScript Object Notation) alan adı (name), iki servisin aynı sırayla çalışması gerektiği varsayımı (execution), iki uygulamada ayrı ayrı yazılmış aynı doğrulama kuralı (algorithm). Bunlar derleyicinin yakalayamayacağı yerde durur; contract test'ler ve tek kaynaktan üretilen client'lar burada işe yarar.

Küçük Bir Deneme

★☆☆ OrderMailer.Send'deki isReminder parametresini kaldırıp iki ayrı method'a böl. Çağıran yerler nasıl değişti? Üçüncü bir e-posta türü (kargoya verildi) eklemek şimdi kaç yere dokunmayı gerektiriyor?

★★☆ Common coupling örneğindeki iade hatasını, global state'i kaldırarak düzelt: Checkout.Pay uygulanan indirim oranını ödeme kaydıyla birlikte döndürsün, Returns bu kaydı kullansın. Bu değişiklik hangi connascence türünü hangisine çevirdi?

★★★ Kendi projendeki en uzun constructor'ı bul. Parametrelerden hangileri aynı tipte ve yan yana? Value object ve named argument ile connascence of position'ı azalt; ardından derleyicinin artık hangi hatayı yakaladığını gerçek bir yanlış sıralama yazarak doğrula.

Bir sonraki yazıda SOLID'in (Single Responsibility, Open/Closed, Liskov Substitution, Interface Segregation, Dependency Inversion) ilk harfine geçiyoruz. Single Responsibility Principle'ın yaygın "bir class tek şey yapar" yorumunun neden yanlış olduğunu, Robert C. Martin'in (Clean Code ve Clean Architecture kitaplarının yazarı) actor tanımını ve iki farklı ekibin aynı method'u paylaştığında ne olduğunu bir Employee class'ı üzerinden göreceğiz.