Collate The Concern Of Logical Branching
If your code requires a logical branch, try to do it only once. Your code will be much cleaner and easier to read.
Making the same logical branch (i.e. exactly the same logical conditions) in multiple places should be considered an anti-pattern, or at least a code smell. The resulting code is always cluttered and messy. Try to refactor it to have only one occurrence of the logical branch.
In graphical form, the left hand pattern is much easier to understand than the right hand pattern:
Make Branching Explicit
One (misguided) way to save a line of code is to write a logical branch like this:
x = 0 # Default value.
if some_condition is True:
x = 1
Now, does this look like a logical branch? At first glance, the reader notices that a variable is set, then mutated if a particular condition is true. The pattern is: 1) Do some work. 2) If a condition is true, then undo and redo the work.
What does mutating a variable have to do with logical branching? The answer is: nothing. This mutation is not necessary to achieve logical branching. In fact, while all languages allow logical branching, there are modern languages with an emphasis on safety that strongly discourage mutation, or even forbid it. (Eg. Rust, and functional languages.) Mutation typically makes code harder to reason about, so should be kept to an absolute minimum.
The meaning of the code is: make a binary logical choice. Therefore, the code should reflect that meaning as directly as possible. The traditional if else construction does this perfectly:
if some_condition is True:
x = 1
else:
x = 0
The other benefit of the traditional if else is that the code at the same logical level (down one branch or the other) is at the same indentation level.
In the diagrams below, the meaning of the code is shown as the tree with red links, and the execution flow of the code is shown as the black arrows.
The left-hand diagram of the code using mutation shows how the code actually traverses one branch of the tree before encountering the conditional. This disconnect from the semantics of the code has a cognitive cost. So the 'shorter' method using variable mutation is actually harder to understand. Don't try to save a single line of code at the expense of readability. Just use the traditional if else construction.
Use Polymorphism To Eliminate Logical Branching
Consider this code, a toy example of part of some kind of graphics program. It may not be realistic, but it illustrates a point.
class Square:
def _init_(width):
self.width = width
class Circle:
def _init(radius):
self.radius = radius
def calculate_total_area_of_shapes(shapes: list):
total_area = 0
for shape in shapes:
if isinstance(shape, Square):
total_area += area_of_square(shape)
elif isinstance(shape, Circle):
total_area += area_of_circle(shape)
else:
print("Shape not recognised. Cannot calculate area.")
return total_area
def area_of_square(square: Square):
return square.width ** 2
def area_of_circle(circle: Circle):
return 3.141592654 * circle.radius ** 2
The important point to note is the logical branching in the function calculate_total_area_of_shapes().
This example code can also be considered a canonical example of the value of method polymorphism. Let us rewrite the code to use method polymorphism.
class Square:
def _init_(width):
self.width = width
def area(self):
return self.width ** 2
class Circle:
def _init_(radius):
self.radius = radius
def area(self):
return 3.141592654 * self.radius ** 2
def calculate_total_area_of_shapes(shapes: list):
total_area = 0
for shape in shapes:
total_area += shape.area()
return total_area
The code has been simplified considerably, and is easier to read. This shows the value of method polymorphism.
But let's dig a little deeper. Obviously, the two functions for calculating area have been moved to be attached to the data structures they operate on. And the main function calculate_total_area_of_shapes() is drastically simpler because it no longer contains the logical branching. Was it moved? Upon inspection, the logical branching has apparently completely disappeared from our code! Where did it go? In fact, the logical branching is still inside the compiled (byte) code in the form of look-up table or similar. Method polymorphism has allowed us remove the logical branching from our source code, where it adds complexity for the human reader, and move it into the compiled code, where it is completely out of our sight, and now the responsibility of the computer.
Removing the complexity of logical branching from our source code is perhaps an under-appreciated aspect of object-oriented techniques such as this.
🙠