The “Clean” Code Debate: Recognizable is Better Than Readable

As developers, we spend a lot of time discussing (see: arguing) the finer points of “clean code.” We argue over whether code should be indented four spaces or two, whether braces should be on the same line as the function definition or on the next line, and whether we should use camelCase or snake_case. These debates can become quite heated, with developers on either side of the argument convinced their way is the “right” way.

But I’d argue that these debates are largely irrelevant. Sure, we can and should discuss and decide on some project norms, but once we configure the linter, we should stop thinking about it. Clean code is certainly important, but people tend to get hung up on minor issues when discussing that. Clean code is much bigger than syntax — it’s project structure, testability, and, in my opinion, recognizability. Simply put, code that’s easy to recognize and quickly pattern match is easier to read and understand.

Clean Code: Two Examples

Consider the following code snippet:

export const MyButton: React.FC<MyButtonProps> = ({
  color,
  width,
  height,
  className,
  children,
  id,
  type,
  onClick,
  title,
}) => {
  const handleClick = () => {
    registerAction("click", "button", id);
    onClick(id);
  };
  let buttonTitle = title;
  if (type === "submit") {
    buttonTitle = toUpper(title);
  }
  return (
    <button
      onClick={handleClick}
      title={buttonTitle}
      style={{ color, width, height }}
      data-testid={`button-${id}`}
      className={className}
      type={type ?? "button"}
    >
      {children}
    </button>
  );
};

Now consider this alternative version:

export const MyButton: React.FC<MyButtonProps> = (props) => {
  const handleClick = () => {
    registerAction("click", "button", props.id);
    props.onClick(props.id);
  };
  const buttonTitle = getTitle(props);
  return (
    <button
      onClick={handleClick}
      title={buttonTitle}
      style={{
        color: props.color,
        width: props.width,
        height: props.height,
      }}
      data-testid={`button-${props.id}`}
      className={props.className}
      type={props.type ?? "button"}
    >
      {props.children}
    </button>
  );
};

Which one is “cleaner?” Which one is more “readable?”

Readable vs. Recognizable

Many would argue that the first option is both, including the hugely popular Airbnb Style Guide. As the style guide recommends, it has shorter line lengths, destructured objects, and property shorthand, so it must be “cleaner.” But the truth is, this “clean, readable code” takes more mental energy to read.

Now consider which one is more recognizable. Which one is easier to scan and understand at a glance?

In the first option, the reader needs to parse whether onClick or handleClick are coming from props, or if they’re declared inline. Likewise, the same with buttonTitle vs props.title. Additionally, a const value makes it very clear what the thing is, versus a let, which introduces uncertainty. In the second option, it’s immediately obvious that values coming from props are unchanged. Also, what the heck is id? As we move further away from a call site, we lose the context of what the thing is and need to think about what it could be.

The truth is, both versions of the code are perfectly valid and will execute the same way. Ultimately, this is a small contrived example, and it’s certainly not comprehensive, but I believe the point holds true for most code. I also don’t mean to get too hung up on destructuring and mutability. But, these are good ways to illustrate the importance of quick pattern matching over “readability.”

Pattern Matching

As developers, we don’t typically read code line by line. Instead, we scan large, complex codebases quickly to get an overall sense of what things are doing. We tend to pattern match more than read.

Code can change quickly, and we don’t have the luxury to spend hours poring over every individual line of code. We need to be able to quickly scan the code and get an idea of what’s going on. If the code follows recognizable patterns and conventions, it’s much easier to understand quickly.

Of course, there will always be times when we need to deal with “unreadable” code. Perhaps we’re dealing with legacy code written by someone else, or maybe we’re dealing with a particularly complex piece of code. But when that time arises, we can dive into “unreadable” code and spend the time we need to understand it.

So, instead of arguing over minor details of “clean code,” let’s focus on writing code that is easy to recognize and understand. Of course, clean code is still important, and we should strive to write code that is both clean and recognizable. But let’s not get bogged down in debates over minor semantics. Instead, we should focus on the bigger picture of writing code that is easy to read, understand, and maintain.

 
Conversation

Join the conversation

Your email address will not be published. Required fields are marked *