Hacker News new | past | comments | ask | show | jobs | submit login
Law of Demeter and immutability (enterprisecraftsmanship.com)
41 points by speckz on Sept 30, 2016 | hide | past | favorite | 36 comments



The author claims that Dementer's law is primarily there in order to prevent state corruption. I don't know if I agree with this premise. There are also 2 other reasons to follow Dementer's law:

1. Make it easier for your users to achieve specific goals through a single method call, instead of having to know & go through a chain of method calls. Ie, instead of having to know that the Dog object has Leg sub-objects, and having to call Dog.getLeg().selectMuscle().contract(), the user can instead just invoke Dog.move(). This enhances simplicity, and makes life easy for your users.

2. Hiding implementation details from your users, in order to maintain flexibility for future changes. If all your users are calling Dog.getLeg().selectMuscle().contract(), then you're forced to work with this Dog->Leg->Muscle implementation. On the other hand, if your users are simply calling Dog.move(), then you're free to refactor the class internal structure in dramatic ways.

The fact that an object is immutable, has little impact on the 2 benefits given above. Dementer's Law/Guideline would be well worth paying attention to for those reasons, even when dealing with ImmutableObjects.


Exactly, the law of Demeter is about coupling... not state corruption and immutability.

https://en.wikipedia.org/wiki/Law_of_Demeter

Whether those are mutable or immutable is irrelevant to the LoD.


There are two other aspects of Demeter that are more about how humans work and where they make mistakes.

One is that merges are hard (error prone), and mixing concerns results in many more merge conflicts. I 'joke' that half of the tenets of clean code are really about avoiding merge conflicts, but I'm quite serious.

Add to this that Demeter means that you can use local reasoning in many situations, which complements the structure of human working memory. I cannot juggle twelve facts when debugging issues, and Demeter helps you push potentially confounding issues elsewhere where they cannot distract from the matter at hand.


Coupling is an important concept but I think this concern can be mitigated by following the DRY principle. That is, if you have only one function that knows about the object’s internal structure (“couples” to it, so to speak), it doesn’t really matter where exactly that function is located – in the class itself or in some other place.

Also, your example isn't entirely valid as you mutate the objects' state.


Demeter's Law is also called the Principle of Least Knowledge. The reason being that, if you have two dogs -- one robotic and one mammalian -- reading the angle of the second joint distal from the hip is going to walk a distinct path for each type, which requires some otherwise-dog-agnostic piece of code to now know about what types of dogs exist and how to traverse their skeletal chains.

IMO Law of Demeter is not chiefly about state protection -- it's a principle of good design which, when followed, unburdens developers from the cognitive load of keeping the entire universe in their heads. It forces them to think about what is the smallest amount of information this component needs in order to function.


I do agree with this one.


It seems like you're trying to redefine what the LoD is because you think there's a more important principle it should refer to instead, or because you disagree with its original framing. That's not going to be a fruitful approach.


I came here to say exactly this. Those two benefits far outweigh state corruption. The law of Demeter is a good guide to a better API, which ends up being more effective.


There is in my mind another important benefit of following the Law of Demeter: it minimizes coupling.

Even in functional programming if you are digging into a data structure 5 levels deep, you are potentially coupling a function to 5 different structures, rather than just one.

For example: game.player.bbox.topLeft.x has the potential to break if the game, player, bbox, or point structure change, which makes it fragile. If using an IP language with inheritance, it could also break if any of their super classes change.


Coupling is what the LoD is about, not mutability, its not just another important benefit, its the main point.


Disagree with the premise. The purpose of Demeter isn't to prevent one class from mucking up the internal state of another, that's a side-effect. Its purpose is to prevent one class from being coupled to the internal structure of another so that if you change one, you don't have to change the other. It stops code updates, not state updates, from rippling out.

This is why the original formulation of the LoD didn't talk about instances, it talked about classes. You were only allowed to know about the surface-level structure of your neighbours, but anything else of the same class as your neighbours was fair game. So in the `player.Position.X` example, we actually don't have enough information to know if it's a violation or not: we don't know what class `player.Position` is, and we don't know whether it's a legitimate neighbour class. If there's another `Position` field somewhere in scope that's legit, `player.Position.X` is allowed, because we're already coupled to that structure, and getting to it via the `player` object doesn't add any coupling that wasn't there already.


Getting most object-oriented programmers to think about the static structure of their programs (e.g., classes rather than objects, variables rather than runtime values) is going to be an uphill battle.


> The law of Demeter is a guideline

I'm not really sure that much more had to be said than that. There are many instances (especially when you have a data structure that is also a class) where the law of Demeter is a guideline and just that.

However, the caveat is that this:

    int positionX = player.Position.X;
Should really look like this:

    if(player.Position != null) {
      int positionX = player.Position.X;
    }
In which case having a getter eliminates the need for error handling to be spittled all over the codebase. Having an accessor means you can nip checking for a null reference in the bud. There are other benefits as well.


... or you could just use a language which doesn't allow "null".


Or use a null safe member access operator like the one in c#.

    player?.position.x


If it's C#, you could also make Position be a struct that can't be null. Generally things like Points and Vectors are structs anyway in a lot of cases for performance reasons.


That doesn't really solve the problem. What if "x" is a nullable type (i.e. not a primitive type)?


The nulls get coalesced. In what way is it not solved?


"x" could be of a nullable type.


And the result of the entire expression is still nullable. I'm not seeing any problem.


Hm. What do you think "42 + null" is?


According to c# (the language I'm talking about) I think it's null. That seems correct to me. Arithmetic on null operands logically seems to have a null result to me. I'm not sure what you're getting at.

https://dotnetfiddle.net/nQoEKu


That's OK, I.. guess. I don't think I should even be allowed to ask the question because it's an absurd question[1]. Does that position make sense to you?

[1] Not for frivolous reasons. It's almost always nonsensical to ask this type of question with "null" values. For the sensical cases we have Maybe/Option.


I think I see what you want now. In order to get the value out, you'd have to use pattern matching or a type guard or something, inside of which you'd know there was a value.

That makes sense to me too. But compared to Maybe, a null-safe member accessing operator, along with null-lifted operators seems nearly as good from my point of view.


I can't think of a programming language used for game development that doesn't have null.


In Rust, an `&Thing` (reference to thing), `Option<&Thing>` (optional reference to thing), `Box<Thing>` (thing on the heap), and `Option<Box<Thing>>` (optional thing on the heap) all have the same in-memory representation of a single pointer, but prohibit accidentally dereferencing a null pointer, and make non-nullable pointers the default.


That made my head hurt.


I'm not sure I understand exactly why this is specific to game development? (I see that the article uses a game-like example, but...)

Even so: I'm pretty sure Rust is suitable for game development being essentially zero-overhead, non-GC and Rust effectively disallows "null".

(That, and C++ if you allow only references like another poster mentioned.)


C++, unless pointers are explicitly used, which weren't in the OP example.


Still not solving all the problems that an accessor does.

Say you suddenly want to change the position to another unit, or change how it uses position (perhaps return a different position on some cases).

With direct access, you can't.


End goal is to make code that is easier to maintain and understand.

The intermediate goals should be to reduce coupling, increase encapsulation, etc. These are tools to achieve the end goal.

Multiple dots is just a code smell, indicating that you're probably not meeting your intermediate goals or the end goal. If you see multiple dots, you should probably examine that code to see if it "wants" to be somewhere else, in service of the Primary goal.

But if you elevate "multiple dots" to the level of a "Law" then you're looking at the problem from the wrong end, and are in real danger of losing sight of the end goal. You can follow this law right down a hole that works AGAINST your real goal.


This "law" sounds like a reasonable idea, at first, but if you think about it for a few seconds, you'll realize that it makes little, if any, sense (at least, when taken out of some specific context). As the designer of the class, often neither you can possibly anticipate all the ways in which objects of the classes involved will be used, nor would you want to re-expose all (or some of) the APIs provided by the "subordinate" classes.


>As the designer of the class, often neither you can possibly anticipate all the ways in which objects of the classes involved will be used

That's why you subclass -- or compose. The subclass can use them in other ways.

The problem with what you say, that you can't "possibly anticipate all the ways in which objects of the classes involved will be used" is that if you let that happen, then you have to forever support the different ways by which they are used -- and you can't change your implementation internally, make it more efficient etc, because different external code uses various internal details of it.


It's a law as in "law of nature" not "law of the land". Things which follow LoD can be observed to have certain properties.


I've always thought this to.

Some say the law is actually a guideline.

I say it's one extreme of a spectrum, and we would do well to aim for the middle.


Gross mutable OO null-defensive nonsense.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: