What actually is programming?
First off, let’s get one thing out of the way. Programming is not the same as coding. People often use the terms interchangeably, but they’re different. Coding is using computer languages to write a solution to a problem. Programming is about designing that solution in the first place.
Programming is looking at a problem, and designing an entire solution to it. To do that programmers use algorithms and abstractions.
Let’s dive a bit deeper into each of these terms.
Algorithms
An algorithm is simply a set of clear instructions that when followed will get something done.
For example, let’s write an algorithm for making instant coffee.
- Boil water
- Put coffee and sugar in a mug
- Add boiling water
- Add milk
Could you follow that? For a person, these should be clear instructions. But even people may sometimes get stuck at any of these lines. A person who is simply following these instructions blindly like a robot might do far worse. Let’s take line one as an example and imagine getting a robot to do this.
1. Boil water
How do we do that? Well, first we need a kettle. So let’s add that as a first step:
- Find a kettle
- Use the kettle to boil water
Use the kettle to boil water? How does a robot know how to do that? Let’s elaborate.
- Find a kettle
- Put water in the kettle
- Turn on the kettle
- Wait for the kettle to click
Sounds good, though it’s starting to get longer. Let’s test it by telling our robot to do this.
Whoops, the kitchen is flooded. Why?
Turns out the last person to use the kettle left it half full. When our robot reached stage 2, the kettle overflowed. Looks like we need to add another step
- Find a kettle
- Check the water level in the kettle
- If it’s too low for boiling, add more
- Otherwise, carry on to the next step.
Great. Let’s go again.
It’s been half an hour, and the robot has just been sitting there. What’s wrong?
Oh, the kettle isn’t plugged in. Our robot followed steps 1–3, and has been patiently following step 4 ‘Wait for the kettle to click’ for the last 28 minutes. Looks like we need some more clarity.
- Find a kettle
- Check if the kettle is plugged in
- If it is not, find an outlet and plug it in
- If it is, continue to the next step
- Check the water level in the kettle
- If it’s too low for boiling, add more
- Otherwise, carry on to the next step.
- Turn on the kettle
- Wait for the kettle to click
This is starting to get a bit longer.
Let’s test it again. It works! Amazing! Let’s go outside and show off what we’ve done.
Oh, now the robot can’t find the kettle…
You get the point. Computers are really stupid, like our robot. They will do exactly what you tell them — whatever you really meant. They will not be able to handle anything they aren’t told how to handle. Something as simple as ‘boil water’ can end up being a long set of instructions to handle anything from a missing kettle to the tap not working, to the power being out.
In programmers’ language, the case where nothing goes wrong is called the ‘Happy Path’. If you’re trying to make something really quick just to see if it works, that’s fine. But as soon as you want to make something professionally, you need to think of all the ways you can leave that happy path. A professional programmer will spend a lot of time on ‘edge cases’ that need to be handled. Any time you’ve seen an app or a program crash — that’s a programmer who missed one. The consequences of that can be anything from annoyed users to real damage. There’s a famous case of a bug in a robotic radiotherapy machine that killed people.
Apart from ‘natural’ problems that a program can encounter, there are three other sorts that it is worth mentioning.
Firstly, things break. Actually break. How do we handle dropping the mug? What if the water main bursts? What if a hurricane came and blew your kitchen away with the robot, can you still make coffee? Sometimes it’s OK to just give up. A real program should always have a way to tell that something truly catastrophic happened, and fail as cleanly as possible.
Secondly, users can do unexpected things. In our coffee example, if we’d developed the stage ‘Put coffee and sugar in a mug’ like we did ‘boil water’, we would realise that we need to ask the person we’re making coffee for how much coffee and sugar they like. What if your customer says ‘-1 sugars please’? If your robot is made a certain way, it may try to take sugar out of an empty mug. What if they say ‘1 million sugars!’? User input needs to be ‘validated’ to make sure it’s sensible. A real program can never trust its users to understand what they should do and to do it right.
The third category is security. There can be malicious people or programs that try and make your program do things in ways that harm its users. Imagine if someone spiked the coffee supply our robot is using. Our coffee making robot could end up poisoning people!
Any point where a program touches the world outside it is potentially a point where it can be attacked or sabotaged.
Abstractions
An abstraction is separating an idea from the physical thing it represents.
That sounds complicated. Let’s give an example.
In my opinion, the biggest jump in development of mathematics was the realisation by some early humans that the sentence ‘one cow and another cow makes two cows’ didn’t need the word ‘cow’ to be true. They separated the idea of ‘one’ from the physical thing of ‘one cow’. You can’t actually draw a ‘one’. It doesn’t exist in the real world, it is entirely an idea, an abstraction. Obviously the concepts of ‘one’ and ‘two’ have influence on the real world. But separating ‘one’ from ‘one cow’ means we can extend the concept of ‘one’ to ‘one sheep’, ‘one rock’ etc.
Programming uses abstractions in the same way — if you separate the right abstraction from the actual things, you can extend that same abstraction on lots of other cases.
Let’s look at one of the most fundamental abstractions in all of computers. One which everyone knows, but maybe haven’t thought about in this way before.
Everyone knows that ‘computers talk in ones and zeroes’. But what does this really mean?
Well, it means exactly that. When a computer stores the letter ‘a’, there’s no ‘a’ in its memory. Instead, there is ‘01100001’, which computers know translates to an ‘a’. A ‘b’ is ‘01100010’, a ‘c’ is ‘01100011’ and so on. When one computer sends an ‘a’ to another computer, it sends ‘01100001’ and the other computer understands that to mean ‘a’.
But again, what does that mean for real? We said the computer stores a 1 in its memory. We said it sends a 1, and that it can understand a 1. How?
The key here is that as we know, in reality there is no such thing as a physical 1. Anything that can be on one of two states can be ‘read’ as on or off, or 1 and 0.
- On a hard drive a 1 will mean a tiny area of magnetised metal
- On an SSD it will mean an electric charge trapped inside a metal-oxide-semiconductor
- On a DVD it will mean a tiny groove on the surface
- On an ethernet cable it will mean a pulse of electrons on a copper wire
- In a fibre optic cable it will mean a momentary pulse of light
- Over a WiFi or mobile data network, it will be a radio wave on a specific frequency
- In a CPU, it will mean a voltage above a certain level in a transistor
- In a very early form of computer memory, it could mean a sound wave flowing through a tube of liquid mercury
As long as the part of the computer actually managing the physical representation knows what condition represents a 1, every other part of the computer — and the programmer — can ignore the physical reality. All they need to know is the abstraction.
It doesn’t matter that the ‘a’ sent from one computer to another became a series of electric pulses, then radio waves, then flashes of light, then stored electric charge and finally raised voltages in a set of transistors. It was always just an ‘a’.
OK, but how can we use this ourselves?
Let’s go back to our example of the program that tells our robot how to make coffee. Remember how we started with ‘boil water’ and ended up with a long series of instructions about how to work a kettle? What happens when we move to a new kitchen with a gas stove and no electric kettle? We’ll need to make a whole new program! Then someone tells us that the new version of our robot has laser eyes and can heat water all by itself, do we need to rewrite everything to use this new feature?
What if we could ‘abstract out’ the idea of boiling water from the physical kettle, stove or laser eyes?
We can take that series of instructions for boiling water with a kettle, and label it ‘a way of boiling water’. We can make a similar list of instructions for the stove, and another for the laser eyes. Since all of these are just different ways for boiling water, they can all have the same label. The same abstraction.
Then we can go back to our original simple step before we started to complicate things— ‘boil water’! All we need to add is this:
- Find a way of boiling water and use it to boil water.
Now the same program can use any of the specific physical ways to make water for the coffee. We can even make new and exciting ‘ways of boiling water’ in future, and the same coffee program would work. Our program is suddenly versatile enough to use volcano boiled water with no changes! We have disconnected the abstract idea from the physical implementation.
Even better, we can use this same abstraction in another algorithm that makes tea!
Now that we have a clearer understanding of the terms algorithms and abstractions, let’s go back to our original question of what programming is.
My definition of programming would be:
Designing a complete solution to a problem, by abstracting out the ideas involved, and using them to write an algorithm that solves the problem.