9

I was reading this wiki on the Stable Abstractions Principle (SAP).

The SAP states that the more stable a package the more abstract it should be. This implies that if a package is less stable (more likely to change) then it should be more concrete. What I don't really understand is why this should be the case. Surely in all cases regardless of stability we should be depending upon abstractions and hiding the concrete implementation?

maple_shaft
  • 26,401
  • 11
  • 57
  • 131
  • Try using a component that you're comfortable with, by **not** using the abstractions it provides, but doing everything in full detail at one level lower than you're used to. That will give you a pretty good impression of the advantages and disadvantages of abstraction. – Kilian Foth Nov 09 '15 at 10:28
  • 2
    Did you read [the linked article](http://objectmentor.com/resources/articles/stability.pdf) and/or [the book](http://amazon.com/exec/obidos/ASIN/0135974445/) on which the article is based? – Jörg W Mittag Nov 09 '15 at 11:47
  • 1
    +1 Good question, especially since I don't think the relationship between stability and abstraction is immediately intuitive. [Page 11 of this article](http://objectmentor.com/resources/articles/stability.pdf) helps, its example of the abstract case makes sense, but perhaps someone can write a clearer example of the concrete case. Request taking off hold. – Mike Nov 09 '15 at 19:16
  • What problem domain are you dealing with for these abstractions? As noted on C2: "In modelling real-world domains -- the world of customers, employees, invoices, bills-of-materials, products, SKUs, paycheques, etc. - stable abstractions may be difficult to find. Computational domains - the world of stacks, queues, functions, trees, processes, threads, graphical widgets, reports, forms, etc. - are much more likely to be stable." and "In some domains, stable abstractions are difficult to come by." Without knowing what problem you are trying to solve with SAP, its hard to give you a good answer. –  Nov 10 '15 at 14:11
  • @JörgWMittag and Mike - Yeah i read the article. I just feel that there is a lack of an explanation as to why "instable packages should be concrete". On page 13 of said article he shows a graph but does not really go on to explain in too much detail why (1,1) on the graph should be avoided? Is the idea that basically instable means less afferent dependencies and there for there is no need to use abstraction? If so... is it not good practice to use abstraction anyways, just in case the stability changes with requirement changes.. – SteveCallender Nov 10 '15 at 14:54
  • @SteveCallender I found reading a lot of Robert Martin's texts made my brain hurt, but at the same time it's intriguing to see the metrics, namely the graph you mentioned on page 13. Abstractness or concreteness of *packages* is far from intuitive. I wouldn't worry about it, as I don't think it's wise to do top-down designs of packages using these metrics. Even if you measure arbitrary packages, it's not clear how you would refactor them. I found the package design strategies from Craig Larman's Applying UML and Patterns to be more straightforward. – Fuhrmanator Nov 11 '15 at 19:27

5 Answers5

7

Think of your packages as an API, to take the example from the paper, take definitions for Reader with string Reader.Read() and Writer with void Writer.Write(string) as your abstract API.

You can then create a class Copy with a method Copier.Copy(Reader, Writer) and the implementation Writer.Write(Reader.Read()) and maybe some sanity checks.

Now, you make concrete implementations, e.g. FileReader, FileWriter, KeyboardReader and DownloadThingsFromTheInternetReader.

What if you want to change your implementation of FileReader? No problem, just change the class and recompile.

What if you want to change the definition of your abstraction Reader? Oops, you cannot just change that, but you will also have to change Copier, FileReader, KeyboardReader and DownloadThingsFromTheInternetReader.

This is the rationale behind the Stable Abstraction Principle: Make your concretisations less stable than the abstractions.

Residuum
  • 3,282
  • 28
  • 31
  • 1
    I agree with everything you say, but i believe the authors definition of stability and yours differ. You treat stability as need to change, the author says "stability is not a measure of the likelihood that a module will change; rather it is a measure of the difficulty in changing a module." So my question is more why is it beneficial for packages that are easily changed to be more concrete rather than abstract? – SteveCallender Nov 10 '15 at 15:03
  • 1
    @SteveCallender It is subtle difference: The author's definition of "stability" is what most people call "need for stability", .i.e. the more modules depend on a module, the more "stable" a module needs to be. – Residuum Nov 10 '15 at 16:23
6

Because of YAGNI.

If you currently have only one implementation of one thing, why bothering with an extra and useless layer ? It will only lead to unnecessary complexity. Even worse, sometimes you provide an abstraction thinking to the day a second implementation will come... and this day never happens. What a waste of work !

I also think the real question to ask itself is not "Do I need to depend on abstractions ?" but rather "Do I need modularity ?". And modularity is not always needed, see below.

In the company I am working, some of the softwares I develop are strongly tied to some hardware device with which it must communicate. These devices are developed to fulfill very specific goals and are everything but modular. :-) Once the first produced device goes out of the factory and is installed somewhere, both its firmware and hardware can never change, ever.

So, I can be sure that some parts of the software will never evolve. These parts don't need to depend on abstractions since it exists only one implementation and this one will never change. Declaring abstractions on these parts of code will only confuse everyone and take more time (without producing any value).

Spotted
  • 1,680
  • 10
  • 18
  • 1
    I tend to agree with YAGNI, but I wonder about your example. Are you never repeating *any* code in the different devices? I find it hard to believe that there isn't some common code across the devices from the same company. Also, how do clients like when you don't fix bugs in their firmware? Are you saying there never are bugs, **ever**? If you have the same code that's buggy in 4 different implementations, you have to fix the bug 4 times if it's not in a common module. – Fuhrmanator Nov 11 '15 at 14:46
  • 1
    @Fuhrmanator common code is different from abstractions. Common code can just mean a helper method or library - no abstractions are needed. – Eilon Nov 11 '15 at 17:34
  • @Fuhrmanator Of course we have common code in libraries, but as Eilon said, not everything depends on abstractions (some parts do, however). I never said there never are bugs, I said they can't be patched (for reasons that are outside the scope of the OP's question). – Spotted Nov 11 '15 at 18:50
  • @Eilon my comment was about *modularity is not always needed* (not abstractions). – Fuhrmanator Nov 11 '15 at 19:02
  • @Spotted No problem about not being able to patch. It's just a pretty specific example and not typical of most software. – Fuhrmanator Nov 11 '15 at 19:03
  • @Fuhrmanator You are right, the example I gave is rather esoteric. But since the OP wanted to know if _we should in all cases depend upon abstractions_, I just wanted to give an example when not. :) Aside this, I think abstraction is a powerful tool for a programmer (but as for every tool, it must be used for the right job). – Spotted Nov 11 '15 at 19:09
  • Obligatory statement: ["Yagni is only a viable strategy if the code is easy to change"](http://martinfowler.com/bliki/Yagni.html) – Marjan Venema Dec 01 '15 at 07:54
6

I think you're perhaps confused by the word stable chosen by Robert Martin. Here's where I think the confusion starts:

This implies that if a package is less stable (more likely to change) then it should be more concrete.

If you read through to the original article, you'll see (emphasis mine):

The classic definition of the word stability is:”Not easily moved.” This is the definition that we will be using in this article. That is, stability is not a measure of the likelihood that a module will change; rather it is a measure of the difficulty in changing a module.

Clearly, modules that are more difficult to change, are going to be less volatile. The harder the module is to change, i.e. the more stable it is, the less volatile it will be.

I have always struggled with the author's choice of the word stable, as I (like you) tend to think of the "likelihood" aspect of stability, i.e., unlikely to change. Difficulty implies that changing that module will break a lot of other modules, and it's going to be a lot of work to fix the code.

Martin also uses the words independent and responsible, which to me convey much more meaning. In his training seminar, he used a metaphor about parents of children growing up, and how they should be "responsible," because their children depend on them. Divorce, unemployment, incarceration, etc. are great real-world examples of the negative impact that change in parents will have on children. Therefore, parents should be "stable" for the benefit of their kids. By the way, this metaphor of children/parents is not necessarily related to inheritance in OOP!

So, following the spirit of "responsible" I came up with alternative meanings for difficult to change (or should not change):

  • Obligated - meaning other classes depend on this class so it shouldn't change.
  • Beholden - ibid.
  • Constrained - the obligations of this class limit its facility in changing.

So, plugging these definitions into the statement

the more stable a package the more abstract it should be

  • the more obligated a package the more abstract it should be
  • the more beholden a package the more abstract it should be
  • the more constrained a package the more abstract it should be

Let's cite the Stable Abstractions Principle (SAP), emphasizing the confusing words stable/unstable:

Packages that are maximally stable should be maximally abstract. Unstable packages should be concrete. The abstractness of a package should be in proportion to its stability.

Clarifying it without these confusing words:

Packages that are maximally beholden to other parts of the system should be maximally abstract. Packages that can change without difficulty should be concrete. The abstractness of a package should be in proportion to how difficult it will be to modify.

TL;DR

The title of your question asks:

Are there any significant disadvantages to depending upon abstractions?

I think if you create the abstractions properly (e.g., they exist because a lot of code depends on them), then there aren't any significant disadvantages.

Fuhrmanator
  • 1,435
  • 9
  • 19
0

This implies that if a package is less stable (more likely to change) then it should be more concrete. What i don't really understand is why this should be the case.

Abstractions are things that are hard to change in the software because everything depend on them. If your package is going to change often and it provides abstractions, people who depend on it will be forced to rewrite a big bunch of their code when you change something. But if your unstable package provides some concrete implementations, much lesser code will have to be rewritten after changes.

So, if your package is going to change often, it should better provide concretes, not abstractions. Otherwise... who the hell will use it? ;)

Vladislav Rastrusny
  • 1,844
  • 12
  • 14
0

Keep in mind Martin's stability metric and what he means by "stability":

Instability = Ce / (Ca+Ce)

Or:

Instability = Outgoing / (Incoming+Outgoing)

That is, a package is considered completely unstable if all of its dependencies are outgoing: it uses other things, but nothing uses it. In that case, it only makes sense for that thing to be concrete. It's also going to be the easiest kind of code to change since nothing else uses it, and therefore nothing else can break if that code is modified.

Meanwhile when you have the opposite scenario of complete "stability" with a package used by one or more things but it doesn't use anything on its own, like a central package used by the software, that is when Martin says this thing should be abstract. That is also reinforced by the DIP part of SOLI(D), the Dependency Inversion Principle, which basically states that dependencies should uniformly flow towards abstractions for both low and high-level code.

That is, dependencies should uniformly flow towards "stability", and more precisely, dependencies should flow towards packages with more incoming dependencies than outgoing dependencies and, furthermore, dependencies should flow towards abstractions. The gist of the rationale behind that is that abstractions provide breathing room to substitute one subtype for another, offering that degree of flexibility for the concrete parts implementing the interface to change without breaking the incoming dependencies to that abstract interface.

Are there any significant disadvantages to depending upon abstractions?

Well, I actually disagree with Martin here for my domain at least, and here I need to introduce a new definition of "stability" as in, "lacking reasons to change". In that case I would say dependencies should flow towards stability, but abstract interfaces do not help if abstract interfaces are unstable (by my definition of "unstable", as in prone to repeatedly being changed, not Martin's). If the developers cannot get the abstractions correct and clients repeatedly change their mind in ways that render abstract attempts to model the software incomplete or ineffective, then we no longer benefit from the enhanced flexibility of abstract interfaces to protect the system against cascading dependency-breaking changes. In my personal case I've found ECS engines, such as those found in AAA games, to be among the most stable engines I've ever worked on along with the most flexible against changing design needs and, in an ECS, the dependencies flow towards the most concrete: towards raw data, but such data is highly stable (as in, "unlikely to ever need to be changed"). I've often found the probability of something requiring future changes to be a more useful metric than the ratio of efferent to total couplings in guiding SE decisions.

So I would alter DIP a bit and just say, "dependencies should flow towards components that have the lowest probability of requiring further changes", regardless of whether those components are abstract interfaces or raw data. All that matters to me is the probability that they might require direct design-breaking changes. Abstractions are only useful in this context of stability if something, by being abstract, reduces that probability.

For many contexts that might be the case with decent engineers and clients who anticipate the needs of the software upfront and design stable (as in, unchanging) abstractions, while those abstractions offer them all the breathing room they need to swap out concrete implementations. But in some domains, the abstractions might be unstable and prone to be inadequate, while the data required of the engine might be much easier to anticipate and make stable in advance. So in those cases, it can actually be more beneficial from a maintainability standpoint (the ease of changing and extending the system) for dependencies to flow towards data rather than abstractions. In an ECS, the most unstable parts (as in parts most frequently changed) are typically the functionality residing in systems (PhysicsSystem, e.g.), while the most stable parts (as in least likely to be changed) are the components which just consist of raw data (MotionComponent, e.g.) which all the systems use.