1

I often encounter the following program while programming. There is a "partial" solution to a problem, which provides all the functionality that is necessary, but the documentation explaining said functionality and the behaviour of the solution is very dense and esoteric. Then, there is a "full" solution, which provides more functionality than is necessary, but the documentation doesn't have to explain why it doesn't work in certain cases, and thus is very simple and easy to understand.

Just as an off-the-cuff example, consider a method Foo(A a) that takes a single argument of type A. Let's just say there's an easy implementation of Foo, but it requires A to have certain properties, and the explanation for why those properties are necessary is long and complex.

The "partial" solution is at the beginning of Foo I simply check if A has the required properties, and then reject it if it doesn't. This, however, makes the documentation very complex as now I have to explain why it doesn't work for certain objects.

On the other hand I have the "full" solution, which is simply to make Foo work for all As, regardless of their properties. Suppose I theoretically could implement a Foo that accomplishes this, but it requires a lot more code than the "partial" implementation.

Which is better?

Maybe that's a bad example, but I run into generalizations of this problem very frequently in a lot of my larger projects. Which solution should I choose when faced with this dilemma (if either of the "partial" or "full")? Are there any general tips for preventing this situation from arising?

user3002473
  • 674
  • 5
  • 16
  • are you aware of [tag:liskov-substitution] principle? I guess not, otherwise you'd see why "partial" solution is not acceptable – gnat Apr 19 '16 at 07:59
  • @gnat I am, hence is why I qualified the example as "bad". – user3002473 Apr 19 '16 at 08:00
  • If you run into this problem frequently, it suggests something else is wrong. Can you give another couple of examples? – paj28 Apr 19 '16 at 08:01
  • 2
    Possible duplicate of [What can go wrong if the Liskov substitution principle is violated?](http://programmers.stackexchange.com/questions/170222/what-can-go-wrong-if-the-liskov-substitution-principle-is-violated) – gnat Apr 19 '16 at 08:03
  • @gnat I've edited the question to include an example that doesn't violate the Liskov substitution principle. Honestly I'm not sure why I didn't just use that example in the first place :) – user3002473 Apr 19 '16 at 08:04
  • edited question appears to be covered in [Is this a violation of the Liskov Substitution Principle?](http://programmers.stackexchange.com/questions/170138/is-this-a-violation-of-the-liskov-substitution-principle) – gnat Apr 19 '16 at 08:09
  • 2
    @gnat, what does this have to do with Liskov Substitution Principle? I don't see any mention of inheritance in the question at all! – Jan Hudec Apr 19 '16 at 08:11
  • 1
    @gnat That's not my question. I'm not asking about the ramifications of violating the Liskov Substitution Principle, I'm not worried about the Liskov Substitution Principle in the slightest. I'm asking, if you have a complicated solution with easy documentation, and an easy solution with complicated documentation, which do you choose. Does my new example not make sense? – user3002473 Apr 19 '16 at 08:12
  • @JanHudec OP simply obscured the essence with handwaving edits. [Original revision](http://programmers.stackexchange.com/revisions/316156/1) said it clearly and unambiguously: _'the "partial" solution would be that Foo works for all objects that are subclasses of A provided they are decorated with a certain Attribute, otherwise Foo won't work'_ - if that's not a violation of LSP then I don't know what is – gnat Apr 19 '16 at 08:20
  • @gnat The error has been corrected, apologies for the initial confusion. – user3002473 Apr 19 '16 at 08:22
  • 1
    @gnat, ah, yes, that would be. But it is just a special case of the actual question (and it was obvious from the beginning that it is). – Jan Hudec Apr 19 '16 at 08:27
  • @JanHudec it was at the core from the very beginning, as openly admitted by OP when clarifying about LSP in [this comment](http://programmers.stackexchange.com/questions/316156/implementing-something-partially-vs-providing-simple-documentation?noredirect=1#comment669110_316156): _'I am [aware of LSP], hence is why I qualified the example as "bad"'_ – gnat Apr 19 '16 at 12:08
  • @gnat OP qualified the example as bad exactly because it was never core of the question. And to me it is pretty clear even from the original wording that it wasn't. – Jan Hudec Apr 20 '16 at 05:08

2 Answers2

5

This is actually an easy one.

Do your requirements state that you need to be able to process As without the limiting properties? (Okay, everything is relative, so I'll ask: do you need the capability badly enough to pay for the extra development time?)

If yes, then write the extra code.

If no, then code the simple solution with the more complicated precondition.

Preconditions on APIs are a drag, but if you document them there is no question of having done anything improperly. Elegant solutions are nice, but they have a cost, and every cost has to be justified. "I apologize for writing such a long letter, but I lacked the time to make it shorter" can be a valid excuse in professional setting.

Kilian Foth
  • 107,706
  • 45
  • 295
  • 310
2

A lot of times, people have problems like this and think it's a design problem with Foo or its enclosing class, but it's really a design problem with A.

As an example, let's say A is a database connection. Common preconditions on database connections are that they point to a valid IP address, contain valid authentication information, etc. If you had to check every precondition upon every function call, it would get tedious, so a common pattern is to split the class. One class you use while you're putting in the server address and the authentication information, then from that object you validate the preconditions once and create another object which is used for executing queries.

In other words, instead of having the same object in a different state, look for opportunities create a new object for a different state. This eases both your precondition checking and your overall complexity.

Usually if your type hierarchies are designed properly, the situations where you have to choose a heavily-preconditioned implementation are fairly rare. If it's coming up a lot in a code base, the time it takes to get your types into shape will be well worth it.

Karl Bielefeldt
  • 146,727
  • 38
  • 279
  • 479