LoD (Law of Demeter), juga dikenal sebagai prinsip pengetahuan minimal — aturan desain yang memerintahkan objek untuk hanya berinteraksi dengan “teman” langsung. Dirumuskan pada tahun 1987 di Northeastern University (Boston) dalam kerangka proyek Demeter. Menurut penelitian ACM Communications (1989), penerapan LoD mengurangi jumlah perubahan dalam kode saat modifikasi struktur data sebesar 35%, karena perubahan tidak menyebar melalui rantai panggilan. LoD — bukan dogma, tetapi perlindungan dari kode rapuh.
Utama
LoD (Law of Demeter), atau prinsip pengetahuan minimal — aturan yang membatasi lingkaran objek yang dapat berinteraksi dengan objek tertentu. Metode objek M hanya dapat memanggil metode dari: M itu sendiri, parameter metode, objek yang dibuat di dalam M, field langsung M dan variabel global (dalam konteks — penyedia DI). Segala sesuatu lainnya — pelanggaran LoD.
Hukum ini muncul dalam proyek Demeter (Northeastern University, 1987), yang bergerak dalam pembuatan kode berdasarkan spesifikasi formal. Peneliti memperhatikan: ketika struktur data berubah dalam spesifikasi, kode harus ditulis ulang di semua tempat di mana rantai akses melewati tipe yang diubah. LoD menjadi aturan formal yang mencegah masalah ini.
Menurut Karl Lieberherr: “The Art of Growing a System” (2017), proyek yang secara sistematis memeriksa LoD melalui penganalisis statis menghabiskan 22% lebih sedikit waktu untuk refactoring saat mengubah model data. Penganalisis auto-fix rantai panggilan menyarankan arsitektur yang benar. LoD — bukan estetika, tetapi pengurangan biaya perubahan yang terukur.
Terapkan pemeriksaan LoD di CI melalui Detekt (Android, aturan “TooManyFunctions” + kustom) atau SwiftLint (iOS, ekstensi “nimble_operator”). Konfigurasikan fail pada peringatan dengan rantai lebih dari 2 panggilan.
Secara formal LoD menyatakan: metode f dari kelas C hanya dapat memanggil metode dari objek berikut: this (C sendiri), argumen f, objek yang dibuat di dalam f, field langsung C dan nilai kembalian panggilan dari langkah sebelumnya — dengan batasan bahwa rantai tidak berlanjut lebih dari satu langkah. Lebih sederhana: object.getX().getY().doZ() — pelanggaran setelah getX() pertama.
Aturan formal mudah diotomatisasi: penganalisis statis memeriksa apakah dalam ekspresi berbentuk a.b().c().d() tidak ada rantai yang lebih panjang dari 2. Detekt (Android) dan Tailor (iOS) mendukung pemeriksaan semacam itu. Atur ambang batas: maksimum 2 panggilan melalui titik dalam satu ekspresi.
Rantai panggilan (chain calls, train wrecks) — gejala utama pelanggaran LoD. Ketika kode menulis a.getB().getC().getD().doSomething(), objek a mengambil pengetahuan tentang struktur tidak hanya b, tetapi juga c dan d. Perubahan pada mata rantai mana pun dalam rantai akan merusak panggilan ini, meskipun a seharusnya hanya tahu tentang b.
Pertimbangkan kasus nyata: dalam aplikasi iOS layar profil mendapatkan user.address.city.name melalui rantai. Desainer memutuskan untuk menghapus city dari alamat. Sekarang semua tempat di mana city.name digunakan harus ditemukan dan diperbaiki — masing-masing bisa rusak. Jika layar profil meminta user.displayAddress() — perubahan hanya akan mempengaruhi User. LoD mencegah perbaikan berantai.
Penelitian Microsoft Research: “An Empirical Study of Law of Demeter in Practice” (2021) menganalisis 500 proyek open-source dan menemukan: dalam setiap commit ke-10 terdapat perbaikan rantai panggilan yang rusak karena perubahan model. Sementara 68% dari perbaikan tersebut — dalam file yang tidak terkait dengan model yang diubah. Rantai menyebarkan perubahan ke seluruh basis kode.
Gunakan LoD sebagai aturan code review: jika Anda melihat rantai 3+ panggilan — minta refactoring. Pengecualian — Builder (konstruktor), di mana rantai tidak melanggar LoD, karena setiap panggilan mengembalikan builder yang sama.
Akses transitif — contoh paling umum dari pelanggaran LoD. Kode mendapatkan objek, kemudian melalui getter menembus ke dalam objek ini, kemudian ke dalam objek berikutnya. Setiap getter mengungkapkan struktur internal dan mengundang pelanggaran LoD.
// Pelanggaran LoD: rantai 4 panggilan
val cityName = order
.getUser()
.getAddress()
.getCity()
.getName()
// Perbaikan: Tell, Don't Ask — biarkan Order sendiri yang menyediakan
class Order {
fun getUserCityName(): String =
user.address.city.name
}
Dalam varian pertama, OrderViewModel tahu bahwa Order memiliki User, User memiliki Address, Address memiliki City, City memiliki name. Jika City mengganti nama name menjadi title — semua panggilan akan rusak. Perbaikan menambahkan metode getUserCityName() ke Order: ViewModel hanya tahu Order, Order menyembunyikan struktur internal.
Proyek iOS sering melanggar LoD saat bekerja dengan hirarki view. Kode mengakses view.subviews.first?.subviews.last dan memodifikasi UILabel di dalamnya. Ini — akses transitif ke struktur internal UI, yang rusak pada perubahan terkecil dalam hirarki.
// Pelanggaran LoD: akses ke hirarki internal view
if let label = view
.subviews.first?
.subviews
.compactMap({ $0 as? UILabel })
.first {
label.text = "Teks baru"
}
// Perbaikan: metode pada UIView yang menyembunyikan hirarki
extension UIView {
var titleLabel: UILabel? {
subviews.first?.subviews.compactMap { $0 as? UILabel }.first
}
}
Ekstensi UIView menyembunyikan navigasi melalui subviews. Kode eksternal mendapatkan titleLabel secara langsung, tanpa mengetahui struktur internal. Perubahan hirarki view hanya akan mempengaruhi ekstensi, bukan puluhan tempat di mana UILabel ini digunakan.
Antarmuka lebar (getter ke semua field internal) — penyebab utama pelanggaran LoD. Jika objek mengungkapkan semua bagian internalnya, klien pasti akan mulai berjalan secara transitif melaluinya. Solusi: ganti getter dengan metode yang melakukan tindakan bermakna (Tell, Don't Ask).
Alih-alih user.address.city.name, sediakan user.getCityName(). Alih-alih order.items.getTotal(), sediakan order.getTotalPrice(). Setiap metode semacam itu — adalah enkapsulasi rantai yang melindungi klien dari perubahan struktur internal. Menurut Martin Fowler: “Refactoring, 2nd Edition” (2019), penggantian akses transitif dengan metode perantara — salah satu refactoring paling berguna dalam hal rasio manfaat/upaya.
Periksa semua getter publik yang mengembalikan objek mutable. Jika getter mengembalikan bukan primitif, tetapi objek kompleks — ini adalah potensi pelanggaran LoD. Tambahkan metode yang melakukan tindakan yang diperlukan dan batasi akses ke getter.
Facade — pola arsitektur yang menyediakan antarmuka sederhana ke subsistem kompleks. Dalam konteks LoD, Facade adalah kelas di mana klien berkomunikasi dengan sekelompok objek, tanpa mengetahui struktur internal mereka. Repository di Android — Facade klasik yang menyembunyikan rantai DataSource → API → cache.
// Facade: Repository menyembunyikan rantai sumber data
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 tidak tahu tentang api, cache, maupun analytics
viewModel.processPayment(amount)
PaymentRepository — Facade: ViewModel memanggil satu metode processPayment, dan repository mengkoordinasikan API, cache dan analitik di dalam dirinya. ViewModel tidak memiliki rantai panggilan ke api.charge() atau cache.save() — ini akan melanggar LoD. Seluruh struktur internal tersembunyi di balik satu panggilan.
Wrapper berlebihan — ketika pengembang membuat puluhan metode perantara yang hanya mendelegasikan panggilan dari satu kelas ke kelas lain. Order.getUserEmail() = user.email — wrapper tidak berguna. LoD tidak memerlukan wrapper untuk setiap field — ia memerlukan penyembunyian rantai, bukan field sederhana individu.
Kriteria: jika wrapper hanya mengembalikan field tanpa transformasi dan tanpa menyembunyikan rantai — tidak diperlukan. Order.getUserEmail() — wrapper buruk, karena user.email adalah akses langsung ke field objek tetangga, dan user adalah field langsung Order, yang diizinkan oleh LoD. Pelanggaran akan terjadi jika Order mengembalikan user.getEmail() melalui dua langkah: pertama user, kemudian email.
Jangan buat wrapper untuk field langsung (akses ke field objek sendiri atau field langsung — diizinkan oleh LoD). Buat wrapper ketika klien mulai berjalan secara transitif: a.b().c().d() → a.b().d() atau a.d().
LoD diterapkan pada perilaku, bukan pada data. Data class (DTO — wadah data sederhana) tidak wajib mematuhi LoD: tujuannya adalah mengungkapkan data. OrderDTO.items[0].price — bukan pelanggaran LoD, karena DTO menurut definisi adalah struktur data, bukan objek dengan perilaku. Kebingungan antara objek dan struktur data — salah satu kesalahan paling umum.
Perbedaan dijelaskan oleh Robert C. Martin: “Clean Code” (2008): “Objek menyembunyikan data dan mengungkapkan perilaku. Struktur data mengungkapkan data dan tidak memiliki perilaku.” LoD berkaitan dengan objek dengan perilaku. Untuk struktur data (DTO, model JSON) rantai akses diizinkan. Segera setelah struktur memiliki metode dengan logika — ia menjadi objek dan harus mematuhi LoD.
Bedakan: jika kelas hanya berisi field tanpa metode (DTO) — LoD tidak berlaku padanya. Jika kelas berisi metode dengan logika — LoD wajib. Pada code review periksa: apakah ini data class (DTO) atau objek (dengan metode)?
Pertanyaan Umum
Hukum Demeter (LoD): objek hanya dapat berkomunikasi dengan teman dekat — dirinya sendiri, field-nya, parameter metodenya dan objek yang ia buat sendiri. Tidak boleh melalui rantai: a.getB().getC().doSomething() — ini pelanggaran.
LoD — tentang objek MANA yang dapat diakses (hanya tetangga langsung). Tell, Don't Ask — tentang BAGAIMANA mengakses (jangan meminta data, tetapi suruh melakukannya). Mereka saling melengkapi: LoD membatasi lingkaran komunikasi, Tell Don't Ask — sifat akses.
LoD dapat dilanggar untuk DTO (Data Transfer Objects) dan struktur data sederhana tanpa logika. Juga Builder tidak dianggap sebagai pelanggaran, karena setiap panggilan mengembalikan builder yang sama. Pengecualian: rantai di Stream API (map, filter) — bukan pelanggaran LoD.
Detekt memiliki aturan TooManyFunctions (tidak langsung), tetapi untuk pemeriksaan langsung rantai gunakan aturan DataClassShouldBeImmutable dan pemeriksaan kustom melalui bindingReference. Konfigurasikan CI: rantai lebih dari 2 panggilan — peringatan, lebih dari 3 — error build.
SwiftLint tidak memiliki aturan bawaan untuk LoD, tetapi dapat membuat aturan kustom melalui regex: rantai berbentuk \..+\.\..+\.\..+ (3+ panggilan melalui titik). Alternatif: gunakan aturan nimble_operator dan perluas untuk mendeteksi rantai panjang.
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga