DEV Community

Cover image for SLAP Your Functions!
Arun Sasidharan
Arun Sasidharan

Posted on

SLAP Your Functions!

No-Brainer Functions

I’ll admit it, for the longest time I had no idea that a lot of thought goes into writing functions. For me, creating a function meant writing a block of code that is reusable. It’s a simple idea and it stuck with me, function == reusable code.
I had no notion of structure or length or meaning. With time, as the project grew, things started to look like long scripts. There was almost a pattern to these long functions with code and comments interleaving with each other.

long

Long Functions

Long functions are very costly:

  • Hard to read and remember
  • Hard to test and debug
  • Conceal business rules
  • Hard to reuse and lead to duplication
  • Have a higher probability to change
  • Can’t be optimized
  • Obsolete comments

Long functions by design have very low cohesion and know too much about the system, high coupling. This is orthogonal to the fundamentals of software design which say components should have high cohesion and how coupling.

Single Level of Abstraction Principle(SLAP)

It’s difficult to judge if a function is long by counting the number of lines. Is 50 too long? What about 25 or 10? How small is small enough? This is where SLAP comes into play.

Code within a given segment/block should be at the same level of abstraction.

So the question is not how long a function is, it’s what is the level of abstraction of a function? A function should not mix different levels of abstraction. For ex. a function doing form validation should not make I/O calls.

As a thumb rule:

Functions should do just one thing, and they should do it well. — Robert Martin

Functions that do more than one thing face the same drawbacks as long functions. This rule becomes even more obvious when you start testing your code. It is so much simpler to test functions that do one thing as you don’t have to worry about all the complex permutations and combinations.

Compose Method Pattern

Often times when you have comments explaining blocks of code, they are candidates for function extractions.

extract

When you use ExtractMethod a bunch of times on a method the original method becomes a ComposedMethod. It is composed of logical steps that read like the way we communicate and hide the implementation details.

Extract till you just can’t extract any more. Extract till you drop. — Robert C Martin

TL;DR

As Kent Beck said, divide your program into functions that perform one identifiable task. Keep all of the operations in a function at the same level of abstraction. This will naturally result in programs with many small functions, each a few lines long.

Originally posted on Hackernoon

Discussion (7)

Collapse
jvanbruegge profile image
Jan van Brügge

Just extracting a commented part of a function into another one does not help to make it more readable, because now, to understand what it does, you have to jump around in your code. This mental contrxt switch makes it harder to read actually.

Of course you should avoid long functions. But do this by abstracting the problem, not the implementation!

Collapse
dmerejkowsky profile image
Dimitri Merejkowsky

This mental context switch makes it harder to read actually.

Maybe, maybe not ...

Consider:

def tweet_to_html(tweet):
   res = re.sub(r"(^|\s)@(\w+)",
                r'\1<span class="handle">@\2</span>'
                tweet)
   res = "<pre>" + res + "</pre>"
   return res
Enter fullscreen mode Exit fullscreen mode

versus:

def tweet_to_html(tweet):
    res = insert_span_around_handles(tweet)
    res = surround_with_pre(res)
    return res
Enter fullscreen mode Exit fullscreen mode

I find the second version much easier to understand :)

Collapse
jvanbruegge profile image
Jan van Brügge

the first could be written:

def tweet_to_html(tweet):
    //insert span around handle
    res = ...
Enter fullscreen mode Exit fullscreen mode

Here the function is nothing more than specifying a name, you can do this in a comment

Thread Thread
lewisvail3 profile image
Lewis Vail

If you just add a comment, you still have to skim past all the implementation details to figure out what the function is intending to do. With this example this, since it's such a small function this isn't too burdensome but if the implementation is more complex, then that's a lot of noise to sift through to determine what the function does.

Collapse
arne_mertz profile image
Arne Mertz

Hi, nice article! While single level of abstraction is a useful concept, you are mixing it up with another concept here. The form validation vs. I/O example is about Separation of Concerns, as is the Bob Martin quote.
Having said that, long functions usually violate both principles and more.

Collapse
ericbonow profile image
Eric Bonow

One big advantage of this approach predictably is also writing more straight forward unit tests.

Collapse
millebi profile image
Bill Miller

A primary component of properly breaking things up is to name the functions appropriately. Function names of "Block1" and "Block2" should have you hung outside for paintball practise!

I also like to remember that depending on the language in use the compiler may decide to put the code back into a single monolithic block for performance reasons. And sometimes that's important to remember.