Thinking Part 1: How to Think

Part I: How to Think

People often come to a computing problem with good knowledge of what they want to do, but with no knowledge of programming. Good examples of this are found in website development, online minigames or financial work.

The object of this document is not to teach you any particular language; there are many books and online tutorials for the 'standard' languages in common use (for example C#, PHP or Java) and other resources for the minority languages I may refer to to make particular points. You may want to read this in conjunction with such 'how to code' resources. This document aims to give you some tools of thought that you can use to:

This document is not necessarily the 'right' way to do things, and certainly patterns not mentioned in it are not the 'wrong' way. It is merely a collection of ideas which I find helpful in my work.

The Full Toolbox

An important part of being successful at solving problems is having the right tools available. A screwdriver is great for putting together a flat pack cupboard that comes with screws, but it doesn't work too well for putting together a garage. Like physical toolkits, your mind works better when you have many different approaches available to you, and when you know which problems should be approached in which way.

Different programming languages encourage (or in some cases even force) you to use certain approaches more than others. However, it's possible to use all of these approaches in almost any language, and it's often very useful to use one which isn't necessarily what the standard textbook would choose. You should consider how to approach a problem first, and then adapt the approach you choose to fit the language you are using, or if possible choose a language that works with the approach you have chosen.

Although I am placing this section right at the start in order to emphasise the importance of thinking correctly before you start coding, it may only be when you start solving problems – which inevitably means using real computer languages as well as thoughts – that you can really use the information here. If you don't understand it the first time you read through it, don't worry – you will probably refer back to it as you work through real problems.

The Procedural Approach

This is the simplest approach to understand, and where computers started. Procedural programming involves telling the computer to proceed through a series of steps: 'do this, do that, if something is true do something else'. Procedural code is the easiest to write, but it is one of the hardest to read if done badly. Be careful not to over-use the procedural approach – because it is very easy to adapt any problem to a series of steps (and to some extent you always have to to convert it into computer code), it is easy to slip into procedural style when you are not sure how to proceed.

The procedural approach is ideally suited for performing actions, for example when a user does something and you need to do something in return to respond to that. It is also very good for any situation where your program needs to affect the world outside, for example saving a file (write this, now write this, now write that). For symmetry it is also often used when reading information from outside, for which it is also a good option although not the only one.

In all mainstream languages, procedural style is the 'default option' and you won't need to modify procedural thought patterns at all in order to convert them to code.

The Functional Approach

Approaching a problem in a functional way is a very data-oriented approach. A function in computer circles means a piece of program that takes data, runs an algorithm on it and produces a result, without affecting anything. That is, the result of running a function depends only on the input you pass to it. This makes functional programming much easier to debug and test than other approaches, but restricts what it can be used for.

The functional approach is particularly suited to mathematical problems, where you are writing a computer equivalent of a mathematical function, as these are by definition dependent only on the parameters they take. For example, a statistics library would be written entirely functionally. Within many applications you find simple algorithms that work on data and that can be expressed as mathematical functions, and you should aim to write anything in that form in a functional way.

Functions are completely inappropriate for any tasks which require your program to modify anything, or read anything from the outside. For example: reading or writing files, any user interface material or any components that need to communicate with other parts of the application or with other applications.

While there are some languages that require you to write everything in a functional fashion, in most mainstream languages it is easy to write functional code. Simply make sure you do not use any data that is not passed in to the function, and you do not write to any data store outside the function (this means don't refer to any global variables and don't assign to properties of objects that are passed to you).

The State Approach

The polar opposite of the functional approach is to make a system where the state of the system does affect the result of what happens next. A simple example would be a computer drawing system driving a pen: the effect of asking the pen to move depends on whether the pen is up or down. The state approach makes the behaviour of the system depend on the value of one or more variables, known as state variables (or together 'the state'). A system which consists mostly of code using the state approach can be called a 'state machine'.

In a state machine, because everything depends on everything else, testing and fixing problems can be hard, even impossible, so you should take care not to use the state approach where it is not suitable. It is particularly suitable where a system can be in a small number of states where the results of performing some operations can be quite different in different states (like drawing or not drawing a line, or a connection to another application being available or not). Storing a user's preferences and modifying what you display as a result is also a good example of state variables.

State goes hand in hand with a procedural approach, and like that you should make sure you do not over-use it. Too much state information affecting too many operations means that your application will be very complex and difficult to write and maintain.

In most languages saving state is simple, requiring you to declare a variable and read from it or write to it when you need to. In object oriented languages, to store state information for the whole application you should make use of static fields, perhaps creating an entire static class called State which contains nothing but static fields (or properties) containing state information.

The Object Oriented Approach

Object orientation is something that modern languages strongly encourage you to use, and it's likely that the language you choose for your problem will have a lot of supporting material telling you how to use objects, so I won't write a lot about it here. It is basically the principle that categories of information have intrinsic actions that can be performed on it, and the exact result of that action depends on exactly what the information is.

Object orientation – OO as it has become known – is based on a few basic principles. The first is polymorphism: the result of asking an object to do something depends on what the object is (for example, asking a car to move causes its wheels to turn, asking a dog to move causes it to walk and asking a tree to move is an error). The second is inheritance: if an object doesn't know what to do when you ask it to do something, it will do whatever its 'parent' would do (for example, asking a Ferrari to move would cause the code for asking a car to move to be run). The third is encapsulation: you don't need to know how an object does something, just what it does.

These three principles allow you to create extremely flexible and modular applications.

The object approach is good for most things, which is why modern languages are almost without exception based around it. In fact it is useful in so many scenarios that I couldn't begin to talk about them here, and it's such a wide approach that I'll talk about how to apply it in some particular scenarios much later, when I'm talking about writing code instead of just thinking about it. OO is particularly indispensable when an application is handling data that is not of uniform type but where it all supports similar operations, and it helps a lot with the process of splitting a problem up into smaller parts as I will discuss later.

There are a few situations where the OO approach is a hindrance. In fact you've met them already: libraries of functions and storing state information. Objects also do not match up particularly well with files or other low level input/output (I/O) situations. However, you can always make use of static items to bring the non-OO approach into a predominantly OO environment.

Onward to Part II: Building an Application from Scratch