It's not hard to debunk cliched phrases like Don't repeat yourself. It's clear they don't apply across all possible scenarios, however, they've still guided and shaped me as a developer.
I want to discuss a few programming principles that have influenced me, in chronological order:
- DRY - Don't repeat yourself
- PEP20 - The Zen of Python
- Unix philosophy
- Optimizing for testability
- AHA programming - Avoid Hasty Abstractions
- Optimizing for deleteability
DRY - Don't repeat yourself
My earliest memory of programming principles comes from university. I remember being introduced to functions and how they enable code-reuse without copy-paste. DRY became my mantra as I hunted down code duplication candidates to be extracted as utility functions.
Needless to say, I spent the next few years obsessively optimizing my codebases much too early. However, over time I became better at recognizing the areas where rigorous application of DRY has diminishing returns (CSS, UI styling) vs. where it's absolutely critical (data access layer).
PEP20 - The Zen of Python
There should be one-- and preferably only one --obvious way to do it.
I learned to code in Python. Unlike Ruby and Perl, Python isn't shy about spelling out best practices. I've since moved on to other languages but I still carry the mottos from The Zen of Python with me. Like "Explicit is better than implicit". I highly recommend giving them a read to inspire you how to improve your code.
Needless to say, I spent the following years painstakingly converting for-loops into more readable List Comprehensions 😉.
“Write programs [or functions] that do one thing and do it well.” And “Expect the output of every program [or function] to be the input of another, as yet unknown, program.”
For those of us working in academia in the early 2010s, cloud computing was out of reach. We ran all of our analyses on our own infrastructure. This meant gluing a lot of scripts together with Bash. Spending my days inside Unix terminals certainly rubbed off on me. I came to discover and appreciate interoperable and single-purpose tools like
grep with friends. A good rule of thumb I brought with me from this time was:
If I have to use “and” when describing what a function/module/program does it’s time to break it up.
As I build React-based interfaces I still aim to design the ideal component API before hooking it up to any specific business logic that is inferred from my API or global state. This keeps them for getting tightly coupled to what the current business logic demands.
Optimizing for testability
I vividly remember my discovery of unit testing. Still at my academic job, I obsessed about reaching 100% test coverage which I could brag about in my open source project READMEs.
Reaching that goal meant I needed to account for testability already in the initial software architecture. No more spaghetti code! No more raw MySQL queries! I was pushed to get familiar with advanced patterns like dependency injection. This usually brought nice side effects that made my solutions more robust and generalizable.
I used testability to:
- ...decide between possible implementations, always choosing the one that made my code more testable.
- ...assess overall codebase health, basing it on how hard it was to implement new tests.
Poor testability is a code smell, however, I've never come across a testable yet crappy codebase.
The focus on testability also pushed me to think about separation of concern before anything else. Needless to say, I spent the next few years prematurely optimizing my codebases, separating code into neat layers and implementing plugin systems that never grew beyond a default add-on.
AHA programming - Avoid Hasty Abstractions
Duplication is far cheaper than the wrong abstraction (Sandi Metz)
After leaving academia, I started working for an agency. We often focused on quick turnaround to get our products in the hands of users early. This meant that testing became only one of many concerns. Instead I developed a new appreciation for maintainable code that could be successfully handed over to new maintainers.
AHA programming comes with several important realizations:
- Whenever we extract code into a function, we also create a new layer of abstraction. Doing it too early contributes to avoidable technical debt.
- A shared function also couples two (potentially) separate modules by both depending on the new abstraction. This complicates future refactors.
- Finally, abstractions tend to live a life of their own. Even when they are no longer used, they tend to stick around. So many time, I've worried that removing deprecated functions will cause more harm than keeping them around just in case.
Optimizing for deleteability
I've come a long way from when I itched to re-write old codebases, convinced that this time I would get it right. One important realization I carry with me is that I need to write code to understand how it should be structured.
Given this belief, I also accept that code I write today eventually will be rewritten, removed, or refactored. The best way to serve future maintainers (including myself) is to make my code easy to delete. Be humble. Whoever maintains the code a year year from now will know best what to do with it.
An important part of writing deleteable code is to lean into co-location. Related pieces of code should be placed as close to each other as possible. This goes for styles, documentation, and unit and integration tests.
That's pretty much where I am right now. I keep collecting new principles I pick up along the way to fill in pieces of the software development puzzle. What are some programming principles that have inspired and influenced you most? Let me know @robinandeer!
Removing duplication feels good, but is often wrong. The DRY principle is not about code duplication. The meta-principle of good design is ETC [Easier to Change].
If you find yourself passing parameters and adding conditional paths through shared code, the abstraction is incorrect. It may have been right to begin with, but that day has passed.
Let clean code guide you. Then let it go.
When you write code to be easily extractable [...] Your code has to be modular [...] If everything is tangled together like spare extension cords, good luck trying to remove anything [...]