DEV Community

Cover image for Writing Clean Code
Jason McCreary
Jason McCreary

Posted on • Originally published at jason.pureconcepts.net

Writing Clean Code

I recently started a new job. With every new job comes a new codebase. This is probably my twentieth job. So I've seen a lot of codebases.

Unfortunately they all suffer from the same fundamental issue - inconsistency. Likely the result of years of code patching, large teams, changing hands, or all of the above.

This creates a problem because we read code far more than we write code. As I read a new codebase these inconsistencies distract me from the true code. My focus shifts to the mundane of indentation and variable tracking instead of the important business logic.

Over the years, I find I boy scout a new codebase in the same way. I apply three simple practices to clean up the code and improve its readability.

To demonstrate, I’ll apply these to the following, real-world code I read just the other day.

function check($scp, $uid){
  if (Auth::user()->hasRole('admin')){
    return true;
  }
  else {
  switch ($scp) {
    case 'public':
      return true;
      break;
    case 'private':
      if (Auth::user()->id === $uid)
        return true;
      break;
    default: return false;
  }
  return false;
  }
}
Enter fullscreen mode Exit fullscreen mode

Adopt a code style

I know I’m the 1,647th person to say, “format your code”. But it apparently still needs to be said. Nearly all of the codebases I’ve worked on have failed to adopt a code style. With the availability of powerful IDEs, pre-commit hooks, and CI pipelines it requires virtually no effort to format a codebase consistently.

If the goal is to improve code readability, then adopting a code style is the single, best way to do so. In the end, it doesn’t matter which code style you adopt. Only that you apply it consistently. Once you or your team agrees upon a code style, configure your IDE or find a tool to format the code automatically.

Since our code is PHP, I chosen to adopt the PSR-2 code style. I used PHP Code Beautifier within PHPCodeSniffer to automatically fix the code format.

Here's the same code after adopting a code style. The indentation allows us to see the structure of the code more easily.

function check($scp, $uid)
{
    if (Auth::user()->hasRole('admin')) {
        return true;
    } else {
        switch ($scp) {
            case 'public':
                return true;
            break;
            case 'private':
                if (Auth::user()->id === $uid) {
                    return true;
                }
                break;
            default:
                return false;
        }
        return false;
    }
}
Enter fullscreen mode Exit fullscreen mode

Naming things properly clearly

Yes, something else you’ve heard plenty. I know naming things is hard. One of the reasons it’s hard is there are no clear rules about naming things. It’s all about context. And context changes frequently in code.

Use these contexts to draw out a name. Once you find a clear name, apply it to all contexts to link them together. This will create consistency and make it easier to follow a variable through the codebase.

Don't worry about strictly using traditional naming conventions. I often find codebases mix and match. A clear name is far more important than snake_case vs camelCase. Just apply it consistently within the current context.

If you’re stuck, use a temporary name and keep coding. I’ll often name things $bob or $whatever to avoid getting on stuck on a hard thing. Once I finish coding the rest, I go back and rename the variable. By then I have more context and have often found a clear name.

Clear names will help future readers understand this code more quickly. They don’t have to be perfect. The goal is to boost the signal for future readers. Maybe they can incrementally improve the naming with their afforded mental capacity.

After analyzing this code, I have more context to choose clearer names. Applying clear names not only improves readability, but boosts the context making the intent of the code easier to see.

function canView($scope, $owner_id)
{
    if (Auth::user()->hasRole('admin')) {
        return true;
    } else {
        switch ($scope) {
            case 'public':
                return true;
            break;
            case 'private':
                if (Auth::user()->id === $owner_id) {
                    return true;
                }
                break;
            default:
                return false;
        }
        return false;
    }
}
Enter fullscreen mode Exit fullscreen mode

Avoid Nested Code

There are some hard rules regarding nested code. Many developers believe you should only allow one nesting level. In general, I tend to ignore rules with hard numbers. They feel so arbitrary given code is so fluid.

It's more that nested code is often unnecessary. I have seen the entire body of a function wrapped in an if. I have seen several layers of nesting. I have literally seen empty else blocks. Often adding guard clauses, inverting conditional logic, or leveraging return can remove the need to nest code.

In this case, I'll leverage the existing return statements and flip the switch to remove most of the nesting from the code.

function canView($scope, $owner_id)
{
    if ($scope === 'public') {
        return true;
    }

    if (Auth::user()->hasRole('admin')) {
        return true;
    }

    if ($scope === 'private' && Auth::user()->id === $owner_id) {
        return true;
    }

    return false;
}
Enter fullscreen mode Exit fullscreen mode

In the end, coding is writing. As an author you have a responsibility to your readers. Maintaining a consistent style, vocabulary, and flow is the easiest way to ensure readability. Remove or change these and maintain readability you will not.

Want to see these practices in action? I'm hosting a free, one-hour workshop where I'll demo each of these practice, and more, through live coding. Sign up to secure your spot.

Latest comments (66)

Collapse
 
programmingdive profile image
Programming Dive

I think in the first image you first check for authorization of the user and then check the scope. This way we will avoid code execution for unauthorized user.

Collapse
 
victoryshoe profile image
Victor Shi

Great article, love that you went step by step through your proposed process and showed each small change and how much individual impact it had.

Collapse
 
gonedark profile image
Jason McCreary

Thanks. Definitely check out BaseCode as it has over 40 such examples.

Collapse
 
juankoss profile image
Juan Koss

I think that example can be shorter too!
function canView($scope, $ownerId) {
return ($scope === 'public' ||
Auth::user()->hasRole('admin') ||
($scope === 'private' && Auth::user()->id === $ownerId));
}

Collapse
 
gonedark profile image
Jason McCreary

As noted in other comments, it's not about shorter, it's about more readable.

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
gonedark profile image
Jason McCreary

Seems like you don't agree with any of the post then…

Collapse
 
oleksiirybin profile image
oleksii-rybin

I agree with the idea of clean code in general. And I agree that code example at the end of an article became much better than it was before. And I just added some more things I assume as "clean code" principles.

Thread Thread
 
gonedark profile image
Jason McCreary

Fair. Nevertheless, they contradict mine. One of the contributing factors of messy code is that everyone disagrees on "clean code".

For clarity, I don't believe in a having single return statement. And there is nothing wrong with temporarily naming a variable something obviously incomplete in the spirit of maintaining momentum.

Collapse
 
oleksiirybin profile image
oleksii-rybin

I agree that writing a clean code is necessary. But I have something to add:

1) Decide whether you're using a camel case or not and stick to it, don't mix it up (I personally prefer using camel case in my projects).
2) Only one return statement per function - makes it much easier to read and debug, less likely to have hidden bugs.
3) I don't agree that naming a variable $bob or $whatever to rename it later is a good solution, you could easily forget to do this or be to "tired and lazy" after finishing with the feature. I always name variables clearly from the moment I create it, yes, it could be not easy at first, but you get used to it and it becomes automatic. And if later you decide that you don't like the name, you could always change it, but even if you don't - it still would be meaningful.
4) Also, it doesn't feel right when I see OOP mixed up with functional programming.

Here's my variation of this code:

Collapse
 
joeymink profile image
Joey Mink

Consistently formatted code is surely ideal. But previous efforts I've been a part of have suffered from some combination of the following:

  1. Lack of interest from all members of the team
  2. Differing opinions on what looks/reads best
  3. Too much legacy code to visit

So I've basically landed here: to all who are driven to format new or legacy code, go nuts! People tend to fall in line if there's already momentum.

However: make any formatting-only changes In. Their. Own. Commit. If someone has to review a commit where the whole file changed to figure out why a bug was introduced, that will be a huge fail...

Collapse
 
gonedark profile image
Jason McCreary

Agreed about momentum and separate commit.

However, most of your points are moot when automating this process with a formatter/linter as suggested.

Collapse
 
joeymink profile image
Joey Mink

If it's a new code base or new file, agreed. But what about modifying a legacy file to introduce/change actual functionality -- the automated formatter/linter may obscure the actual changes with a complete reformat, correct?

Thread Thread
 
gonedark profile image
Jason McCreary

To your point, do it in a separate commit. Run the formatter/linter one time manually across the entire codebase. This will provide a basis (momentum) and from then on only changed files need to be formatted/linted (which could be automated).

Collapse
 
ryanwinchester profile image
Ryan Winchester • Edited

Nice job, I've been liking these kind of posts you've been doing lately, and your final function is way better.

However, I feel like playing along, too. How about this? :)

  <?php

  function canView(string $scope, int $owner_id): bool
  {
      return $scope === 'public' || $this->userCanView(Auth::user(), $owner_id);
  }

  function userCanView(User $user, int $owner_id): bool
  {
      return $user->hasRole('admin') || $user->id === $owner_id;
  }

I don't know the context, but I'm pretty sure it doesn't even matter if the $scope is 'private', if the current user is the owner.

Collapse
 
nicolasvanhecke profile image
NicolasVanhecke

Great read. This might push me over the edge to actually start doing it. Been thinking about it for a while to have 1 style.

Collapse
 
danieldubovski profile image
Daniel Dubovski

Couldn't agree more. Was very close to writing my own article emphasizing the need to be clear when writing code.
To be more specific, the guidelines you iterated, help read code as if it was a story, or a self explanatory recipe. Bad naming often helps find bad code separation patterns, while nesting tends to make code hard to follow. Very good post!

Collapse
 
raffacabofrio profile image
raffacabofrio

Great article. 👍