(Note: this is an expanded version of the explanation, with pretty pictures)
Well, programming as I know it is an extremely high-level abstraction. But it’s probably best to go right down to the lowest levels to explain.
Steve Jobs famously explained the supposed “magic” of computers by saying all a computer can do is perform very simple number manipulations extremely (mind blowingly) fast. Whilst accurate, this isn’t the real magic — the real magic is that the computer never “knows” that the numbers it’s manipulating mean anything. At the very lowest level, the circuits don’t even understand the electron levels they’re manipulating are numbers.
At the lowest level, a computer can be thought of as a very long list of addresses, each of which can store a number. The lowest level programming language, assembler, gives you a way of instructing the computer to do these very simple manipulations in barely human-readable code:
movlw 15 ; Move 15 into the working register
addlw 5,w ; Add 5 to the value in the working register
movwf 1 ; Move the value in the working register to address 1
; Address 1 contains the value 20
The problem is, at this level, doing even simple things like dividing a number by another is a difficult task. The next level of languages effectively provides a whole load of shortcuts to doing things like that, so we can write code like this:
integer a = 10;
integer b = 5;
print a / b; // Prints 2
This level also introduces some extra abstractions, so instead of dealing with numbers all the time, we can deal with more human things, like letters, and lists of values. Whilst at the very lowest levels you have to use numbers to represent letters, at this level of abstraction you can write in letters, and manipulate 'strings' (lists of letters):
string greeting = "Hello";
string name = "Gerald"
string thingToSay = greeting + " " + name + "!";
The next significant level of abstraction is the ability to create new abstractions. Whilst before we only had basic things like numbers, characters and lists (of those things), at this level we can create new things to deal with, and give them properties and behaviours, like this:
class Human:
has name = 'Magenta Mark'
has legs:
right,
left
can walk ( move legs.right forward, move legs.left forward )
At this point, you no longer have to worry about how particular sets of numbers map represent letters, or behaviours, or things. You can deal with things which are more real, and represent objects in the domain you’re working in — for example, if I’m making a blog I might define abstractions for "blog posts", "users", "posting", "editing" and "deleting". I’m no longer bothering with how any of those things are stored as numbers, I’m only worried about how they interact.
And that’s how code works.