What the heck is ‘good code’ anyway? Every developer in the world is (hopefully) asked to create clean, maintainable and reusable code. The problem with these requirements is that they are entirely subjective, and the level to which each applies varies between organisations.
While perfect code is unattainable, there are a number of neat tricks that help me work towards more object-orientated, decoupled and flexible codebases. It’s these sorts of codebases that I feel are good. At least, good enough.
However, there is always a difficulty in showing restraint when scoping out a new project. Knowing when to stop is arguably as equally as important as being able to keep going. Too much flexibility is without a doubt a bad, bad thing.
This post is more conversational than substance, but I will be updating and following it up with direct links to in-depth analysis on each of the techniques discussed.
Let's get going.
What makes code flexible?
I would argue that good code is often the most flexible code. You can extend, change or remove it all with a theoretically optimal time for implementation.
If you anticipate change, it can be useful to plan ahead slightly. Apply some common design patterns or create a few abstractions here and there.
On the other hand, if you think that change in a particular section of the codebase, or even the entire project, is likely to be thrown out or will never be touched again, then by all means put it out there like a code monkey, collect your pay-cheque and forget about it.
Flexible code is only useful if it’s going to be bent to your will in the future.
Advanced Techniques to Artfully Create Flexible Code
Dependency Injection
This is a technique to invert the dependency graph so that an object that depends on another object is given the dependency rather than creating it itself. This inverts the side responsible for creating the dependency.
The main benefit of this is that you can swap out one dependency implementation for another without having to change existing classes. This aids in testing (where you can swap in a mock that doesn’t go off to a 3rd party API or database) and also when you want to switch production to a new system. For example, if you had a MailProvider service class and you wanted to swap out MailChimp for SendGrid, you could do it without changing the class that uses MailProvider.
// Without Dependency Injection
class MailProvider {
public function __construct() {
$this->driver = new MailChimpDriver();
}
}
// With Dependency Injection
class MailProvider {
public function __construct(MailDriver $driver) {
$this->driver = $driver;
}
}
Anonymous Functions and Closures
Anonymous Functions === Closures
This is arguably my favourite method of decoupling code and is a key way to hack around inflexible code in other packages. It’s possibly more functional than object-orientated, but good code doesn’t always have to be object-orientated, right? By making and using functions as first-class citizens, you can define expected input and output and forget about the implementation details. You can also defer the generation of these closures to other classes as well as decorate them (wrap them up in closures that have the same interface). These also provide a simple way to implement lazy loading.
// An example for lazy-loading
public function __construct(\Closure $databaseConnector) {
$this->databaseResolver = $databaseConnector;
$this->database = null;
}
public function getDatabase() {
// Expensive operation to connect to database driver is now only done once only when and if required
if(!$this->database) {
$this->database = ($this->databaseResolver)();
}
return $this->database;
}
public function getAllRecords() {
return $this->getDatabase()->getAllRecords();
}
Take a look at my new article on Anonymous Functions to find out more
Sensible Interfaces
Interfaces are one of those things that mainstream developers seem to avoid. I naturally took a liking to interfaces as I like to find patterns between things in real life, and interfaces are a way to enforce that all things that match the supposed pattern really do what they say, and can be relied on to behave that way.
Interfaces should be as small as reasonably possible. I don’t subscribe to this with the resolve that some developers do, but I think that you should aim to keep interfaces as small as possible without losing intent on what a collection of items do. One tell tale sign your interface is too big is if you are having to fake-implement methods on some implementations just because they’re in the interface.
An example of an interface that’s too big:
class HeadlessChrome implements WebBrowser {
public function getScreenshot() {
throw new Exception("You can't use screenshots on a headless browser, dummy!");
}
}
In this case I’d probably extract a CanScreenshot or Screenshottable interface for the specific browsers that do implement this.
Configuration (with a sprinkle of dependency injection)
I often see code examples with an API client or a class that interacts with an external system in some way. Typically, in these examples, you see something like:
public function __construct() {
$this->apiKey = env("MYAPIKEY");
$this->apiSecret = config("MYAPISECRET");
$this->client = new ApiClient();
}
public function doAction() {
$this->client->doSomething([
'key' => $this->apiKey,
'secret' => $this->apiSecret
]);
}
This would be much better if you weren’t depending on framework functions (in this case Laravel) and also if you weren’t hardcoding where they come from. In this case you have no choice but to expose an environment variable named MYAPIKEY, and you have no choice but to expose MYAPISECRET as a config parameter in a file somewhere. You also have no choice but to use this class with the Laravel framework.
To add salt to the wound, you then build up an api client that can never be swapped out using these fixed variables.
I’d improve this in 2 ways:
- Inject the ApiClient
- Inject the configuration variables directly in to the ApiClient without anything knowing where they came from.
The improved version would look something like this:
// Build up the ApiClient from the outermost layer of your framework or application. Typically the 'wiring up' or 'service provider' part if using a dependency injection framework.
$client = new ApiClient($key, $secret);
// Original constructor improved
public function __construct(ApiClient $client) {
$this->client = $client;
}
public function doAction() {
$this->client->doSomething();
}
Reinventing the Wheel
Sometimes, just sometimes, reinventing the wheel is a good thing. Don’t shoot me, but it’s true.
Even with the advancements made in PHP, we can't get everything for free.
Due to the nature of the language and its namespacing system, we are unable to load more than one version of a class from the same namespace. This means that we must have an ecosystem that is compatible with all of your other packages, and you don’t have one hard-dependent on an old library, with another dependent on a newer version.
This would probably become a non-issue if developers took the time to really question whether they need to pull in a library at all. For example, the wonderful Laravel Support package (which includes the Collection class) is used because it makes interacting with array-like structures a breeze. Unfortunately, because so many other packages do the same and the framework is constantly updated, you end up with version conflicts. Not to mention you pull in thousands of lines of code to do something that probably amounts to this:
return array_map($array, function($item) {
return $item['id'];
});
Totally unnecessary.
Restraint
Possibly of the most importance out of all of the things you can do to write good code, is to exercise restraint.
This isn’t an excuse to write sloppy code that ‘just’ matches scope but does no more, ready for the colleague 6 months down the line to pick up and fix.
This is a point about knowing when to stop. Apply all of these techniques, apply all of your design patterns, but only if they make sense for your situation.
One criticism of this is that people say ‘But if it ‘depends’, then how do I know?’ - well, software development is an art in this way. You’ll never get it 100%, but you can try. Keep writing, keep throwing away code, and keep learning. You'll eventually start to get it right.
Try to consider these points when you’re deciding if you should implement the latest architecture craze or design patterns:
- Am I likely to have to change this part again in the future? If I am very likely, is the cost of implementing a design pattern now going to save me a headache later? Be honest with yourself - I’m looking at you, (Eloquent) Repository Pattern!
- Does the code look nicer? Subjective tastes go a long way in determining the cost of a design decision.
- Am I going to throw the code away? If so, forget about your scalable architecture. Really.
- Do I have bigger concerns? (for example, does my livelihood or dreams depend on me getting this out the door as quickly as possible?). Then forget it.
Closing Remarks
This article attempts to overview some of the things I consider when trying to attain ‘clean code’ status, as well as some of the techniques I use to get there. It’s not an exhaustive list, and believe me I’ve got a lot more to write in the future, but it’s a start.
I hope that you’ll follow along individual articles for each of the techniques as I write them.