Coding Style Changes
Looking back at some of my previous personal projects on Github, it made me realize how much my coding style has actually changed since then.
One of my previous personal projects, the Simple Poll Engine which lets you quickly integrate a poll on your website, was written 5 years ago. Looking back, seeing that I’ve declared it “simple”, it’s kind of ridiculous how many files are actually contained in this project. This might have been a reason why the project never took off (4 stars, 2 forks and 1 open issue over the entire lifetime). Given my experience in the past 5 years after I’ve created this project, here I describe 2 problems that I used to create which I’m now actively trying to avoid.
Too Many Files!
First, to really deserve the name simple, this should have been a single file
application that you can upload to wherever you want and start using it.
Instead, in this project, I’ve counted a total of 24 PHP files. These files
range from endpoint files like index.php
, files that you can require from
your own PHP script external/include.php
, configuration file, HTML template
files, all the way to stupid files like interfaces/database.php
. These all
need to be uploaded to the web server, and if I’m a user I’d be quite confused
as to what all these files do. This project does provide an admin interface to
manage the polls you create, but this certainly still looks overwhelming and
it’s definitely not impossible to inculde that feature in a single file
application.
This highlights one of the changes in my coding styles over the past few years.
Previously, I might have been too obsessed on splitting files and keeping
files small. Over the years I’ve learned that there’s actually no reason I need
to split code into multiple files, especially in PHP. The only times I can
think of a reason to split files, is from the cognitive convenience standpoint,
where you know what the code in this file does, and if you have multiple entry
point files you might want to put common code into a separate file just so you
can include it from different files. As a simple example, you might have a file
called database.php
which handles all database specific operations, that
makes sense. Another case is when you have index.php
and install.php
that
both requires function abc
, then it makes sense to put function abc
in
common.php
, then both index.php
and install.php
can include that. The
over-obsession shows in my Simple Poll Engine, when there are separate
controllers.php
, domain.php
and usecases.php
files. These all could have
gone into one single common.php
file.
Stupid Abstractions
Now this leads to another question, why do controllers.php
, domain.php
and
usecases.php
exist in the first place? This brings up another change in my
coding style. Back then, I was quite obsessed with the Clean
Architecture
being advocated by Robert C. Martin. The idea that you have a layered
architecture code where each layers are cleanly separated and are
interchange-able between layers was kind of the craze back then. My mindset
back then, was that code is very difficult to write and difficult to change,
thus once code was written you want to avoid changing it as much as possible.
This means that I was looking for a method of coding which allows me to first
write a bunch of very very basic code, which will not change overtime, and then
slowly build things up on top of that. The problem that this induced was that
most of the time, I didn’t know very clearly what I was trying to write. And I
was so obsessed at trying to avoid changing code that was written when I had no
idea about how that code should be written! In hindsight this should have been
so obviously ridiculous, but to this day I still work with developers that have
the same mindset as the young me, and they think it’s righteous that they
should write code this way.
Since this is a mindset that most developers still maintain which I feel is one
that I’ve thrown away to grow up as a better developer, I’ll spend a little
more text to try to explain this. Looking back at the project that I wrote as
an example, PollRepository
interface.
An interface, in case you don’t write in PHP, is sort of an abstract class that
defines what methods a class that implements this interface should have. The
goal of this one PollRepository
is to define a list of methods that should
allow me to retrieve polls from whatever datastore there is. This project only
supports one datastore, which is MySQL. This already gives one huge red flag,
why do you need an interface when there is only one such class that implements
it??? If you attempt to add another datastore that is significantly different
from MySQL, for example a NoSQL database like mongodb, some operations that are
cheap in MySQL and can be performed in one-go might be very expensive in
mongodb!
As a totally arbitrary and uninformed example, assume that GetAnswersById
is
a very cheap operation in MySQL, since it’s cheap it’s called several times for
every page load. Now I move on to implement mongodb support and
GetAnswersById
turns out to be a very expensive operation that requires
several scans in mongodb, all of a sudden the performance of the application
sucks. It turns out, I should change the structure on the code one level higher
to not call GetAnswersById
so often and instead use some other operations
that are cheaper in mongodb and will produce the same result. Now how will I go
to fix this issue? The code on the higher level will now have to be aware
whether we’re using MySQL or mongodb and then choose which operations to
perform depending on that, which defeats the entire purpose of this
PollRepository
trying to abstract away the datastore that is being used.
Now this is just one example where abstractions break down completely. In this case, it’s not only broken but it also actively stops you from making changes to lead to better code. I certainly don’t think that abstractions are evil and that we should be running away from abstractions completely. If you look at the above example, the abstraction was broken because it was constructed from just one concrete usecase. If you start off without abstraction, implemented two or three more usecases, then try to build an abstraction, that abstraction that you built is certainly going to be more useful than the one I’ve managed to build here. Note that in this case it can still fail if the usecases were one-sided (i.e. only relational database usecases and now all of a sudden you need NoSQL). So my coding style has now morphed into one where I delay building abstractions as much as possible. No abstractions until I feel confident that I can now create an abstraction that will help me. This situation where I start feeling confident enough to build an abstraction is somewhat arbitrary and relies more on my experience than anything, but I certainly do hope to be able to concretely lay down some requirements that I look for in the future.
Going Forward
5 years is a long time for someone to grow up. I’m fortunate that I can look at my code 5 years later and point out problems in it. Signifying that I have actually made progress in my personal development over the years and have not stopped learning. I certainly do hope that whatever code that I’m writing today, eventhough given my current knowledge I think it’s the best that I’ve ever written, I will be able to look back 5 years later and point out more problems.