44

I was reading a book today called "Clean code" and I came across a paragraph were the author was talking about the levels of abstraction per a function, he classified some code as low/intermediate/high level of abstraction.

My question is what is the criteria for determining the level of abstraction?

I quote the paragraph from the book:

In order to make sure our functions are doing “one thing,” we need to make sure that the statements within our function are all at the same level of abstraction. It is easy to see how Listing 3-1 violates this rule. There are concepts in there that are at a very high level of abstraction, such as getHtml(); others that are at an intermediate level of abstraction, such as: String pagePathName = PathParser.render(pagePath); and still others that are remarkably low level, such as: .append("\n").

Adam Lear
  • 31,939
  • 8
  • 101
  • 125
OKAN
  • 725
  • 1
  • 5
  • 8
  • 2
    Related: [What is abstraction?](http://programmers.stackexchange.com/questions/16070/what-is-abstraction). – Adam Lear Sep 27 '11 at 04:17

4 Answers4

28

The author explains that in the "Reading Code from Top to Bottom" subsection of the part that talks about abstractions (hierarchical indentation mine):

[...] we want to be able to read the program as though it were a set of TO paragraphs, each of which is describing the current level of abstraction and referencing subsequent TO paragraphs at the next level down.

  • To include the setups and teardowns, we include setups, then we include the test page content, and then we include the teardowns.
    • To include the setups, we include the suite setup if this is a suite, then we include the regular setup.
      • To include the suite setup, we search the parent hierarchy for the "SuiteSetUp" page and add an include statement with the path of that page.
        • To search the parent ...

The code that'd go along with this would be something like this:

public void CreateTestPage()
{
    IncludeSetups();
    IncludeTestPageContent();
    IncludeTeardowns();
}

public void IncludeSetups()
{
    if(this.IsSuite())
    {
        IncludeSuiteSetup();
    }

    IncludeRegularSetup();
}

public void IncludeSuiteSetup()
{
    var parentPage = FindParentSuitePage();

    // add include statement with the path of the parentPage
}

And so on. Every time you go deeper down the function hierarchy, you should be changing levels of abstraction. In the example above, IncludeSetups, IncludeTestPageContent and IncludeTeardowns are all at the same level of abstraction.

In the example given in the book, the author's suggesting that the big function should be broken up into smaller ones that are very specific and do one thing only. If done right, the refactored function would look similar to the examples here. (The refactored version is given in Listing 3-7 in the book.)

Adam Lear
  • 31,939
  • 8
  • 101
  • 125
16

I think to understand this question, you need to understand what an abstraction is. (I'm too lazy to find a formal definition, so I'm sure I'm about to get dinged, but here goes...) An abstraction is when you take a complex subject, or entity and hide most of its details while exposing the functionality that still defines the essence of that object.

I believe the example the book gave you was a house. If you take a very detailed look at the house, you'll see that it's made of boards, nails, windows, doors... But a cartoon drawing of a house next to a photograph is still a house, even though it is missing many of those details.

Same thing with software. Whenever you program, just like the book advises, you need to think about your software as layers. A given program can easily have well over a hundred layers. At the bottom, you might have assembly instructions which run on a CPU, at a higher level these instructions might be combined to form disk I/O routines, at a yet a higher level, you don't need to work with Disk I/O directly because you can use Windows functions to simply Open/Read/Write/Seek/Close a file. These are all abstractions even before you get to your own application code.

Within your code, the abstraction layers continue. You might have lower-level string/network/data manipulation routines. At a higher level you might combine those routines into subsystems that define user management, UI layer, database access. Yet another layer these subsystems might be combined into server components that come together to become part of a larger enterprise system.

The key to each of these abstraction layers is that each one hides the details exposed by the previous layer(s) and presents a very clean interface to be consumed by the next layer up. To open a file, you shouldn't have to know how to write individual sectors or what hardware interrupts to process. But if you start travel down the abstraction layer chain, you will definitely be able to trace from Write() function call, all the way down to the exact instruction that is sent to the hard drive controller.

What the author is telling you to do is when you define a class or a function, think about what layer you are one. If you have a class that is managing subsystems and user objects, the same class should not be performing low level string manipulation or contain a whole bunch of variables just for making socket calls. That would be the violation of crossing abstraction layers and also of having one class/function do only one thing (SRP - Single Responsibility Principle).

DXM
  • 19,932
  • 4
  • 55
  • 85
3

My question is what is the criteria for determining the level of abstraction?

The abstraction level is supposed to be obvious. It's abstract if it's part of the problem domain -- not part of the programming language. It's hard to be more clear than "highly abstract" == "not real" == "problem domain". And "not abstract == concrete == part of the language". It's supposed to be trivial to decide the abstraction level. There's not supposed to be any subtlety at all.

.append("\n") is not abstract. It just puts a character onto a string. That would be concrete. Not abstract.

String pagePathName = PathParser.render(pagePath); deals with Strings. Concrete things. Partly on concrete programming language features. Partly working with abstract "path" and "parser" concepts.

getHtml(); Abstract. Deals with "Markup" and things that aren't trivial, concrete, language features.

Abstract == not a language feature.

Concrete == a language feature.

S.Lott
  • 45,264
  • 6
  • 90
  • 154
  • 2
    If you define abstract as a quality that describes those things that a programmer creates, i.e. programmer-generated abstractions, then I'm inclined to agree with your word definitions. But all programming languages are abstractions over something more concrete. The choice of programming language determines, to a large extent, what level of abstraction you start out at. – Robert Harvey Sep 27 '11 at 04:29
2

I think the level of abstraction is simple... if the line of code does not directly implement the single responsibility of the method, it is another level of abstraction. For example, if my method name is SaveChangedCustomers() and takes a list of ALL customers as a parameter, the single responsibility is to save any customers in the list that have been changed:

foreach(var customer in allCustomers)
{
    if (CustomerIsChanged(customer)
        customer.Save();
}

Often, instead of calling the CustomerIsChanged() method, you'll find the logic to determine if the customer has changed embedded in the foreach loop. Determining if the customer record has changed is NOT the responsibility of this method! It is a different level of abstraction. But what if the logic to make that determination is only one line of code? It doesn't matter!!! It is a different level of abstraction and needs to be outside this method.