2

Today I had a discussion with a colleague.

It is my understanding that a class has the responsibility to ensure that its objects have a valid state when interacted with from outside the class. The reason for this rule is that the class does not know who its users are and that it should predictably fail when it is interacted with in an illegal manner. In my opinion that rule applies to all classes, including to immutable, somewhat primitive objects, that do little more than holding primitive values.

In the specific situation where I had a discussion today, the constructor of such a ValueObject had to do some work (it had to call a couple of getters) in order to acquire the values it needed for a valid state. The correlation between the computed results is a condition of the ValueObject's class invariants, i.e. they must have the same source.

This is an illustration of the class (php code example):

class Example {

    private $someValue;
    private $computedResultA;
    private $computedResultB;

    public function __construct($someValue, $someDependency) {
        $this->someValue = $someValue;
        $this->computedResultA = $someDependency->computeResultA();
        $this->computedResultB = $someDependency->computeResultB();
    }
}

My colleague argues that it is better to compute the results in the class that creates an instance of the ValueObject and pass these primitive results to that ValueObject's constructor. After all, he reasons, constructors must not do work. Additionally, the test code would become larger if a mock had to be provided to the ValueObject's constructor - something which is not necessary when simply primitive values are provided. And apparently testing code is more important than tested code.

This is an illustration of the code he prefers:

class Example {

    private $someValue;
    private $computedResultA;
    private $computedResultB;

    public function __construct($someValue, $computedResultA, $computedResultB) {
        $this->someValue = $someValue;
        $this->computedResultA = $computedResultA;
        $this->computedResultB = $computedResultB;
    }
}

Can anyone explain why constructors must not do work? Google tells me that constructors should not cause side-effects. But then what about (Value)Objects that explicitly embody some kind of computation result? Should that computation result always be processed in a factory instead, and only then be passed to the value object? Doesn't that fail to maintain the object's integrity constraints?

  • Possible duplicate of Start Method vs. Setting up everything in constructor – gnat Oct 24 '16 at 17:50
  • 1
    @gnat Not a dupe, since no start method in Example is involved. What the OP is asking is whether the constructor must do additional work (i.e. compute stuff based on its parameters) or have every parameter passed to it already in ready-to-use form. – Andres F. Oct 24 '16 at 17:53
  • You will find your answer in this video. Btw, I also strongly encourage you to look the other videos of the playlist. Another interesting article to read on the subject. – Spotted Oct 25 '16 at 8:22
  • @AndresF. agree, it's not a duplicate (retracted my vote) – gnat Oct 25 '16 at 8:27
2

Can anyone explain why constructors must not do work?

Because constructors have limited mechanisms to handle error cases. You need to return an object of that type or throw an exception. You can't return a different type. You can't signal failure. You can't return null. You (almost always) just get to throw an exception, making your callers deal with it.

But then what about (Value)objects that explicitly embody some kind of computation result?

If those computations are reasonably fast, and only fail in exceptional scenarios then it's excusable. Sometimes implicitly doing those computations makes the code cleaner by enforcing those hard invariants via a simplified interface.

But as a general guideline - constructors should not do work.

Should that computation result always be processed in a factory instead, and only then be passed to the value object? Doesn't that fail to maintain the object's integrity constraints?

Always is probably too strong, but yes, they generally should. In some languages you can provide a static function to create the object that uses a private constructor. This maintains your object's invariance while allowing you to handle error conditions more gracefully.

  • 1
    I have never heard before that constructors should not throw exceptions. How is that different from other methods (which also often need to return an object of a certain type) that potentially throw an exception? If I understand correctly: constructors must not do work, because they may throw an exception. But then again, even when constructors don't do much work, there may still be plenty of reason to throw an exception, e.g. upon incorrect parameter provision; if my constructor expects a positive int, and you provide a negative one, then it will throw an exception, despite it not doing work. – user2180613 Oct 24 '16 at 20:46
  • 1
    @user2180613 I think you misunderstood this answer. Throwing exceptions in constructors is acceptable. – Andres F. Oct 24 '16 at 21:34
  • 1
    @user2180613: You are misunderstanding: constructors must not do complex work that can fail in complex scenarios, because the only thing it can do is either succeed or throw an exception … and usually constructors are expected to succeed. So, the tiniest problem will always result in an exception. Methods can do a lot more than that. They can return an object of a different type (depending on the type system) in case something unforeseen happens. They can return null. They can fork off a background thread to try to fetch valid date from a web API. Theoretically, a constructor can do … – Jörg W Mittag Oct 24 '16 at 22:09
  • some of those things as well. But the expectation is that it doesn't. It constructs an object, that's the expectation. Ruby is a good example here: Ruby doesn't have constructors. There is simply a convention that a class responds to a method called new and that method returns a new instance of the class. So, new is actually a method like any other method, there is nothing special about it, not even the name (it's purely a convention). And yet, Ruby programmers place heavy constraints on what they are willing to do inside new (or initialize, which is customarily called by new). – Jörg W Mittag Oct 24 '16 at 22:12
  • ... which means new shouldn't do work because we don't expect new to do work. Find some where else to do work. – candied_orange Oct 25 '16 at 16:19

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.