Laracon Online is the official worldwide Laravel online conference and returned in 2019 for its third year. There were nine great talks this year, on a range of subjects including UI and UX, realtime applications, new Laravel features and even Blockchain.
In this article we will be summarising the insightful talk by Jason McCreary (@gonedark) about writing lasting code that is less complex and more readable.
Jason started his talk with a few great quotes, starting with this OSNews comic:
“The only valid measure of code quality — WTFs/minute”
Also this quote from Evan You (Creator of Vue.js) is a useful summary:
“Over the years I’ve started to prefer code that is boring, but easy to understand.”
Evan goes on to explain how “smart” abstractions make it difficult for outsiders to read code and understand what is happening.
Finally from Kent Beck:
“Programs are read more than they are written”
When creating something new, you will still be reading documentation, or reading code snippets on StackOverflow and the ratio could be anything between ten or 100 lines of code read for every line written, or even far higher for legacy code. Jason makes the point that many of us have doubtless read through 1000 lines in a legacy code project before finding that one line that needed changing.
So let’s look in brief at the ten key areas Jason considers most important when writing less complex, more readable code.
1. Formatting:
Formatting matters because as with reading normal text or with reading code, we tend to use patterns to determine what the structure is at a subconscious level.
If the code we write is “visually honest” — making use of indentation and line breaks, we can at a glance determine what the particular code block is doing.
We should choose a consistent format, set up our tools to automate it as much as possible, and move on!
For Laravel, sticking with PSR-2 standards is a good choice.
2. Dead Code:
Dead code can mean any of the following:
Commented code
A method or variable that is no longer needed and has been commented out
Unused code
Perhaps a method that isn’t called anywhere in the code but is still there
Unreachable code
For example multiple return paths in a method where the second return can never happen
Unnecessary syntax
Such as spare breaks in switch statements if the different cases contain returns
Flagged code
For example promotions that rely on flag that is no longer needed, they will never return true so a related function never runs or it just returns early, leading to dead code
The key advice is: Remove dead code!
3. Nested Code:
Simple functions with if
/else
conditions that return true
/false
can be re-factored to just one simple line that’s still easy to understand. For example:
public function isEmpty()
{
if ($this->size === 0) {
return true;
} else {
return false;
}
}
Can become:
public function isEmpty()
{
return $this->size === 0;
}
Empty if
blocks can be switched around to remove the else
, so this example:
public function add($item)
{
if ($item === null) {
} else {
// Your code
}
}
Could become:
public function add($item)
{
if ($item !== null) {
// Your code
}
}
However, an even better option is to put the primary action of the method at the highest level, so to modify the example above, this would become:
public function add()
{
if ($item === null) {
return;
}
// Your code
}
This is now a guard clause, where the the method returns early in the case of a null
value and the action of the function is nice and clear to the reader.
Essentially, avoid using else
and avoid nested code if possible!
4. Using Objects:
If you are using functions that are passing primitives together that form a data group as parameters for example $amount
and $currency
, we can formalise the coupling of this data group into an object.
So to use Jason’s example:
function transfer(Account $from, Account $to, $amount, $currency)
{
$from->debit($amount, $currency);
$to->credit($amount, $currency);
}
$amount
and $currency
can be a Money
object that holds $amount
and $currency
, changing the above example to:
function transfer(Account $from, Account $to, Money $money)
{
$from->debit($money);
$to->credit($money);
}
If you find yourself repeating the same groups of parameters in methods, consider using objects to create more formal code.
5. Big Blocks
Quite often Controller methods can grow to be big blocks of code. The validation code within a store()
method for example can run to several lines. As Controllers should ideally focus on business logic, the validation code should be elsewhere. In Laravel we can easily move it into a separate form request object.
Use implicit model binding to bring in an object into a method, for example:
public function store(EnrollUser $request, $id)
{
$course = Course::findOrFail($id);
// Code for $course
}
Can become:
public function store(EnrollUser $request, $course)
{
// Code for $course
}
If there are common code paths in a method that depend on an if
condition, consider abstracting the switching logic into private method so the controller contains only the higher level function.
When building or creating objects, for example from form requests you can push that code into the model instead of the controller, then use this model code, which again keeps the focus on business logic within your controller.
6. Naming:
When it comes to naming variables in common use cases, such as for loops, as Jason puts it: “Don’t be a unique snowflake”, stick with $i
as it’s a common convention across a number of languages, for example:
for ($i = 0; $i < 10; ++$i) {
// Your code
}
Try to use descriptive names when using foreach
, to help with understanding at a glance, for example:
foreach ($visitors as $visitor) {
// Your code
}
There’s no need to abbreviate your variables, give them a sensible and descriptive name, so for example use $visitors
instead of $vi
.
Try and use variable names that are friendly and descriptive to readers, and avoid names that describe variables from a developer’s perspective, for example $str_error
may show it’s an error message string to a developer, but it’s harder to read at a glance than $error_message
and the fact it’s a string should be obvious from the context.
Don’t spend too long thinking about the perfect name at first — if you need to give a Class a temporary name then let conversations with other developers, time, and even a thesaurus help you to come up with the appropriate name.
When naming classes, use method names and variables that have relatable vocabulary, so a BankAccount
class may have credit()
and debit()
methods that accept $money
.
7. Removing Comments:
This may be a controversial area but the quote from Rob Pike explains Jason’s reasoning:
A delicate matter, requiring taste and judgement. I tend to err on the side of eliminating comments, for several reasons. First, if the code is clear, and uses good type names and variable names, it should explain itself. Second, comments aren’t checked by the compiler, so there is no guarantee they’re right, especially after the code is modified. A misleading comment can be very confusing. Third, the issue of typography: comments clutter code.
There is a simple way by which comments can be eliminated from code, in the case of methods and variables with comments describing them — rename the method or variable to be more descriptive, and comments won’t be required.
There are circumstances where comments should remain, for example comments that explain methods that do not easily describe their functionality by the naming of the methods or variables alone.
Also: DocBlocks != Comments
. Don’t remove DocBlocks! They contain useful information and can be used by IDEs to help with autocompletion and for the generation of documentation.
8. Reasonable Returns:
We should avoid returning null
from a method, as it forces higher level functions to handle this null value. A better approach is to check what the “happy path” of the method will be returning, and return a representative state. So if the method will return an array, then instead of returning null
in the negative path, an empty array should be returned. This means higher level functions do not need to carry out a null
check.
9. Rule of Three:
The basis of the Rule of Three is that with more and more data-points we can determine some rules for the data and predict what will happen next.
The principle of DRY (Don’t Repeat Yourself) in development is well known, and it’s tempting to abstract something into a separate method if it appears in your code more than once.
However, Jason recommends wait until you’ve seen something duplicated multiple times and live with the duplication before you abstract so you know your abstraction will be correct. He illustrates this point with a quote from @sandimetz:
duplication is far cheaper than the wrong abstraction
10. Symmetry:
The final area Jason covered is related to symmetry within your code.
If you have a class that performs the standard CRUD functions (Create, Read Update, Delete), then stick with this naming convention.
Make sure your method and variable names align with the class names.
If you have multiple methods that return a boolean, group the methods that return true and the methods that return false. If they take parameters, try to be consistent with the way parameters are passed into methods.
Try not to mix method calls and lower level functions if possible, for example:
function process()
{
$this->input();
$this->count++;
$this->output();
}
From a symmetry perspective, $this->count++;
doesn’t fit as well in the method. By abstracting count++
into a private method, for example tally()
, the function looks more readable and symmetrical, to give you:
function process()
{
$this->input();
$this->tally();
$this->output();
}
By following these tips, your final code should be easier to read at a glance to determine its key function and purpose.
Summary:
We found Jason’s talk was an essential and thorough run through of a set of important principles that we should always try to adopt when we’re coding.
Jason goes into further depth on all these principles and more in his book: BaseCode — a field guide on writing less complex and more readable code, you can find out more about it at www.basecodefieldguide.com.