Get Professional

Computational Thinking: How to think like a programmer and find great solutions to tough problems

3 Aug , 2016  

Let me ask you a question:

What does a programmer do all day?

Stare at a monitor and type? Attend meetings? (Ha!). Read Hacker News? (Double ha!)

Seriously, mostly what they do is think, which is why they’re considered knowledge workers.

Sounds cushy right?

Sitting around thinking all day. It’s sure better than cleaning fish or hauling ice (I recommend neither), but it’s no walk in the park.

Programmers are held accountable for the quality of their thinking and the decisions they make based upon it.

Poor thinking means poor code. Poor code means software bugs, projects running over time and over budget, and everyone working late to fix bad software.

You may be thinking at this point – well bigger brains mean better software, right? If there’s more IQ to pour into a problem we’re going to get a better solution.

While having a few brain cells definitely helps, the IQ advantage goes out the window when we’re stressed, sleep-deprived, angry, in love(!) or generally just being human.

What saves us from ourselves? Tools and techniques that allow us to reduce complex, overwhelming problems to small, achievable tasks.

These techniques comprise computational thinking.

Computational thinking doesn’t mean thinking like a computer – just the opposite! It’s where you use your ability to think like a human to solve problems with a computer.

In today’s article I’ll give you a framework for thinking about problems that you can use to develop great solutions.

Computational thinking - how to think like a programmer

What we’ll cover on computational thinking

Here’s what we’ll look at and practice today:

  • Decomposition – how to break complex problems down into simple ones.
  • Algorithms – how to make a recipe for solving a problem, every time.
  • Checking for blind-spots (examining assumptions)
  • And bundling everything up into abstractions

Sound weird? Don’t worry, it won’t by the end of the article.

What you’ll be able to do by the end of this article.

By the end of this article you’ll have the tools to:

  • Know how to understand a problem from the top to the bottom
  • Build solutions that work every time.
  • Make your solutions easy to understand.

Sound too good to be true? I didn’t say it was going to be easy!

But first, you’ll have to meet your new robot!

Get started with computational thinking – Train the UrkelBot!

Computational thinking with UrkelBot

Congratulations! You’re the proud new owner of a brand new UrkelBot5000. Just like you always wanted.

He’s a domestic robot, and besides bursting into your kitchen at odd times and making whiny noises, he’ll understand and DO anything you tell him – anything at all!

The only problem is, he can’t really think for himself.

So before you can enjoy your new domestic helper, you’ll have to train him.

Let’s start with a nice hot cup of tea.

How to make tea

This is actually a very controversial question – people from the UK, India and Japan will have very different ideas about it. Never mind the rest of us.

But we’ll keep things simple for UrkelBot.

We’re going to ask him to make tea by adding a tea bag to a cup of freshly boiled water (from an electric kettle).

So before we go any further, please write down the steps for making a cup of tea (make sure you don’t leave anything out!)

Breaking the tea problem down (Decomposition)

So, instead of me trying to read your mind and guess how you make tea, I’ll give you the list I made sitting in an early programming class:

Huzzah! A nice hot cup of tea.

And I can give this tea recipe to just about anyone, and they’ll hand me back a hot cup of tea. Excellent.

That works with humans because we have a similar frame of reference. We’re all more or less familiar with cups, tea and kettles.

UrkelBot however, is a machine, and doesn’t share that frame of reference. So we really need to spell things out for him.

This is where we start decomposing our problem – i.e. break it down into smaller problems.

As you can see, young Luke has already done this somewhat. Our ‘make tea’ problem has been turned into three problems. But is this enough? Let’s consider the first step:

“Boil water in kettle” is fine for you and me, but UrkelBot? He’s not too sure where this water is coming from, nor how to make it ‘boil’.

So maybe we can break this problem into:

So that’s a little better, but did UrkelBot just splash water over the top of a closed kettle? Did he produce any water at all?

So “Fill kettle from tap” needs to be spelled out too:

And UrkelBot might have a bit more luck filling the kettle with those instructions.

Too bad we’re only one third of the way to a cup of tea.

This isn’t just for UrkelBot, of course. We as programmers need to go through this process to solve complex problems. Decomposition is a tool for managing problems that exceed our brain’s capacity to understand all at once. It’s a vital part of being a programmer – much more than learning the ins and out of the latest Javascript framework.

If you went through this process already and produced a nice, take-nothing-for-granted list of instructions for making tea – then congratulations! You’re a natural at decomposition!

Otherwise, now might be a good time to re-examine your steps for making tea, and expand them to the point where UrkelBot can do no wrong.

Key Takeaways:

  • Decomposition is breaking large complex problems into small simple ones
  • It reduces the amount of complexity we need deal with at any one time
  • It’s a fundamental process for dealing with complexity

Hidden Assumptions

Computational Thinking - Make some tea!

A program is only as good as the assumptions it was built upon, and the same goes when we teach tea-making to UrkelBot.

The thing about this exercise is that we’re making tea. I’ve made an assumption (an explicit one) that you drink tea, and not coffee or some other hot beverage. This isn’t really a reasonable assumption (I prefer coffee, myself), but at least it’s been stated.

Hidden assumptions are trickier. These are things that are so obvious that we don’t even think about them.

I’ve been talking all this time about putting teabags into a cup, but do we actually have any tea in the house? Does the cup need washing? Is the kettle plugged in (and the electricity bill paid)? Was the kettle full already? And so on.

If you were actually standing in a kitchen these things might have jumped out to you as obvious.

But since we’re dealing with an imaginary kitchen they may be a little more difficult to anticipate.

That might not seem fair, but that’s the situation we’re often dealing with when we’re programming – imagining data, files, network connections and so on. Many things you’ll never see in the flesh.

So we need a tool to help us uncover these assumptions.

One thing I like to do is list all the states of the things I’m dealing with, for instance:

Kettle:

  • Lid closed? Yes/No
  • Plugged in? Yes/No
  • Full? Yes/No
  • Turned on? Yes/No

And so on. This helps make our imaginary kettle a little more concrete.

Examining each of its states in turn can highlight problems in our solutions.

Again, it’s a way of decreasing the amount of complexity our brain must deal with in a single moment. (You might see a pattern forming here).

So try it yourself. List the states of the Tea, and the Faucet, and then compare them to the steps you’ve developed so far.

Key Takeaways

  • Hidden assumptions get us because they’re so obvious we don’t think about them.
  • They can be a big source of problems in code.
  • Assumptions are ok if we state them
  • Listing properties and states of the things you’re dealing with can expose hidden assumptions

Turn it into a recipe (let’s make an algorithm)

You’ll hear a lot about algorithms in the programming world. College courses have multiple semesters devoted to them and there are hundreds of books on algorithms.

But we can define an algorithm simply as a series of steps that lead to a solution.

So we started working on an algorithm for making tea during our problem decomposition.

You’ll notice that our definition of algorithm doesn’t say ‘maybe leads to a solution’ or ‘sometimes leads to a solution.’ An algorithm is no good if it doesn’t get us to a solution.

Luckily we fleshed our first steps for UrkelBot’s tea-making out by examining assumptions. When we incorporate those into our tea recipe, it gets a little more robust.

But now it’s time to express the tea recipe as an algorithm a little more formally.

First, we need to know that an algorithm is made up of three types of things:

  • execution, or sequence (i.e. do something, and then do something else)
  • decision, or branching (“If the kettle isn’t full…”, “if there is tea…”)
  • repetition, or iteration (“Is the kettle boiled? No, wait. Is the kettle boiled? No, wait…”)

We’ll apply these to our ‘Fill the kettle’ algorithm, and incorporate extra steps to deal with some assumptions identified earlier.

Since we already have a series of steps to execute, we’ll move on to decision:

This is also called branching, because the code branches into different paths.

The If…Then construct is an extremely common way of implementing decisions in programming languages. If you’ve done even a little programming before you’re bound to have run into them.

You’ll notice that they allow us to skip steps that don’t need to be performed, and avoid making mistakes.

What’s more, this is the kind of instruction UrkelBot understands: If this, then do that… And our tea recipe is looking a little more like a program.

Because that’s what a program is – an algorithm encoded in a form a machine can understand.

Let’s move on to repetition, more formally known as iteration. Less formally known as looping.

Why looping? Because sometimes in an algorithm we need to loop back to an earlier part of the algorithm and repeat some steps.

Let’s add it to the kettle-filling algorithm:

Now we repeatedly wait until the kettle is full. Once again, repeating code like this is often referred to as ‘iteration’ or ‘looping’.

The appearance of ‘Go to’ may give some people the shivers here, because ‘Go to’ commands (called ‘goto’) were once abused terribly in early programming. Goto was synonymous with messy, undisciplined code.

But don’t worry! All programming languages (and programmers) use ‘go to’ style statements all the time, it’s just that they’re dressed up with a different name that forces us to use them well. These are ‘looping’ constructs, often called ‘for’ or ‘while’. We’ll look at using them in a later article.

Finally, we should ask ourselves “is this a good algorithm?” Is it going to give us a full kettle every time? I think if we state some outstanding assumptions (‘water is available’, ‘kettle lid works reliably’, ‘the faucet works’) then we’ve got a good algorithm.

Over to you – try applying these constructs (execution, decision and repetition) to the rest of your tea-making instructions, and see what you come up with!

Key Takeaways

  • An Algorithm is a series of steps that lead to a predictable outcome
  • Algorithms are comprised of sequences, decisions and repetition
  • A sequence is one or more steps executed one after the other
  • We make decisions using the If..Then construct
  • Looping is implemented with constructs like ‘For’ and ‘While’, but they behave similarly to the ‘go to’ example above

Abstractions

Let’s take another look at our kettle-filling algorithm:

It’s getting fairly involved now, and it’s only about one sixth of our overall tea-making instructions!

So to save ourselves the trouble of dealing with all this detail in the future, we can put a label on it, and know that the label represents a well thought-out algorithm. What should we call it then?

Fill kettle from tap.

What?! Back here again? This is our step from our first effort at decomposition!

Here’s the difference:

Before, ‘Fill kettle from tap’ was useless to UrkelBot. But now ‘Fill kettle from tap’ contains everything UrkelBot needs to know to fill the kettle.

But we could have just given UrkelBot the original list, right? Machines don’t care if instructions are wrapped up in nice labels.

As I said before, it makes life easier for us.

Having worked out all the detail, we can happily forget it and use our abstraction, confident that now, and in the future, we can tell UrkelBot to ‘Fill kettle from tap’, and he can go and look at our detailed instructions.

And here’s another benefit – we can generalise the abstraction, and the underlying algorithm to give us something like:

Fill from tap (thing)

So that we expand UrkelBot’s abilities to

  1. Fill from tap (kettle)
  2. Fill from tap (pot)
  3. Fill from tap (cup)

And so on. Our abstraction not only saves us from thinking work, it expands our (and UrkelBot’s) abilities, so that we multiply the value of the work we did developing the original algorithm.

Over to you – see what abstractions you can come up with from the algorithms you developed in the previous section. How could you generalise and expand them in the future?

Key Takeaways

  • Abstractions are a way of dealing with complexity.
  • They act as a label for complex details.
  • They save us from dealing with complex details over and over again.
  • We can generalise them to apply them to new situations and make them more useful.

Wrapping up Computational Thinking

So now we’ve have a good look at the fundamental practices for understanding problems and developing solutions:

  • Decomposing problems
  • Developing algorithms
  • Analysing assumptions
  • Using abstractions

While I can’t give you an actual UrkelBot (how I wish I could!), I’m sure you realise by now he’s standing in for your computer.

And of course these are the practices you’ll use to develop programs for your computer.

I look forward to seeing you again when we explore using these principles with an actual programming language!

By



Leave a Reply

Your email address will not be published. Required fields are marked *