There are quite a few rules/principles that get thrown around in the software world. Some that come to mind are SOLID principles, Design Patterns, Do one thing and do it well, etc. I totally stand by all of them and believe our world would be a much better place if these principles are followed more.
The problem, however, is that those principles are quite abstract and at a very high level. During my code reviews, engineering discussions and writing of code, its tough to apply them directly. You wouldn’t know when you’re violating any/some of them.
I thought of compiling a list of principles that are easier to apply. They are derivatives of the above mentioned ones.
Let’s begin!
I have found myself falling into this trap more times than I’m willing to admit. Let’s create a ParentClass (that will be extended by only 1 ChildClass), let’s store the history of user status changes (even though we need only the latest one now), etc. Don’t!
After burning my fingers a couple of times, I found out I’m not the only one. There’s a principle called YAGNI:
YAGNI- You Aren’t Going to Need It
Martin Fowler has a great piece on it; the gist of it is:
We end up with three classes of presumptive features, and four kinds of costs that occur when you neglect yagni for them.
Now, when engineers or project managers argue we might need it in the future, I tell them YAGNI (and share the above link).
An example: We needed to show the job application status of a job. We thought, let’s store the history of the statuses, we might need it in the future. Needless to say, we didn’t. Moreover, we started seeing duplicate jobs in some cases. Due to a programming bug, the
INNER JOIN
SQL query was giving duplicates on production data. When I realized the root cause, I’m like I violated YAGNI 🤦♂️.Every time you copy and paste code (even if it’s just one line), you’re making a mistake.
Do not copy-paste code. Create a private function and put the common code there. Create a common base class, or extract the common functionality into a module.
In software, DRY is preferred over WET
DRY- Don’t Repeat Yourself
WET- We Enjoy Typing.
“Don’t Repeat Yourself” — How many times do you see that there are similar codes in different parts of a system? The DRY principle, formulated by Andrew Hunt and David Thomas in their book The Pragmatic Programmer, states that “every piece of knowledge must have a single, unambiguous, authoritative representation within a system.” In other words, you must try to maintain the behavior of a functionality of the system in a single piece of code.
On the other hand, when the DRY principle is not followed, this is known as WET solutions, which stands for either Write Everything Twice or We Enjoy Typing.
— Oscar Salas
Another way to look at this principle is to have a Single Source of Truth. I have another piece on the lengths I went to have a single source of truth. I used metaprogramming in production. Totally worth the effort.
Recently, instead of copy-pasting a lot of raw SQL in my ORM, I created a small framework that takes in the params, and creates SQL queries based on that. That took longer than expected; because, you know, SQL. While writing the queries and fixing the SQL errors, I was regretting my decision. Nonetheless, I powered through. Weeks later, I had to add a simple
WHERE
clause in 7–8 complex SQL queries. Fastest change I have ever made. When the code worked in the first attempt, made me smile at the computer.Optimize for code readability rather than for performance. As a software engineer, fresh in college, I took great pride in optimizing code (use fewer CPU cycles).
I wish I had gotten this advice then:
Programs must be written for people to read, and only incidentally for machines to execute.
— Abelson & Sussman, Structure and Interpretation of Computer Programs
All the (premature) optimzations I have ever done have never ever solved (or partly helped) even one of the performance issues I have actually faced. Now, I solve for performance, when the profiler running in production tells me to, or when my apps are crashing with OutOfMemoryExceptions; not a moment before.
Make it correct,
make it clear,
make it concise,
make it fast.
In that order.
— Wes Dyer
Don’t write
x << 1
instead of x * 2
. All modern compilers will generate the exact same output anyway.This is a corollary to both YAGNI and code readability. Delete unused code because you aren’t going to need it, and, nothing is much more readable than anything else!
I always delete classes/functions/variables that aren’t being used. In Java (using the IDE) this is very easy to spot. In python (and other interpreted languages) I have to be more deliberate about code deletion.
This always means:
Don’t commit commented code.
Delete commented code whenever you see it.
Deleting directly unused code is easy. I take it a step further and delete old (unused) API endpoints and other functionality. Because that means I can delete the corresponding view, controller and serializer(s)! It’s not always easy to figure which endpoints are not being used anymore but it is always worth the effort.
My Favourite Code refactor so far!
By far my favourite story is A Conspiracy To Kill IE6: Engineers secretly hatched a plan to effectively deprecate IE 6, and secretly push this change without the necessary approvals from the management.
This banner caused IE 6 usage to suddenly drop drastically!
These “Old YouTubers” first made the old code unused, then deleted the unused code! This story isn’t all about deleting old code. It just illustrates my point very well. Go to the extra mile to delete code.
The best way you can contribute to an open source project is to remove lines of code from it. We should endeavour to write code that a novice programmer can easily understand without explanation or that a maintainer can understand without significant time investment.
Name your classes, methods, class variables and even local variables very semantically.
Whenever you’re introducing a new name, stop and think for a few seconds. Name the symbol as per its behaviour.
I follow a consistent naming pattern:
class User
List<User> users
get_user
and get_or_create_user
. Name your method as it behaves!This actually prevents me from violating the Single Responsibility Principle. If I need to name my class something of the form of
class DoesXAndY
I know I’m making a mistake.In my early open source days, I spent a lot of time renaming my variables after achieving the desired functionality. I used to think “What a waste of time. The code is working”. Now, when I read others’ code, I realize its importance. I don’t feel bad about being pedantic in code reviews: change
num_users
to number_of_users
or user_count
I have done this mistake many times. I take the task of refactoring a part of the codebase. While refactoring, I realize these other things that are broken (as they always are). I fix them as well. Soon, I end up having a thousand line long PR!
I needed to refactor the project’s Job model. It had a lot of related models. I guess the intent was to have a de-normalized schema. Instead, it was a tangled messed of bad data modelling. I knew how it should it look ideally. Naively, I created that schema and started writing migrations. It was like opening a can of worms. So many dependant things broke. It was stressful for me and the entire team. Instead of 3 days and 1 engineer, it took 7 days and 2 engineers. Not to mention the un-needed stress and loss of faith.
In hindsight, I should have removed one dependant model at a time, rather than all at one go. Remove one model, migrate it, test it. Then, repeat this process a couple of times.
“Change One Thing at a Time” extends to the entire engineering process, not just to the coding phase. It’s very common for companies to break up their monolith into smaller services as they grow and handle more scale. This process takes months and years. Not only does the code needs to be refactored, the teams need to be re-organized. Project structure, deployment process, everything needs to change. All this cannot be done in one huge change.
If you every wonder how to do a change that seems like moving a mountain, now you know how to do it. Break the task down into smaller bite-sized chunks, and finish them one by one.
At Apna, we do feature sprints rather than the usual fixed-days (1 week or 2 week) sprint. We build one feature, test it internally, and then release it in production. There is no point in clubbing 2–3 unrelated features in a release. Our sprints range from 3 to 4working days long. This gives us the much needed speed as a startup, and reduces the amount of stress during a release.
This is one of the most counter-intuitive thing I’ve learnt (rather, am learning).
The following excerpt explains it perfectly:
If, like me, you were once a Junior Dev, you may remember your first experience looking at a Senior Dev’s code and thinking, “I can write that. Why aren’t I a senior?”
Yet I tried to write code like that for a long time, and I couldn’t.
What was so mystifying about “Senior Dev” code was not that I didn’t understand it, but that I could understand it immediately, it was fundamentally dumb, and it seemed like there had to be more to it. “Where’s the rest?” I remember thinking. “How does this do all of that?”
Since then I’ve learned all the names of all the principles and qualities of code that make it dumb: YAGNI, Single Responsibility Principle, DRY, Single Level of Abstraction Principle, low coupling, etc. And I’ve become a “Senior Dev” as well.
The greatest lessons I’ve learned are that writing dumb code is actually hard, and that it pays exponential dividends to do so.
— Why Senior Devs Write Dumb Code
Kent Beck said it more provocatively:
Any fool can write code that a computer can understand.
Good programmers write code that humans can understand.
— Kent Beck
I have made the mistake myself of writing cool code like meta programming, but the real achievement would have been to get the same functionality without the “cool” code. In my defence, I did write a lot of comments.
Phew, that’s a long list!
Still, there are many more that I didn’t write down. I too forget to follow them sometimes. I do cut corners under time pressure. So, I wrote them down to remind myself. I have always regretted violating any of the principles a few weeks/months later. These are rules of thumb- you don’t have to follow them, but you’re always better off following them.
The principles mentioned above are not necessarily applicable to all of software engineering. It is more inclined towards high level software projects- python/java, web/mobile apps. The stuff that I work on. Probably most of it wouldn’t be applicable in, say, LINUX kernel development, the GNU C/C++ compiler, etc.
I want this post to be a perpetual work in progress- add, re-arrange and remove ideas from time to time. Reply to this post or hit me up on twitter for any suggestions/corrections.