In computer programming, the pyramid of doom is a common problem that arises when a program uses many levels of nested indentation to control access to a function. It is commonly seen when checking for null pointers or handling callbacks.[1] Two examples of the term are related to a particular programming style in JavaScript,[2] and the nesting of if statements that occurs in object-oriented programming languages when one of the objects may be a null pointer.[3][4]
Examples
Most modern object-oriented programming languages use a coding style known as dot notation that allows multiple method calls to be written in a single line of code, each call separated by a period. For instance:
theWidth = windows("Main").views(5).size().width();
This code contains four different instructions; it first looks in the collection of windows for a window with the name "Main", then looks in that window's views collection for the 5th subview within it, then calls the size
method to return a structure with the view's dimensions, and finally calls the width
method on that structure to produce a result that is assigned to a variable name theWidth
.
The problem with this approach is that the code assumes that all of these values exist. While it is reasonable to expect that a window will have a size and that size will have a width, it is not at all reasonable to assume that a window named "Main" will exist, nor that it has five subviews. If either of those assumptions is wrong, one of the methods will be invoked on null, producing a null pointer error.
To avoid this error, the programmer has to check every method call to ensure it returns a value. A safer version of the same code would be:
if windows.contains("Main") {
if windows("Main").views.contains(5) {
theWidth = windows("Main").views(5).size().width();
//more code that works with theWidth
}
}
If the programmer wishes to use that value based on whether or not it exists and is valid, the functional code inside the if
statements is all pushed to the right, making it difficult to read longer lines. This often leads to attempts to "flatten" the code:
if windows.contains("Main") { theWindow = windows("Main") }
if theWindow != null && theWindow.views.contains(5) { theView = theWindow.views(5) }
if theView != null {
theWidth = theView.size().width();
//additional code
}
Or alternatively:
if !windows.contains("Main") {
// handle error
} else if !windows("Main").views.contains(5) {
// handle error
} else {
theWidth = windows("Main").views(5).size().width();
//more code that works with theWidth
}
This sort of programming construct is very common and a number of programming languages have added some sort of syntactic sugar to address this. For instance, Apple's Swift added the concept of optional chaining in if statements[5] while Microsoft's C# 6.0 and Visual Basic 14 added the null-conditional operators ?.
and ?[
for member access and indexing, respectively.[6][7][8] The basic idea is to allow a string of method calls to immediately return null if any of its members is null, so for instance:
theWidth = windows("Main")?.views(5)?.size.width;
would assign null to theWidth
if either "Main" or the fifth subview is missing, or complete the statement and return the width if they are both valid. There are many times where the programmer wants to take different actions in these two cases, so Swift adds another form of syntactic sugar for this role, the if let
statement, also known as "optional binding":
if let theView = windows("Main")?.views(5) {
//do things knowing the view exists...
theWidth = theView.size.width
}
Resolution
Pyramid of Doom can usually be resolved in any language by simply breaking up the code into multiple nested functions (or other groupings). For instance, instead of:
main() {
aaaaa() {
bbbbb() {
ccccc() {
ddddd() {
// do something now
}
}
}
}
}
You can break up the functionality like this:
doSomething() {
// do something now
}
CC() {
ccccc() {
ddddd() {
doSomething()
}
}
}
main() {
aaaaa() {
bbbbb() {
CC()
}
}
}
Similarly, data structures can be broken up by levels, when similar pyramids occur.
Not only is the Pyramid of Doom solved, but it's better practice to not have large, complicated functions; smaller ones are easier to get right, and easier to read, and verify the operation of. Choosing function names for each of these levels will also help the author clarify to readers what is done where. Typically, each level doesn't need many connections to levels that are far away, so separating them out is easy. If there are such connections, the author can re-think their design to something more reliable, because this is a fertile source of bugs.
See also
- Promises, a technique for avoiding the pyramid of doom, e.g. used in JavaScript[9]
- Law of Demeter
- Safe navigation operator, a programming language operator that lets one avoid the pyramid of doom
References
- ↑ Dave Herman (14 December 2011). "Why coroutines won't work on the web". The Little Calculist. Archived from the original on 2016-03-06.
- ↑ "The Pyramid of Doom: A javaScript Style Trap". 27 November 2012. Archived from the original on 2015-12-09.
- ↑ Eberhardt, Colin (8 December 2014). "Tearing Down Swift's Optional Pyramid Of Doom". Archived from the original on 2016-07-31.
- ↑ "New Language Features in Visual Basic 14". 9 December 2014. Archived from the original on 2014-12-25.
- ↑ "Optional Chaining". Apple.
- ↑ "Null-conditional Operators (C# and Visual Basic)". Microsoft.
- ↑ "What's New for Visual C#". Microsoft.
- ↑ "What's New for Visual Basic". Microsoft.
- ↑ Joe Zimmerman (March 28, 2013). "What's The Point Of Promises?". telerik.com.