The original article was published on January 23, 2025.
Hello, I'm Munetoshi Ishikawa, a mobile client developer for the LINE messaging app.
This article is the latest installment of our weekly series "Improving code quality". For more information about the Weekly Report, please see the first article.
The Law of Demeter
One of the programming principles is the Law of Demeter. This principle states that you should only operate on members (properties/methods) that you directly know, and you should not operate on "members of members". In other words, it is often described as "only talk to your friends". More precisely, when accessing members, the receiver (in many languages, the value to the left of the dot operator .) should be limited to the following:
this- Fields/properties of
this - Function arguments
- Objects created directly within the function
- Top-level/global values or singletons
In practice, function calls without a receiver can also be considered to follow the Law of Demeter.
Below is an example that follows the Law of Demeter.
val TOP_LEVEL_VALUE = ...
class Klass {
val property: Property = ...
fun thisFunction() { ... }
fun function(parameter: Parameter) {
// OK: Function with `this` as the receiver
thisFunction()
// OK: Member with `this` as the receiver
property.prop
// OK: Function argument as the receiver
parameter.func()
// OK: Object created directly within the function as the receiver
Value(42).prop
// OK: Top-level variable as the receiver
TOP_LEVEL_VALUE.func()
}
}
On the other hand, the following code does not follow the Law of Demeter.
class Klass {
val property: Property = ...
fun function(parameter: Parameter) {
// Bad: Receiver is the return value of a property method
property.func().prop
// Bad: Receiver is a property of an argument
val prop = parameter.prop
prop.anotherProp
}
}
Consider the following code. In this code, the receiver of filter is the return value of getAllUserModels, so it does not follow the Law of Demeter.
class Caller {
val repository: UserModelRepository = ...
fun function() {
val friendModels = repository.getAllUserModels()
.filter { it.isFriend }
...
}
}
Therefore, a developer thought it would be better to follow the Law of Demeter and refactored it as follows.
class Caller {
val repository: UserModelRepository = ...
fun function() {
val friendModels =
filterFriends(repository.getAllUserModels())
...
}
fun filterFriends(allUserModels: List<UserModel>): List<UserModel> =
allUserModels.filter { it.isFriend }
}
Is there any problem with this "refactoring"?
In a nutshell
Instead of superficially applying the Law of Demeter, be aware of the boundaries of knowledge.
Keywords: Law of Demeter, information hiding, encapsulation