A Quick Rebuttal to “Stop Using If-Else Statements”

Zachary Lome
4 min readJan 10, 2022

This is a story version of a response to an article from a while ago, lightly edited, in response to this line:

Having many, specialized classes will make your codebase more readable, maintainable, and simply overall more enjoyable to work with.

tl;dr: no it doesn’t and no it won’t.

About me

I work on a monolith repo with > 1.5mil lines of code plus over a dozen different services each weighing in between 10k and 100k lines of code. I’ve been programming professionally for 15 years across a number of languages (C++, PHP, Javascript, Actionscript, Perl, Python, Ruby) and have worked in both a small boutique shop where I was literally the only dev and for a large multinational corporation where I was one of many hundreds of devs.

I see the above quote, in one form or another, parroted repeatedly by people day after day after day.

It is not at all my lived experience.

What Makes Code Readable?

Code tends to be most readable when the following conditions are met:

  1. variable names are precise and meaningful.
  2. method names are precise and meaningful.
  3. class/module/namespace/object names are precise and meaningful.
  4. related pieces of code are near to each other.
  5. there are not too many related disparate pieces of code in any given code “clump”.

I think 1–3 is pretty clear: naming is a core fundamental of good programming, and I doubt anyone will disagree. Where opinions differ, and where I see the biggest problem, is that people either don’t consider item #4 to be useful, or don’t consider item #4 when working on item #5.

What actually happens when you have many specialized classes?

Primarily, you have a problem of discovery. Specifically, it becomes difficult to correlate observed behavior from the code that results in that behavior.

Too many classes or too many files or too many small single-purpose methods, despite how well-named they are, will result in a developer spending an inordinate amount of time “tracking down” a side effect or behavior.

Consider it this way: when a dev is working to fix a bug, they know both what is happening and what should happen instead. The easiest way to identify where the bug occurs is to know what code causes the effect, and being able to correlate it back to the expected effect. If there are too many segmented pieces of code, however, the likelihood of both the effect occurring as a slice of behavior in multiple places increases, as does the likelihood that the path from the surface (i.e. the API layer, controller layer, etc.) is many levels removed from the effect. Both make this difficult to work on.

Secondarily, you have a problem of confidence. Specifically, it becomes difficult to ascertain if a given change in a given file will have unintended side effects elsewhere, because there are too many disparate code elements to manually review.

When a dev is working to implement a new feature, they are typically provided with a base location or place — add this endpoint, add this UI, add this download button, perform this calculation here, write to logger there. With too many files/classes/modules to create it becomes more difficult to organize the relationship between the business expectations and the expressed code (although with too few files we have another unique problem).

Code exists to execute on real processes in the real world, and if your neatly segmented code provides zero clues as to why it is there — what real processes that piece of code solves for — then the end result over time will be divergence and duplication as different developers implement slightly different solutions around the already-extant code because they either couldn’t find or weren’t confident in touching your code.

A Better Heuristic for Code Architecture

Frequently, when people ask “is this well-architected code”, they look at the things that can reliably be objectively ascertained: things like conditional complexity, scoping, size of classes, number of methods, etc. These are things you should care about, but luckily for us, we have linters that get to tell us when we’re failing to meet our standards.

Instead of building your architecture in the form of “here’s the nicely split up reusable generic agnostic interface”, try asking and then resolving these questions instead:

  • What business purpose does this code solve? Can I place this code in a place (a file or class or namespace) that helps me understand the business purpose and this code’s part in dealing with it? (keep shared code close by)
  • If this code solves many underlying problems, how can I utilize it so that I have high confidence that changes to it won’t cause negative unintended side effects? (make the connections between your code obvious)
  • How many steps does it take to get from an external endpoint into this code? (Avoid needless depth)
  • How many steps does it take to get from this code to every possible calling path? (Avoid needless breadth)

--

--