SLAP: Single Level of Abstraction Principle

Even though this acronym is quite catchy, SLAP is the one principle that you don’t find many people talking about.

Single Level of Abstraction

In essence, the principle proposes that each block of code should not mix what the code does with how it does it. Another way of thinking about it is, whenever possible, the code should describe in steps the actions that will take and then each step can elaborate on that.

For example:

class CreateTask(
private val clock: Clock,
private val localStorage: LocalStorage,
private val observers: List<TaskObserver>
) {
fun invoke(description: String) {
// normalize description
val normalizedDescription = if (description.length > MAX_DESCRIPTION_LENGTH)
description.substring(0, MAX_DESCRIPTION_LENGTH 1) else
description
// create task
val currentTime = clock.now()
val initialStatus = Status.NotStarted
val newTask = Task(normalizedDescription, initialStatus, currentTime)
// save task
localStorage.save(newTask)
// notify
val observingNotStarting = mutableListOf<TaskObserver>()
for (i in 0..observers.size) {
val taskObserver = observers[i]
if (taskObserver.observedStatus == Status.NotStarted) {
observingNotStarting.add(taskObserver)
}
}
for (i in 0..observingNotStarting.size) {
observingNotStarting[i].notify(newTask)
}
}
}
view raw slap__many_levels.kt hosted with ❤ by GitHub

this class has one method that showcases both what it does and how it does it.

If we want to SLAP it we need to delegate the how of each step to its own method:

class CreateTask(
private val clock: Clock,
private val localStorage: LocalStorage,
private val observers: List<TaskObserver>
) {
fun invoke(description: String) {
val newTask = createNewTask(description)
localStorage.save(newTask)
notifyAnyObservers(newTask)
}
private fun createNewTask(description: String): Task {
val normalizedDescription = normalize(description)
return Task(normalizedDescription, Status.NotStarted, clock.now())
}
private fun normalize(description: String): String {
return if (description.length > MAX_DESCRIPTION_LENGTH)
description.substring(0, MAX_DESCRIPTION_LENGTH 1) else
description
}
private fun notifyAnyObservers(newTask: Task) {
observers
.filter { taskObserver -> taskObserver.observedStatus == Status.NotStarted }
.forEach { taskObserver -> taskObserver.notify(newTask) }
}
}
view raw slap__one_level.kt hosted with ❤ by GitHub

here the invoke method simply describes what will happen upon its invocation. A new task will be created, then saved and finally passed to any observers.
For knowing how each step gets implemented we need to drill down one level. For example, creating a new task requires us to normalize the provided description and then create the task. For knowing how the normalization gets implemented we yet again move one level deeper!

Conclusion

Keep hiding how something gets implemented in new methods until you can no longer avoid it!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s