Code Philosophy 101 — Introduction, Statement of Intent, and an Example

Zachary Lome
7 min readNov 20, 2020

Hello, and welcome to the first of (hopefully) many articles on Code Philosophy. By the end of this article you should have a firmer grasp on your own understanding of your particular code philosophy, as well as a better understanding of some basic heuristics for identifying when a particular coding tool or strategy can be considered useful.

What is Code Philosophy?

Richard Stallman, By Man with one red shoe at the English Wikipedia, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=4420870

Before I begin, I want to define what the purpose of this article— and every article I will write — is, and how it relates to what I call “code philosophy”.

Code has but one purpose: to enable the building of tools that real people can use to improve some aspect of their life. But just like any tool, there are good and bad tools for any given category, and there are good and bad ways to use those tools. Someone writing PHP for low-level systems infrastructure work is using a bad tool to a bad purpose; someone writing python code to run machine learning is using a(n arguably) good tool to good purpose. But how can we actually say good or bad definitively, either in use or in implementation?

Code Philosophy is the framework by which software engineers understand their code to be well- or poorly-written and understand how well any arbitrary slice of code suits the purpose for which it was implemented. That means that there many code philosophies — as many as there are developers — but also that there is a specific layer of developer knowledge in which we can improve ourselves and others. Typically, discussions around writing good code simply ascribe already-foregone conclusions as to what is or isn’t good. Code Philosophy as a discussion, then, is about recognizing our frameworks by which we approach code design and quality, and establish a clear set of strategies, ideologies, and heuristics that enable us to reduce the difficulty of writing good code.

Applied Philosophy

Sandi Metz, a Ruby developer and a Code Philosopher I admire greatly, talking at RailsConf. Source: https://sandimetz.com/speaking

Any discussion around any philosophy is only as good as its application (a statement that I imagine would get me pilloried in universities the world over). When I am talk about code philosophy, I am always talking about “how can I grow as a developer by having a consistent worldview and thus output as to how code should be written”. It is not useful or interesting to discuss patterns or anti-patterns, data structures or service interfaces, domain ownership strategies or meta-languages, or any of a myriad other topics without first having a solid foundation of why do these things matter and how can I apply a consistent reasoning to these things to solidify or break down my means of applying that reasoning to these things.

As you go forwards reading this, always keep in mind how these strategies might be useful to you. That is why I am writing this: to improve my own understanding of these things, to clarify in my mind where I may be disagreeing with myself at the ideological level, and better leverage the available tools to write better code.

An Example of Applying Philosophy To Paper — DRY, KISS, and Why We Care About Either

DRY and KISS (amongst many other acronyms) are great examples of Code Philosophy in action, because they are mantras specifically about the ideology of what good code does, and not exhortations about specific code implementations. For those that are unfamiliar with the above terms:

  • DRY — Don’t Repeat Yourself
  • KISS — Keep It Simple, Stupid (or, Keep It Simple and Stupid)

Both of these are very good platforms upon which to base your code philosophy. Part of it is that their value is borne out in their use — anyone who has worked on a codebase that was Kept Simple (and Stupid!) and that utilized libraries and method isolation that ensured code wasn’t repeated can see its value; those that have had to work on copy-paste spaghetti nightmares or overly-generalized eighty-parameter methods with a million switches probably see that value all the more.

But what is interesting about these two phrases is that they are, at some level, at odds with each other; namely, one of the key mechanisms by which we can avoid repeating ourselves is by providing switches and flags to various methods, which increases complexity. Conversely, one of the easiest ways to ensure that your code is simple is to have straightforward “paths” through which your code travels from ingress to egress, which, when you have to manage multiple concurrent states, increases the likelihood of you having to repeat yourself in one form or another.

To figure out, ultimately, how we can keep things DRY and keep things simple, we have to dig into what both of these phrases are trying to get at — why do we state these things at all? Why do we care?

Fundamentally, it boils down to two concerns:

  1. The code should be readable and understandable by your future self and any hypothetical collaborators; and
  2. The code should be extensible, improvable, and remediatable without significantly increasing the risk of breakage.

Let me break those two points down:

Your code should be clear

One of the worst things about being a developer is having to figure out someone else’s code. It just is. Any dev worth their salt has stories of having to dig twenty layers deep to find some random bug. It’s awful and we should be better about it when we write our own code.

This is because you’re not going to be alone in handling your code. Even if you think you understand your idiosyncratic methods of organization, the truth is that the you six months or a year from now isn’t going to be the same you as right now, and you’re going to be cursing your past self for poor organization.

Your code should be fungible

Code is organic. What I mean by that is that it changes and evolves over time, by necessity, as the demands for what that code should do change. Sometimes you forgot a corner case in an algorithm; sometimes the client wants a new feature; sometimes you encounter scalability concerns. One of the worst problems I have ever worked on at my current job is tearing out code that has been dead for three years because the underlying test suite was built on that code and, due to years of stagnation, completely at odds with the current (operational) codebase. If the code had been better written initially, the test suite should have been easy to update along with the system change that killed that code dead; but unfortunately it was too unyielding to move easily and so it sat there for years causing problems.

The One Commandment

Metaprogramming via tools like Ruby’s “method_missing” increase system complexity but can reduce implementation complexity when handled well. Source: me.

What’s fun about these concerns is that they really, actually, boil down to one concern, which is the statement upon which all articles I will write in this series shall lie. This is the foundation of my personal code philosophy, the one place where I see junior developers flounder the most and senior developers most misunderstand:

THOU SHALT MANAGE THY OVERALL SYSTEM COMPLEXITY.

There is a game designer who I absolutely adore, Mark Rosewater. One of the key contributions he made to the game he works on, Magic: the Gathering, was the conception of New World Order. In the article introducing the concept (which I highly recommend you read), he pointed out that the best way to grow the game was to manage the immense complexity of that game — new people frequently got stuck on complex behaviors and thus were turned off. But older, more enfranchised players also ran into problems with certain complex behaviors that are the direct reason the MtG rulebook is a massive, terrifying tome.

What impressed me most about the article is that he laid out fundamentally the same complexity concerns that many devs have to deal with on a day-to-day basis:

  • Decisioning complexity — how easy is it to decide what to do? for code, there are lots of decisions made in how the code is written; having too many open choices tends to result in worse choices being made.
  • State complexity — in Magic, boards can grow huge with many cards and many possible interactions. Code which has many possible entry points and/or many possible outputs renders the act of working with the code more difficult.
  • Comprehension complexity — it is an unfortunate fact of life that sometimes you need to write code to do complex things. Breaking it down into manageable, smaller components that themselves are easy to grok is a key way of making things understandable, and thus, something you can work with.

Overview, and Next Steps

I covered a lot of ground here, and one of the ways we can reduce complexity in our writing (which is just as important as our code!) is by clearly summarizing the key points, so let’s do just that:

  • Code Philosophy is the framework by which we can determine what makes code “good” or “bad”.
  • That philosophy has to apply to the core goal of writing code, which is to build tools for people to use.
  • DRY and KISS are valuable tools to add to our code philosophy framework, because they describe ideological positions that apply to the core mantra of Code Philosophy and are demonstrably correct.
  • The core intent of code philosophy, the lens through which all attempts to evaluate code and the process of writing code are viewed, is: Thou shalt manage thy overall system complexity.

Thank you for reading. Next time, I’m going to talk about Where Data Should Live.

--

--