I was in the shower and thought about something.
The deferred / promise pattern is to decrease callback hell, by allowing the developer to chain call functions, as mentioned here:
Parse.User.logIn("user", "pass").then(function(user) {
return query.find();
}).then(function(results) {
return results[0].save({ key: value });
}).then(function(result) {
// the object was saved.
});
From the top of my head - correct me if I am wrong - but it seems like using deferred / promises is an easy way to break the Law of Demeter?
The Law of Demeter states:
A method of an object may only call methods of:
- The object itself.
- An argument of the method.
- Any object created within the method.
- Any direct properties/fields of the object.
Each unit should have only limited knowledge about other units: only units "closely" related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers.
Any comments concerning this?
Update December 1 2013:
A summarized version of my question. The Promise framework is designed to simplify asynchronous coding and avoid "callback hell". One of the most beneficial features of Promise is that you can chain-call events by using .then()
, as seen in my example above.
Given all code/functions are now using Promise (like Benjamin Gruenbaum (author below) is currently doing), won't it open it up to make chain-calling functions really easy by going .then().then().then()
etc.
Writing code that chain-call functions after each other (.then().then().then()
) has to be a text-book example of how to break the Law of Demeter.
Hence my question; Does the Promise framework promote / open up / make it easier to abuse / break the Law of Demeter?
I was in the shower and thought about something.
The deferred / promise pattern is to decrease callback hell, by allowing the developer to chain call functions, as mentioned here:
Parse.User.logIn("user", "pass").then(function(user) {
return query.find();
}).then(function(results) {
return results[0].save({ key: value });
}).then(function(result) {
// the object was saved.
});
From the top of my head - correct me if I am wrong - but it seems like using deferred / promises is an easy way to break the Law of Demeter?
The Law of Demeter states:
A method of an object may only call methods of:
- The object itself.
- An argument of the method.
- Any object created within the method.
- Any direct properties/fields of the object.
Each unit should have only limited knowledge about other units: only units "closely" related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers.
Any comments concerning this?
Update December 1 2013:
A summarized version of my question. The Promise framework is designed to simplify asynchronous coding and avoid "callback hell". One of the most beneficial features of Promise is that you can chain-call events by using .then()
, as seen in my example above.
Given all code/functions are now using Promise (like Benjamin Gruenbaum (author below) is currently doing), won't it open it up to make chain-calling functions really easy by going .then().then().then()
etc.
Writing code that chain-call functions after each other (.then().then().then()
) has to be a text-book example of how to break the Law of Demeter.
Hence my question; Does the Promise framework promote / open up / make it easier to abuse / break the Law of Demeter?
Share Improve this question edited Dec 1, 2013 at 17:09 corgrath asked Nov 28, 2013 at 23:03 corgrathcorgrath 12.3k17 gold badges73 silver badges107 bronze badges 4 |3 Answers
Reset to default 10 +100I think you're misinterpreting the meaning of Demeter's Law and its applicability to both languages like JavaScript and frameworks like promises.
Promises are not a "unit" in the sense that Demeter's Law envisions, which correspond to something like a "class" such as Account or Customer in a banking application. They are a higher-level, meta-construct for asynchronous control flow. It's hard to see how a such a meta-construct could even exist, or be useful, without being able to "talk to" arbitrary outside entities (non-friends) which it is designed to control.
Demeter's Law seems highly focused on classical OO systems where everything is a class or a method. As stated, it would appear to not allow any invocation whatsoever of passed-in functions, and thus most if not all of functional programming.
Another way of looking at this is that to the extent you view promises as violating this law, then callbacks certainly do too. After all, the two are basically isomorphic--the difference is essentially syntactic. So if you are fixated on not violating Demeter's law, you are also not going to be able to use callbacks--so how are you going to write the most basic asynchronous program?
The short answer is Yes.
I think this has been made more complicated than necessary because people have confused the issue with points that while interesting aren't directly relevant to the Law of Demeter. Like the fact we are talking JavaScript. Or the fact we are dealing in callbacks. Those are details that just don't apply.
Let's step back and reset the discussion. The overarching goal in software engineering is to limit coupling as much as possible. In other words, when I change code, I want to make sure that doesn't force me to work on the weekend to change a ton of other code as a consequence. The Law of Demeter exists to prevent one type of coupling--feature envy. It does so by providing formal limits to what a method f can use to do its job.
The OP @corgrath was nice enough to enumerate these limits. An easy way to characterize a violation of the Law of Demeter is this: "You can't call any methods on any of the 4 objects allowed."
Now finally to the sample code provided by @corgrath:
Parse.User.logIn("user", "pass").then(function(user) {
return query.find();
}).then(function(results) {
return results[0].save({ key: value });
}).then(function(result) {
// the object was saved.
});
Let's call Parse
a data structure as opposed to an object (See Chapter 6 of Uncle Bob's fabulous book Clean Code, which was my first exposure to the Law of Demeter, for more on the distinction). Then we are fine with Parse.User
.
But User
is clearly an object with methods and behavior. One of these methods is logIn
. This returns a Promise
. As soon as we call anything on this object, we have violated the Law of Demeter.
That's it.
As an aside, I will mention quickly also that in JavaScript functions are objects. So the Law of Demeter would also apply to the callback functions passed to each then
call. But within each none of the function's methods, which do exist, are called, so the then
calls do not violate the Law of Demeter.
Now what's interesting is whether this clear violation of the Law of Demeter matters. Software engineering is an art. We have all kinds of laws and principles and practices, but religious adherence to them is just as counterproductive as ignorance of them. It is stupid to attempt 100% code coverage; it is stupid to unit test getters and setters; it is stupid to fight for 100% class cohesion; it is stupid to create 100% package stability; etc.
In this case, I would say the violation of the Law of Demeter doesn't matter. Promise
s don't expose internals in any way; they expose an abstraction for performing another action (in this case, registering a callback, but that is irrelevant to the discussion). Put another way, do I have to worry about working on the weekend by making all these then
calls? The likelihood is realllly low. I mean it's possible they will rename the method to andThen
or whenYoureReadyDoThis
, but I doubt it.
This is a big deal because I like my weekends. I don't want to work on unnecessary things. I want to do fun stuff like posting essay answers on Stack Overflow.
So in summary, there are two questions:
- Does the
Promise
code break the Law of Demeter? Yes. - Does it matter? No
Conflating the two and bringing all kinds of extraneous information into the discussion only confuses the matter.
Nice question! Even though it doesn't focus on the aspects of adhering to LoD that you believe to be conflict with the promise pattern :) From your comments, however, it can be seen that you are mainly concerned with chaining and use of callbacks.
Chaining is common in implementations of the pattern but doesn't add to its semantics; in fact it's nothing but syntactic sugar, making the following code is synonymous:
someAsyncPromise.then(/*do some stuff first*/).then(/*do other stuff*/);
someAsyncPromise.then(/*do some stuff first*/);
someAsyncPromise.then(/*do other stuff*/);
Above, then
method returns a reference to the original promise object someAsyncPromise
and not a new/different promise object. This possibly violates some other OO principles, but not LoD, where it's kosher for an object to call its own methods (d'oh :) Perhaps it's easier recognised in common jQuery selector chaining:
$('#element').hide().text('oh hi').css('color', 'fuchsia');
//synonymous to
$('#element').hide();
$('#element').text('oh hi');
$('#element').css('color', 'fuchsia');
(Okay, not really synonymous since the $()
selector would re-query the element in the second example; however it would be if we've cached the jQuery object! You get the point.)
Let's focus on callbacks now. The beauty of javascript is that functions are first-class citizens and you can pass them around. What does the noop in your example do?
function(result) {
// the object was saved.
}
It's just sitting there, like an egg waiting to be hatched. You're using a function expression to create an anonymous function which is then passed as an argument of .then()
, which pushes it into a stack of promises to be executed.
So in a way, one interpretation of the promise pattern would be to see it as a textbook example of adhering to LoD!
...
Let me finish by saying the current Wikipedia entry is, IMHO, extremely misleading:
For many modern object oriented languages that use a dot as field identifier, the law can be stated simply as "use only one dot". That is, the code
a.b.Method()
breaks the law wherea.Method()
does not.
Lolwut? This focuses too much on syntax and appearances, rather than underlying structures and architecture. There is a very comprehensive explanation covering the difference between the two that (and some other leaks in the Wikipedia entry), as well as the law's non-wikified version that you might want to peruse instead.
.then()
create a new Promise object? If so, the new Promise qualifies as "Any object created within the method." Thus the Law of Demeter allows you to call methods on the new Promise, including yet another.then()
. It depends on just how directly you require the object to be created within the method. Are you strict, qualifying only direct constructor calls? Are you more liberal, qualifying any object that only exists as a result of code executed within the method? I'd lean toward the latter, as the former distinction is highly arbitrary. – Keen Commented Dec 6, 2013 at 19:14