The Way I Code (and have been coding for 30 years)

Back in 1993 I started my A’ Levels. For the non-UK folks, these are the 2-year qualifications you gain between the ages of 16 and 18 in what we call “college”. I took maths, physics and computing.

Computing was, in some ways, a very different topic back then. But it’s amazing what’s not changed. And the fundamentals of coding have not changed.

I’m many years later than I should be, really, but it’s time to write about these fundamentals.

I consider this to be:

  • invaluable advice/knowledge to beginner coders
  • a valuable recap/refresher for seasoned developers – it’s easy to forget, or to neglect the basics
  • and, if nothing else, an insight into how I think when I’m coding.

I’ll start by outlining three fundamental techniques that have stuck with me for so long – like a weird religious mantra that I repeat in my head several times a week.

Then, possibly in a second post, I’m going to work through an example. For the WordPress folks, this will be based on the annoying “Hello, Dolly” plugin, which I will make my own, and which, it turns out, really does have potential as learning material!

Who is this for?

A good question. This article is really for people who already know how to code. I will try to use simple language, but it assumes you understand some coding terminology, can read simple code examples, and understand the basics of object-oriented programming (basically, what goes inside a class, and what an object is).

It might be comprehensible to a complete beginner, but I don’t guarantee it.

I will use PHP in the examples, but they should be so simple that they would reflect a “generic programming language”. And the techniques I present will mostly be using object-oriented programming as the architecture/paradigm. (Gosh these are odd words, but the only ones I have).

Before we start

I want to say something really important before we start: I do not judge you by your code! In fact, I’d love to learn from you code, and your ideas, and your patterns and principles, as I hope you learn from mine.

A huge part of the wisdom of programming is understanding constraints making trade-offs.

Code is written by a human being (yes, even if an AI is helping you) battling constraints. It’s highly unlikely that you know the context of when another coder’s piece of code was written and the story behind how it got written.

But we’re all looking to write better code… I hope. I know that I am! To that end, I share these concepts that I learned long ago in case you’ve not come across them before, for you to think about and draw your own conclusions about.

Let’s get started!

Three fundamentals

The three fundamentals I want to look at are:

  • Abstraction
  • Encapsulation
  • Decomposition

Let’s get some definitions and more concrete patterns or techniques that you may recognise.

Abstraction

Abstraction is the process of hiding the details of how something works behind something that is simple and easy to understand.

Note: There is another definition/use of abstraction that involves making solutions more general – finding commonality in a set of problems to avoid having to solve the same problem multiple times. This is important too, but I’m just working with the detail-hiding part of abstraction here.

Our world is full of abstractions. When you turn a tap, clean (hopefully) water (hopefully) flows out. It seems simple. But behind that delivery of clean water are reservoirs, pipes, cleaning and purification systems, pressurisation systems, valves, washers, meters, billing systems, and so many other things I’ve not thought of or listed.

The tap is an “interface” to a highly complex water system. An abstraction of a system that you generally don’t need to know about – in fact, it would be overwhelming if you knew the details of every system you interacted with.

There’s a lesson here that we’ll come back to!

In the same way, in computing, an API (if you know what that is) is an abstraction. It gives you a list of things that you can ask or do, and delivers you results or changes without you needing to know about how those results or changes happened.

Abstractions happen at many levels in computing. Even a simple function like round that rounds a number is an abstraction. I don’t know how the rounding is happening. I just have a promise that I’ll get “the rounded value of num to specified precision“. That promise is the abstraction. “You give me this, and I’ll give you that back”.

For today, I’m going to focus on classes and functions as my abstractions.

I’ve previously written about trusting code. Good, and well-documented, abstractions are key to earning trust in code. And the combination of good abstractions and trust in a codebase make working with code much easier.

And, like with the tap example, if you trust the code, you don’t need to have the details of it filling up your brain’s working memory – you’ve got more important things to think about, like making that nice cup of coffee!

Encapsulation

Encapsulation is the idea of bundling or wrapping up (“encapsulating”) data together with the code that operates on that data.

In my software development world you will mostly see this in the form of object oriented programming. The “class” is a very obvious way of combining state/data and methods that operate on it.

Encapsulation works hand in hand with abstraction. An class is often used to bundle up some data and present you with a simple means of interacting with it.

A class for interacting with objects in a database is a great example. (I’d use the term ORM, but not everyone will know what that means.)

If you have a “wizards” table in your database, you can have a “Wizard” class. And you can create, fetch, and update wizards like so:

  • Wizard::create( ['name' => 'Gandalf'] )
  • $wizard = Wizard::where( 'name', 'Gandalf' )->get()
  • $wizard->save()

This Wizard could be stored in a database. But it could be stored anywhere: in memory, in a file on disk, on a remote server.

That’s the point of abstraction. We’re only thinking about storing a wizard. We don’t need to concern ourselves with how it’s being stored. Something else is taking care of that.

And encapsulation helps us by allowing the information about the Wizard, and the mechanisms for storing and retrieving that data to be kept together. It’s really handy as it means when you change the data, it’s easy to see where to change the logic around changing that data.

Decomposition

Decomposition is the process of breaking down a large problem or system into a series of smaller ones.

This takes place so much in the process of creating software, but in my coding process, the simplest form of decomposition is what I’ve come to call “programming by wishful thinking”. Imagine that a function or method or class that you need exists, and use it, on the basis that you will create that thing later on.

What does this look like?

Well, let’s say you’re writing some kind of simple mapper or proxy that takes input in one format and sends it off to some API in another format.

Our “wishful thinking” might just look like this:

$input = getInput();
$newData = transformData($input);
outputData($newData);

That’s it! What a simple, easy-to-understand program. We nailed it. Let’s take the rest of the day off!!

Well, maybe not so fast. But we did write a really simple program. It doesn’t do anything yet other than express our intent. But this exact code could actually end up being our main function.

We will probably want to evolve it to be a bit more specific. But we could also just implement these things as we’ve written them.

So often people start with the detail: “I’m getting input from a file, so I need to open a file.” and then they’re on a journey of writing the details of validating filenames and opening file handles and printing errors when things go wrong, and making sure data is the right shape when you read it.

You don’t need to do that yet! Right now, when you’re writing “getInput()” you’re probably thinking about what you’ll do once you have the input, not with you you’re getting the input.

You may spot that this is a form of abstraction. You would be completely correct. And yes, this technique can assume a level of encapsulation as well.

These things all work together to make your programs simpler and easier to understand.

Thinking up and down

The decomposition example just above here is an example of “high level” thinking. Concern with big ideas rather than small details. The “what do I want to do” part.

At the opposite end of the ways-of-thinking spectrum is “low level” thinking. Gathering and implementing the precise details of how you want to make something work. The “how do I want to do it part”.

From what I’ve said, you might think that I always start with some very simple, high-level thinking that’s very abstract. But that’s not true.

I think in both directions, depending on the problem, and usually end up meeting in the middle.

Just today I was working on a API interface that someone else depended on. I needed to send some data that they needed.

This task demanded that I went straight into the details of that data. What fields would be sent? What types would they be? Did they want a filtered version of the data or a firehose of everything with enough details that they could filter it themselves?

I was, for step 1, totally deep down in precise data definitions. Creating abstractions; something like an OutboundMessage class with properties and types all defined so that my stakeholder knows exactly what they are getting. Allowing them to build trust in my API.

Later on I needed to figure out where this data was coming from and how to transform it into the OutboundMessage. For this, I went back to high-level thinking. Something like:

$inputMessage = InputService::getInput();
$validatedInput = $inputMessage->validate();
$outputMessage = mapInputToOutput($validatedInput);
OutputService::sendOutput($outputMessage);

There’s my decomposition in action.

There’s some state, but no so much logic in this system yet – it’s just a flow of messages from input to output with some kind of validation and transformation.

Let’s say we wanted to add filtering of messages, perhaps we could do something like:

$inputMessage = InputService::getValidatedInput();
$validatedInput = $inputMessage->validate();
if ($inputMessage->shouldBeSkipped()) {
  return;
}
$outputMessage = mapInputToOutput($inputMessage);
OutputService::sendOutput($outputMessage);

Again, we can work on the details later, but we’ve encapsulated the filtering logic with the input message.

With some low level and high level thought done, we’ll need to do the detailed work of plumbing everything together. But we will have already done the work of:

  • understanding and abstracting some of the low-level data
  • decomposing the main problem into some smaller problems that will be easier to solve, especially with out abstractions
  • and we’ve benefitted from encapsulating some of our logic with our data.

Before the experienced devs start commenting: yes, this may not be the best way to organise this system, and we could bikeshed different architectures – probably mostly asking about what logic gets encapsulated with which state. But I present it as a simple example to demonstrate the point.

Hopefully you see how these three principles work together to make software easy to think about, easy to write, and easy to change.

An aside on comments…

…I promise it’s relevant.

Just to be clear: Docblocks are different and not what I’m talking about here. And when I first saw docblocks I hated them, but I have grown to love them. And they don’t count as comments for the purposes of this section.

I hear and see lots of discussion about using comments in code.

I am highly pragmatic about comments in code.

There are people who are against comments because “the code explains itself”. Well, that’s fine if the code does actually explain itself!!

Like, of course there’s no point in writing:

// Get the user
User::get($userId);

But, in my opinion, it makes perfect sense to write this comment (if you can’t modify the code):

// Check that the user ID is valid
// and exists in the database
if (empty($userId) || ! is_numeric($userId) || User::get($userId) === null) {
  return;
}

because that condition needs too much mental effort to understand.

On the flip side, there are people who want to put comments everywhere because the code doesn’t make sense. Which is also fine but… wouldn’t it be better to write code that makes sense??

So if you’re writing the comment on the condition above, why not just treat that as decomposition?

if (! userIsValidAndExists($userId)) {
  return;
}

My use of comments is approximately guided by the following flow chart:

  • Will the code make sense to someone that’s new to the project?
    • Yes: Hooray! Great job.
    • No: Can I change the code to make it make sense to such a person?
      • Yes: Hooray! Let’s do that!
      • No: OK, add a comment. That’s cool!

Summary

This got long, so I’m going to break and I’ll make a second part of this to work through an example another time.

There is always a lot of judgement and wisdom in programming. I don’t like hard and fast rules. Even my commenting flowchart probably has exceptions.

But as I said at the start, these principles have guided my coding practice for decades; I still find them useful. Even if they don’t fit every situation, knowing about them might be helpful for you.

Start with good decomposition. Then use encapsulation to create good abstractions – and you can do this top-down, bottom-up, or both.

That’s how I code. That’s how I was taught to code 30 years ago. And it’s still how I code today.

In the meantime, thanks for getting this far. I hope you learned, or were reminded of, something useful.