46

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").

1

5 Answers 5

29

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.)

1
  • So the IncludeSuiteSetup() is one level below the IncludeSetups() in abstraction? Jun 5, 2021 at 3:19
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).

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.

1
  • 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. Sep 27, 2011 at 4: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.

0

I think it starts from the method / function name, then you have to identify the consequential steps that are required to achieve the goal defined in the method name. By doing that you are allowed to decompose the problem in only 1 level of abstractions. For instance you may get from a user id the order with a list of products, in this method you need first to get the user from the database then the last order by user then the list of product of the last order, the method name would be List<Product> getLastOrderProducts(int userId). If in your method your are putting code lines about how to perform the queries for retrieving elements from the database you are making wrong. Instead the code should rely on services for retrieving User, Order objects and then returning order.productList() in this way you just get one level of abstraction below, which is performing WHAT is required to retrieve the sub sequential steps in order to get the list of products, without focusing in the same method HOW to get that intermediate step: List<Product> getLastOrderProducts(int productId){User user= userService.getUserById(userId); Order lastOrder = orderService.getLastOrder(user); return lastOrder.getProductList(); }

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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