Writing Clean Code (Part 2)

Main Thread • 4 min read

In Part 1 of Writing Clean Code I outlined three simple practices of formatting, naming, and avoiding nested code. All in an effort to improve code readability.

In Part 2, I want to go a little deeper and cover grouping. When I say grouping, I'm really talking about the Object Oriented Programming paradigm of encapsulation. Whether we group the code into a function or a class is often not important. What is important is did we improve the readability of the code.

To measure our change, we should ask:

Did we improve readability?

Admittedly a bit subjective, but you push yourself to stay objective. I've been pair programming for the last two years. Developers tend to agree on fundamental readability. Where we differ at the edges. These nuances can lead to some pretty great discussion.

What to group is often easy to identify. We can all point out the code we don't like. We said how to group the code is not important. The question that remains is when to group code. When do I clean up the code by grouping?

Let's look at three motivations for grouping code.

Improving communication

Any bit of code which requires additional context is ripe for grouping. I prefer when my code is not written in a way that requires me to know the business logic. No matter how simple the implementation, I'll never understand. By grouping this code, we provide an additional layer of abstraction. A way to shield ourselves and future developers from the inherit complexity of the system.

Consider our previous code sample:

1function canView($scope, $owner_id)
2{
3 if ($scope === 'public') {
4 return true;
5 }
6 
7 if (Auth::user()->hasRole('admin')) {
8 return true;
9 }
10 
11 if ($scope === 'private' && Auth::user()->id === $owner_id) {
12 return true;
13 }
14 
15 return false;
16}

While the logic is straightforward, we improve communication by extracting it into contextually named helper methods.

1function canView($scope, $owner_id)
2{
3 if ($scope === 'public') {
4 return true;
5 }
6 
7 if (isAdmin()) {
8 return true;
9 }
10 
11 if ($scope === 'private' && isOwner()) {
12 return true;
13 }
14 
15 return false;
16}

Methods like isAdmin() and isOwner relay business logic making it easy to understand. Once understood I can apply it easily to other areas of the codebase. In the end, we didn't just improve communication, we also taught the developer about the code.

It's important to point out that I didn't group all of the code. There is a mental cost for every grouping. Each needs to provide enough value to cover its cost. In this case, I didn't group logic into a hasScope() function as it not only didn't improve communication, but the method signature is just as verbose as the expression.

Couple data

Another principle in programming is low coupling. Coupling is not bad. In fact, it's good when data or code truly belongs together. We can identify areas for coupling by spotting logical connections or by a similar rate of change.

Consider the following code sample:

1function plot($x, $y, $z)
2{
3 // ...
4}
5 
6function transfer($amount, $currency)
7{
8 // ...
9}
10 
11function substring($string, $start, $length)
12{
13 // ...
14}

Only the function signatures here as I want to focus on the parameters. You may not see the grouping opportunity right away. No worries, it's because we all suffer from primitive obsession. Nothing wrong with using primitives. But our propensity to only use them may prevent us from grouping this data into an object.

1function plot(Point $point)
2{
3 // ...
4}
5 
6function transfer(Money $money)
7{
8 // ...
9}
10 
11function substring($string, Range $range)
12{
13 // ...
14}

By encapsulating the data within an object, we not only improve the coupling but also provide a place for additional logic. We can move any inline logic related to this data to the object. Take a minute to read Martin Fowler's Range object for a more in-depth example.

Organizing code

Last but not least is simply organization. Don't be afraid to split similar code into its own function or class. So long as it carries its own weight, it will likely improve the codebase.

Spotting these is more high level than spotting data coupling. Here we take more of a visual approach. If something doesn't seem to match the local aesthetics, it may belong elsewhere.

Consider the following code sample:

1namespace App\Models;
2 
3class User
4{
5 public function find($id) {}
6 
7 public function create() {}
8 
9 public function save() {}
10 
11 public function destroy() {}
12 
13 public function displayName() {}
14 
15 public function displaySignature() {}
16 
17 public function displaySalutation() {}
18 
19 public function createBadge() {}
20 
21 public function printBadge() {}
22}

Here we have a model. Typically a model primarily contains the CRUD methods (create, read, update, delete). While it's perfectly acceptable for the model to contain additional methods, we may notice relationships between these other methods in the model.

By name alone we can spot methods related to display and other badge methods. We may be able to organize this code elsewhere. In this case, the display methods can be extracted to a Presenter class. The badge methods to a Printer class or into its own Badge object.

Give these motivations a try. Maybe some work for your codebase, maybe some don't. The answer to did we improve code readability may vary from developer to developer and project to project. But always ask the question…

Find this interesting? Let's continue the conversation on Twitter.