64

As programmers I feel that our goal is to provide good abstractions on the given domain model and business logic. But where should this abstraction stop? How to make the trade-off between abstraction and all it's benefits (flexibility, ease of changing etc.) and ease of understanding the code and all it's benefits.

I believe I tend to write code overly abstracted and I don't know how good is it; I often tend to write it like it is some kind of a micro-framework, which consists of two parts:

  1. Micro-Modules which are hooked up in the micro-framework: these modules are easy to be understood, developed and maintained as single units. This code basically represents the code that actually does the functional stuff, described in requirements.
  2. Connecting code; now here I believe stands the problem. This code tends to be complicated because it is sometimes very abstracted and is hard to be understood at the beginning; this arises due to the fact that it is only pure abstraction, the base in reality and business logic being performed in the code presented 1; from this reason this code is not expected to be changed once tested.

Is this a good approach at programming? That it, having changing code very fragmented in many modules and very easy to be understood and non-changing code very complex from the abstraction POV? Should all the code be uniformly complex (that is code 1 more complex and interlinked and code 2 more simple) so that anybody looking through it can understand it in a reasonable amount of time but change is expensive or the solution presented above is good, where "changing code" is very easy to be understood, debugged, changed and "linking code" is kind of difficult.

Note: this is not about code readability! Both code at 1 and 2 is readable, but code at 2 comes with more complex abstractions while code 1 comes with simple abstractions.

3
  • 4
    Comments and clear names are invented to hasten the time needed to understand complex code. It's perfectly fine for your lower-level code to be more complex; at some level, you're almost certainly calling a far-more complex and far-lower level anyway.
    – DougM
    Commented Jun 23, 2013 at 23:31
  • 33
    “Too much” is bad by definition.
    – Jon Purdy
    Commented Jun 24, 2013 at 4:22
  • @JimmyHoffa: Gotta keep those types in their place. And don’t be jealous—I don’t get to write Haskell all day. It’s mostly PHP, JavaScript, and OCaml actually.
    – Jon Purdy
    Commented Jun 24, 2013 at 14:52

7 Answers 7

97

The very first words of The C++ Programming Language, 4th edition:

All problems in computer science can be solved by another level of indirection, except for the problem of too many layers of indirection. – David J. Wheeler

(David Wheeler was my thesis advisor. The quote without the important last line is sometimes called "The first law of Computer Science.")

9
  • 9
    And how do you know when you have too many levels of indirection? I would tend to say it comes with experience, but a more experienced programmer easily understands more indirection hence doesn't see a problem with too many levels.
    – Random42
    Commented Jun 24, 2013 at 7:30
  • 3
    @m3th0dman - you have the right level of abstraction when it becomes easy to make changes future. Of course, you can also ask how you know when that will occur which merely repeats the question cycle in a different manner.
    – user53019
    Commented Jun 24, 2013 at 10:54
  • 2
    stackoverflow.com/questions/2668355/… Commented Aug 13, 2013 at 12:22
  • 2
    this question is like programmer-level dependent.. you'll have complex programmers that'll understand your crazy 8-tier layer architecture & find it brilliant, while other simple but smooth coders will find it ridiculous & argue about your crazy 8-tier layered project.. this is where documentation pays off, not only does it allow you to document your code, but allows you to defend it
    – Ryhan
    Commented Jul 12, 2014 at 17:25
  • 1
    pardon my ignorance, but I am not understanding "TC++PL4" Commented Aug 18, 2017 at 15:56
35

Yes, definitely. The thing is, no abstraction is perfect. All of the details of the layer that abstractions sit atop are there for a reason, and it can simplify a lot of things, but if that complexity wasn't necessary at some point, it probably wouldn't be there in the first place. And that means that at some point, every abstraction is going to leak in some way.

And that's where the real problem lies. When abstractions fail, the more of them you have layered in between the code you wrote and what's actually going on, the harder it is to figure out the problem and fix it, because there are more places where the problem might be. And the more layers there are, the more you have to know in order to track it down.

9
  • 1
    "And that means that at some point, every abstraction is going to leak in some way.": True. A better abstraction is one that leaks less often.
    – Giorgio
    Commented Jun 23, 2013 at 21:28
  • 11
    In light of Bjarne's answer (and referencing David Wheeler's wiki page), perhaps you can change your quote attribution? :) Commented Jun 24, 2013 at 0:01
  • 2
    @CongXu: Btw I have gone at it from the other end: googling for "Bjarne Stroustrup quotes" and have not found a single reference of Bjarne having uttered the "adding another layer of indirection" phrase... Not conclusive of course but does make it highly unlikely he was the first to utter it. Commented Jun 25, 2013 at 9:20
  • 1
    More layers of abstraction mean simple abstractions and thus less leakage per abstraction. So in a mathematical formulation (of course, without any proof) the sum of abstraction leakage might be constant as the number of abstraction levels change.
    – Random42
    Commented Jul 30, 2013 at 14:52
  • 2
    I've once naively taken the advise of a senior to add an abstraction where it wasn't needed. It was never used beyond what i wanted to do in the first place. Commented Aug 13, 2013 at 12:26
26

Yes absolutely.

The analogy I like to use to explain programming is that of a Tailor. When making a suit, a good Tailor will always leave a small amount of fabric at strategic locations within the garment to allow the garment to be taken in, or out, without changing its overall shape or structure.

Good Tailor's don't leave reams of fabric at each seam just incase you happen to grow a third arm, or become pregnant. Too much material in the wrong places will make for an ill fitting, and ill wearing garment, the extra fabric simply gets in the way of normal use. To little fabric and the garment is prone to tears and will not be able to be altered to cope with minor changes to the physique of its wearer, effecting the way garment sits.

Perhaps one day, our Good Tailor will be tasked to make a dress so tight he has to sew its wearing into it. And perhaps our Good Tailor is asked to make maternity wear, where style and fit are second to comfort and expand-ability. But before undertaking either of those special jobs a good Tailor would be wise enough to make everyone aware of the compromises that are being made to achieve those goals.

Sometimes these compromises are the correct route to take, and people are willing to accept their consequences. But in most cases, the approach of leave a little where it counts the most will outweigh any perceived benefits.

So, relating this back to abstraction. Its absolutely possibly to have way too many layers of abstraction, just like its possible to have way to little. The true art of the programmer, like the our tailor friends, is to leave a little where it counts the most.

Getting back on topic.

The problem with code is usually not abstraction, but dependencies. As you've pointed out, its the code which connects discrete objects that's an issue, because there's an implicit dependency between them. At some point communication between things just needs to be concrete, but judging where that point is usually requires some guesswork.

That being said "Micro" anything is usually an indication that you've overgranulized your object layout, and are probably using Type as a synonym for what should be Data. Having less things also means less dependencies needed to communicate between them.

I'm a big fan of asynchronous messaging between systems for this reason. You end up with two systems dependent on the message, rather than each other. Giving you less tight coupling between communicating systems. At that point, if you need to have a dependency between systems you need to consider whether you've got the bits that are dependent in the right place(s). And its often the case that you dont.

Finally, complicated code is going to be complicated. There's often no way around that. But code which has less dependencies is far easier to understand than one which relies on various external states.

4
  • 7
    +1 for "Good Tailor's don't leave reams of fabric at each seam just incase you happen to grow a third arm, or become pregnant". We often tend to design software this way, sadly.
    – Kemoda
    Commented Jun 28, 2013 at 11:22
  • 1
    Along with understandability, one can also find abstraction useful when bending a curved line into a straight one. Meaning you're reducing complexity and/or entropy with the abstraction. Perhaps something like a Curry type data-handler where the fat you shave is still useful later on.
    – Cody
    Commented Feb 18, 2014 at 21:28
  • 1
    +1 for using an abstraction to explain the idea of too much abstraction. :-)
    – Daniel T.
    Commented Aug 5, 2021 at 11:35
  • I don’t see how this relates to abstraction at all. Commented Jan 8, 2023 at 20:19
17

I feel that our goal is to provide good abstractions on the given domain model and business logic.

I have a different view: our goal is to solve a business problem. Abstractions are just a technique to organize a solution. Another answer uses the analogy of a tailor making clothes. I have another analogy I like to think of: a skeleton. The code is like a skeleton, and abstractions are the joints between bones. If you have no joints, then you just wind up with a single bone that cannot move at all and is useless. But if you have too many joints, you wind up with a pile of sloppy jelly that can't stand up on its own. The trick is to find the right balance -- enough joints to allow for movement, but not so much that there's no actual defined shape or structure.

6

One thing that hasn't been mentioned so far is the KISS principle and YAGNI: Keep it simple and stupid; You aren't gonna need it.

Sometimes, there are very good reasons for abstractions:

  • You are already using them and you need them already to keep the complexity low - not maybe sometime in future, but actually already now.
  • You're writing a library / something with a plug-in system and you want to prevent breaking changes for your users.

If it's not that, then maybe you can remove the abstractions.

4

I often see developers jumping into several levels of abstraction immediately, which I don't subscribe too. I always make a simple implementation first... enough to satisfy the requirements of the software... maybe with some re-use in mind, but simple. Then as a program evolves, or as the same requirement arises in another project.. I can always break it out into a separate re-usable chunk, further abstracted.
As an example, you're working on a program that needs to create several PDF reports.... do to this you go grab some shared library out there. Now, hopefully the library has already been abstracted out well.. so why do you need to abstract it out 3 classes deep, each with one method, to use it for that one purpose? Just wrap its functionality in a local library you have and use it directly in there. Later if necessary, not only can you add the additional abstraction, but when the need arises it paints a better picture of how the re-use and encapsulation should be implemented. This also acts like insurance against wasting time on planning for re-use that never happens. Sometimes you absolutely know you are going to want to reuse a feature, and when developing coding-tools or re-usable projects, you want to start out with a little more than a simple implementation... but you still might find it was wasted time.

1

There is one point where you recognise too much abstraction (there are probably others): When you are shown an interface, you read it, and you have no idea what it is doing and why you would use it, that’s too much abstraction.

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