Based on the wild success of the first “5 Rules of Code”, here are five more rules that you should try to follow. I mean, unless you like doing things the hard way, then, by all means, carry on.
1. Commit Often, Push Always
As we covered in the first five rules, you should be using Git branches in your repository.
My preferred strategy is to have short-lived feature branches — think of them as sandboxes. It doesn’t matter how messy or disorganized the sandbox gets, as long as all the sand stays in the sandbox, everyone’s happy.
For feature branches, as long as the code that is being merged back into your trunk is clean and passes code review, it doesn’t really matter how many commits, how many file changes, how much refactoring has been done.
This means that you have no excuses for committing the cardinal sin: not committing and pushing your changes often enough.
Computers are finicky things, they do weird shit and sometimes that weird shit makes you lose all your data. It happens, it’s life.
That’s why you should always back up your data. And that’s why you should definitely commit and push your code every time there’s a break.
Get up to get a drink? Commit and push. Go to the bathroom? Commit and push. Dying of a heart attack? Call 9–1–1, then commit and push.
Unless Greg from sales comes by and dumps an entire super big gulp of Baja Blast Mountain Dew directly into your laptop, there are no excuses for losing un-committed code.
And even then, if you see Greg coming a second time, you better be done typing
git push before he gets to your desk.
2. Just Say No (to Meetings)
Remember when you first got to your company and people didn’t know who you were yet? Remember when you didn’t get unknowingly added to seventeen meetings a day because “you might need to be there”?
It was great, wasn’t it? You had time. Time to sit down and just blast away at your keyboard, knocking out features and tests like you got paid per function.
But alas, the summer of freedom has given way to the winter of perpetual meetings. Now your daily calendar looks like a patchwork quilt stitched together by a drunk grandma after a few too many shots of peach schnapps.
Status meetings, planning meetings, meetings where you prepare for the weekly meetings, one-on-ones, stand-ups, grooming meetings, retrospectives, all-hands calls — it’s getting a little ridiculous, isn’t it? You need to learn to say no.
For every meeting, every phone call, every single time-suck you need to ask yourself or the organizer: “Am I really needed for this?”
Maybe both you and your manager are scheduled to be in the meeting, or you’re there “just in case”, or it’s an all-hands meeting to talk about what happened in the larger all-hands meeting a few days ago, the result should be the same: don’t go.
Your time, whether the company wants to realize it or not, is more valuable than sitting in a meeting talking about nothing. Instead, it should be spent working on the thing that earns you your paycheck and ostensibly makes the company money.
A friend of mine mentioned the other day that meetings should have a price tag attached to them that shows how much money the meeting costs in wages and lost revenue. I have a feeling most meetings wouldn’t happen if that were the case.
Just say no.
3. Quality > Quantity
In software engineering, quantity doesn’t really mean anything. Number of lines doesn’t matter as long as your code is sound. Number of engineers or files in a repository doesn’t matter either.
What matters is quality. Does the thing work the way it’s intended to every time, or does the engineer produce working, testable, documented code?
Does one of your engineers not work a full 40-hour workweek, yet still somehow hits all of their targets? Quantity doesn’t matter. What matters is the quality of work, of time, of code.
4. Keep Your Dependencies Cold
Software engineering is a unique profession. There are not many others whose work has a half-life. Ice sculptors, body waxers, and developers all watch as their work gives way to entropy.
For developers, a lot of that instability and chaos comes in the form of dependencies; they shift and mutate over time as features get added and removed or bugs get introduced.
Function signatures change, API’s break, endpoints get renamed, repositories move. Everything changes.
In my early days of development, I wrote mostly in Node.js on Fedora Linux. Being that Fedora is a bleeding-edge distribution, change is a constant. Packages needed updating just about every time I booted my machine.
After a while my projects stopped working, every time I updated a dependency or upgraded a system package, I would have to go back and fix whatever breaking changes were made between versions.
It became exhausting and a majority of my time was spent simply maintaining the code that I had already written, not developing new features. In the end, I abandoned both Node and Fedora for CentOS and returned home to Python.
Use me as a cautionary tale: when at all possible you must freeze your dependencies. Dependency freezing comes in two main ways: version pinning and vendoring.
Version pinning is when you tell your package manager (whether it be
cargo or something else) exactly what version of every single package you want to install.
While this still relies on that package manager being able to find and download that package, you’re now guarded against new package versions being released and breaking all of your code, as would happen if you just installed the latest version every time.
But, as I’ve already alluded to, version pinning relies on outside services to maintain.
The second method is vendoring. You “vendor” your dependencies when you package them — code and all — in with your application.
In Python, this might be including
psutil as part of your source so you don’t have to install it on deployment, or in Go running
go mod vendor to copy all your dependencies to the
vendor directory inside your project.
And because you’re packaging all of the code your application requires to run alongside the application code, it no longer depends on any outside sources.
The downside of this is that your repository becomes incredibly bloated and it goes against a number of “best practices” for many languages.
5. Gotta Keep ‘Em Separated
Application requirements change over time. Databases change, are deprecated, are bought by Oracle, or otherwise become unsuitable for use. Maybe you’re migrating to AWS and want to use RDS or want to make the jump from Microsoft SQL Server to PostgreSQL.
Whatever the case is, changing databases is not a trivial task. But you can make it less complicated by separating your application logic from your database logic, creating a data access layer that abstracts database-specific features from your business logic.
Its all too easy, especially when writing an HTTP API, to just make database calls from your route handlers. Calling
mysql.Query() might be easy enough now but you will regret it in the future.
What happens if you decide you want to use PostgreSQL in production? Or the
mysql library changes an interface?
Now you have to go back and refactor your code in 1,000 different places. It also violates the DRY principle of not repeating code if you don’t have to. So, what should you do instead?
Abstract everything. Write a database layer that sits between your application logic and your database logic that handles everything.
So, now, when you need to make changes to how you access your data, you only need to make changes to one or two places in your data access layer, not your actual application logic.
Writing generic interfaces for database operations not only allows you to change databases with less time investment, but now you can do things like generate query time metrics or create virtual “views”.
The good news is that if you use a framework like Spring, this functionality is already built-in. Any good ORM should allow you to switch databases as well.
Did I miss anything? Any other rules of code that you’d like to see included if/when there’s a next installment? Disagree with anything? Let me know in the comments!