Avoid hidden complexity

It's tempting to admire complex code that takes time to understand. But complexity is bad precisely because it takes longer to understand.

A subtle form of complexity is hidden complexity. This happens when code does something that you may not even think about until you need to understand what's been hidden.

Here's a simple C program. I'm not trying to trick you; it does what it looks like — it prints out "Hello, World!" Think about what may be hidden here:

#include <stdio.h>

int main() {

  printf("Hello, World!\n");

  return 0;

Imagine this code base grows a bit, and you find yourself using tools like homebrew or apt-get to install libraries. You've been happily including header files from these libraries willy-nilly until BAM you get an error like this:

fatal error: 'some_header.h' file not found

Suddenly you realize that you have no idea how your C compiler found stdio.h or other header files. Maybe you vaguely remember that they might be in /usr/include, but if you're on a recent version of macOS, that directory no longer exists.

You might say "well, that's ok, I'll just use -I flags and tell the compiler exactly where to look." But this is clearly a workaround, and it comes with the cost of not knowing how to fix related problems. What if two different library versions exist in two locations — which one will your compiler use?

This same issue exists for many languages. As an example, importing modules in Python is surprisingly nuanced. (Python coders: Consider virtual environments, python 2 vs 3 differences, and cyclic imports, to name a few fun points.)

These are complexities chosen by folks building languages or compilers. Similar "opportunities" for being clever arise for language-users as well. As an example, it's common for web frameworks to essentially hide the call graph leading to route handlers. In other words, web developers don't typically write anything like this:

if (url.startswith('/pizza')):

This is somewhat boilerplate code, so it makes some sense to factor it out into the framework. However, in some cases a route-handling function may never be called. Or it may be called with unexpected data. This is because some hook functions may be called to perform security checks or data lookups on your behalf before your route-handler gets called. But if something unexpected happens, you really want to have transparency into that call graph.

In many cases, hidden complexity is sold (occasionally with pride) as a steep learning curve with the advantage of giving you clean code. But the purpose of code is to be understood, and to evolve with your needs. Clean code is easy to learn. Not complicated.