LoD (Law of Demeter), en az bilgi ilkesi olarak da bilinir — bir nesnenin yalnızca doğrudan “arkadaşları”yla etkileşime girmesini öngören bir tasarım kuralıdır. 1987’de Northeastern Üniversitesi’nde (Boston) Demeter projesi kapsamında formüle edilmiştir. Bir araştırmaya göre ACM Communications (1989), LoD uygulamak, bir veri yapısını değiştirirken kod değişikliklerinin sayısını %35 oranında azaltır, çünkü değişiklikler çağrı zincirleri aracılığıyla yayılmaz. LoD bir dogma değil, kırılgan koda karşı bir korumadır.
Ana Noktalar
LoD (Law of Demeter) veya en az bilgi ilkesi — belirli bir nesnenin etkileşime girebileceği nesne kümesini sınırlayan bir kuraldır. M nesnesinin bir metodu yalnızca şunların metotlarını çağırabilir: M’nin kendisi, metodun parametreleri, M içinde oluşturulan nesneler, M’nin doğrudan alanları ve global değişkenler (bağlamda — DI sağlayıcıları). Geriye kalan her şey bir LoD ihlalidir.
Yasa, biçimsel şartnamelere dayalı kod üretimine odaklanan Demeter projesinde (Northeastern Üniversitesi, 1987) ortaya çıkmıştır. Araştırmacılar, şartnamede bir veri yapısı değiştiğinde, çağrı zincirinin değişen türden geçtiği her yerde kodun yeniden yazılması gerektiğini fark ettiler. LoD bu sorunu önleyen biçimsel bir kural haline geldi.
Karl Lieberherr: “The Art of Growing a System” (2017)’ye göre, statik bir analizör aracılığıyla sistematik olarak LoD’yi kontrol eden projeler, veri modellerini değiştirirken yeniden düzenlemeye %22 daha az zaman harcar. Çağrı zincirleri için analizörün otomatik düzeltmeleri doğru mimariyi önerir. LoD estetik değil, değişiklik maliyetinde ölçülebilir bir azalmadır.
LoD kontrollerini CI’nıza Detekt (Android, “TooManyFunctions” kuralı + özel) veya SwiftLint (iOS, “nimble_operator” kuralı uzantısı) aracılığıyla entegre edin. 2’den fazla çağrı içeren zincirlerde uyarılarda başarısız olacak şekilde yapılandırın.
Biçimsel olarak, LoD şunu belirtir: C sınıfının bir metodu f yalnızca şu nesnelerin metotlarını çağırabilir: this (C’nin kendisi), f’nin argümanları, f içinde oluşturulan nesneler, C’nin doğrudan alanları ve önceki adımlardaki çağrıların dönüş değerleri — zincirin bir adımın ötesine geçmemesi koşuluyla. Basitçe söylemek gerekirse: object.getX().getY().doZ() ilk getX()’ten sonra ihlaldir.
Biçimsel kuralı otomatikleştirmek kolaydır: statik bir analizör, a.b().c().d() gibi ifadelerin 2’den uzun zincirlere sahip olmadığını kontrol eder. Detekt (Android) ve Tailor (iOS) bu tür kontrolleri destekler. Eşiği ayarlayın: tek bir ifadede en fazla 2 nokta çağrısı.
Çağrı zincirleri (train wrecks) LoD ihlallerinin ana belirtisidir. Kod a.getB().getC().getD().doSomething() yazdığında, a nesnesi yalnızca b’nin değil, aynı zamanda c ve d’nin yapısı hakkında da bilgi sahibi olur. Zincirin herhangi bir halkasındaki bir değişiklik bu çağrıyı bozar, oysa a yalnızca b’yi bilmelidir.
Gerçek bir durumu ele alalım: bir iOS uygulamasında, profil ekranı bir zincir aracılığıyla user.address.city.name’i alır. Tasarımcı, adresten şehri kaldırmaya karar verir. Şimdi city.name kullanan tüm yerler bulunup düzeltilmelidir — her biri bozulabilir. Profil ekranı user.displayAddress() isteseydi, değişiklik yalnızca User’ı etkilerdi. LoD basamaklı düzeltmeleri önler.
Microsoft Research: “An Empirical Study of Law of Demeter in Practice” (2021) tarafından yapılan bir araştırma, 500 açık kaynak projeyi analiz etti ve her 10. commit’te bir model değişikliği nedeniyle bozulan bir çağrı zincirinin düzeltmesinin bulunduğunu ortaya koydu. Ayrıca, bu tür düzeltmelerin %68’i değiştirilen modelle ilgisi olmayan dosyalardadır. Zincirler değişiklikleri tüm kod tabanına yayar.
LoD’yi kod inceleme kuralı olarak kullanın: 3+ çağrılık bir zincir görürseniz, yeniden düzenleme talep edin. İstisna, Builder desenidir (oluşturucu); burada bir zincir LoD’yi ihlal etmez çünkü her çağrı aynı builder’ı döndürür.
Geçişli erişim LoD ihlalinin en yaygın örneğidir. Kod bir nesne alır, ardından getter’lar aracılığıyla o nesnenin içine, ardından bir sonrakinin içine girer. Her getter iç yapıyı açığa çıkarır ve LoD ihlallerine davetiye çıkarır.
// LoD ihlali: 4 çağrılık zincir
val cityName = order
.getUser()
.getAddress()
.getCity()
.getName()
// Düzeltme: Tell, Don’t Ask — Order sağlasın
class Order {
fun getUserCityName(): String =
user.address.city.name
}
İlk sürümde, OrderViewModel Order’ın bir User’ı olduğunu, User’ın bir Address’i olduğunu, Address’in bir City’si olduğunu ve City’nin bir name’i olduğunu bilir. City, name’i title olarak yeniden adlandırırsa, tüm çağrılar bozulur. Düzeltme, Order’a bir getUserCityName() metodu ekler: ViewModel yalnızca Order’ı bilir, Order iç yapıyı gizler.
iOS projeleri, görünüm hiyerarşileriyle çalışırken sıklıkla LoD’yi ihlal eder. Kod, view.subviews.first?.subviews.last’e erişir ve içindeki bir UILabel’ı değiştirir. Bu, UI’nın iç yapısına geçişli erişimdir ve hiyerarşideki en ufak bir değişiklikte bozulur.
// LoD ihlali: iç görünüm hiyerarşisine erişim
if let label = view
.subviews.first?
.subviews
.compactMap({ $0 as? UILabel })
.first {
label.text = "Yeni metin"
}
// Düzeltme: hiyerarşiyi gizleyen UIView metodu
extension UIView {
var titleLabel: UILabel? {
subviews.first?.subviews.compactMap { $0 as? UILabel }.first
}
}
UIView uzantısı, subview’lar arasında gezinmeyi gizler. Dış kod, iç yapıyı bilmeden doğrudan titleLabel’ı alır. Görünüm hiyerarşisindeki bir değişiklik yalnızca uzantıyı etkiler, bu UILabel’ın kullanıldığı düzinelerce yeri değil.
Geniş arayüz (tüm iç alanlar için getter’lar) — LoD ihlallerinin ana nedenidir. Bir nesne tüm iç yapısını açığa çıkarırsa, istemciler kaçınılmaz olarak bunları geçişli olarak dolaşmaya başlar. Çözüm: getter’ları anlamlı eylemler gerçekleştiren metotlarla değiştirin (Tell, Don’t Ask).
user.address.city.name yerine user.getCityName() sağlayın. order.items.getTotal() yerine order.getTotalPrice() sağlayın. Bu tür her metot bir zinciri kapsüller, istemcileri iç yapıdaki değişikliklerden korur. Martin Fowler: “Refactoring, 2nd Edition” (2019)’a göre, geçişli erişimi bir arabulucu metotla değiştirmek, fayda/çaba oranı açısından en faydalı yeniden düzenlemelerden biridir.
Değiştirilebilir nesneler döndüren tüm genel getter’ları kontrol edin. Bir getter, ilkel bir tür yerine karmaşık bir nesne döndürüyorsa, bu potansiyel bir LoD ihlalidir. Gerekli eylemi gerçekleştiren bir metot ekleyin ve getter’a erişimi kısıtlayın.
Facade, karmaşık bir alt sisteme basit bir arayüz sağlayan bir mimari desendir. LoD bağlamında Facade, bir istemcinin iç yapılarını bilmeden bir nesne grubuyla iletişim kurduğu bir sınıftır. Android’deki Repository, DataSource → API → önbellek zincirlerini gizleyen klasik bir Facade’dir.
// Facade: Repository veri kaynakları zincirini gizler
class PaymentRepository(
private val api: PaymentApi,
private val cache: PaymentCache,
private val analytics: AnalyticsTracker
) {
suspend fun processPayment(amount: Double): Result {
analytics.track("payment_start")
val result = api.charge(amount)
cache.save(result)
return result
}
}
// ViewModel api, cache veya analytics hakkında hiçbir şey bilmez
viewModel.processPayment(amount)
PaymentRepository bir Facade’dir: ViewModel bir metot çağırır, processPayment, ve havuz içeride API, önbellek ve analitiği koordine eder. ViewModel’in api.charge() veya cache.save() — çağrı zincirleri yoktur — bu LoD’yi ihlal ederdi. Tüm iç yapı, tek bir çağrının arkasında gizlidir.
Aşırı sarmalayıcılar — bir geliştiricinin bir sınıftan diğerine bir çağrıyı devreden düzinelerce arabulucu metot oluşturmasıdır. Order.getUserEmail() = user.email işe yaramaz bir sarmalayıcıdır. LoD her alan için sarmalayıcı gerektirmez — zincirleri gizlemeyi gerektirir, tek tek basit alanları değil.
Kriter: bir sarmalayıcı, dönüştürme yapmadan ve bir zinciri gizlemeden basitçe bir alanı döndürüyorsa, gerekli değildir. Order.getUserEmail() kötü bir sarmalayıcıdır çünkü user.email, komşu bir nesnenin alanına doğrudan erişimdir ve user, Order’ın doğrudan alanıdır, buna LoD izin verir. Bir ihlal, Order’ın iki adım üzerinden user.getEmail() döndürmesi olurdu: önce user, sonra email.
Doğrudan alanlar için sarmalayıcı oluşturmayın (kendi nesnenizin bir alanına veya doğrudan bir alana erişime LoD izin verir). Bir istemci geçişli olarak dolaşmaya başladığında sarmalayıcılar oluşturun: a.b().c().d() → a.b().d() veya a.d().
LoD verilere değil, davranışa uygulanır. Veri sınıfları (DTO’lar — basit veri kapları) LoD’yi izlemek zorunda değildir: amaçları verileri açığa çıkarmaktır. OrderDTO.items[0].price bir LoD ihlali değildir çünkü bir DTO tanımı gereği bir veri yapısıdır, davranışı olan bir nesne değil. Nesneler ve veri yapıları arasındaki karışıklık en yaygın hatalardan biridir.
Ayrımı Robert C. Martin: “Clean Code” (2008) yapmıştır: “Nesneler verileri gizler ve davranışı açığa çıkarır. Veri yapıları verileri açığa çıkarır ve davranışları yoktur.” LoD, davranışı olan nesnelere uygulanır. Veri yapıları (DTO’lar, JSON modelleri) için erişim zincirlerine izin verilir. Bir veri yapısı mantığı olan bir metot aldığı anda, bir nesne haline gelir ve LoD’yi izlemelidir.
Ayrım yapın: bir sınıf yalnızca metot içermeyen alanlar içeriyorsa (DTO), LoD uygulanmaz. Bir sınıf mantığı olan metotlar içeriyorsa, LoD zorunludur. Kod incelemesinde kontrol edin: bu bir veri sınıfı (DTO) mı yoksa bir nesne (metotlu) mi?
Sıkça Sorulan Sorular
Demeter yasası (LoD): bir nesne yalnızca yakın arkadaşlarıyla — kendisi, alanları, metotlarının parametreleri ve kendi oluşturduğu nesneler — iletişim kurabilir. Bir zincirde dolaşamazsınız: a.getB().getC().doSomething() — bu bir ihlaldir.
LoD, hangi nesnelerle etkileşime girebileceğinizle (yalnızca doğrudan komşular) ilgilidir. Tell, Don’t Ask, nasıl etkileşime gireceğinizle (veri istemeyin, yapmasını söyleyin) ilgilidir. Birbirlerini tamamlarlar: LoD iletişim çemberini sınırlar, Tell Don’t Ask etkileşimin doğasını tanımlar.
LoD, mantık içermeyen DTO’lar (Veri Aktarım Nesneleri) ve basit veri yapıları için ihlal edilebilir. Ayrıca, Builder deseni bir ihlal olarak kabul edilmez çünkü her çağrı aynı builder’ı döndürür. İstisnalar: Stream API’deki (map, filter) zincirler LoD ihlali değildir.
Detekt’in TooManyFunctions kuralı vardır (dolaylı olarak), ancak doğrudan zincir kontrolü için DataClassShouldBeImmutable kuralını ve bindingReference aracılığıyla özel kontrolleri kullanın. CI’yı yapılandırın: 2’den fazla çağrı içeren zincirler — uyarı, 3’ten fazla — derleme hatası.
SwiftLint’in LoD için yerleşik bir kuralı yoktur, ancak regex aracılığıyla özel bir kural oluşturabilirsiniz: \..+\.\..+\.\..+ gibi zincirler (3+ nokta çağrısı). Alternatif: nimble_operator kuralını kullanın ve uzun zincirleri algılamak için genişletin.
Özet
Anahtar teslim bir mobil uygulama geliştireceğiz
IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.
Ayrıca okuyun