what do computer programmers actually do? » the big picture
Today, much of our lives is controlled by computers and software. We have self-driving cars and cleaning robots, software to help us make finance and healthcare decisions, and even apps to check the tone of our emails, before we hit the send button.
But have you ever wondered how we get computers to do stuff? I mean, what exactly is involved when someone "programs a computer"?
In the foreword to the MIT course turned textbook, Structure and Interpretation of Computer Programs, Alan J. Perlis noted that every computer program is a model, of a real or mental process, that is hatched in the human mind and is written for execution on a digital computer. This means that computer programmers essentially do three things:
- Think about how different processes work,
- Code as a means to express how computers can help us carry out those processes, and then
- Execute the written code on digital computers.
That’s it. Well, basically. But if we dive just a bit deeper, we’ll gain a broader perspective of what programmers actually do.
PHASE 1: THINK 🤔
Let’s assume that we’re writing a computer program. We first need to think about what we’re going to tell the computer to do.
To start, we’ll need an idea for a process that a computer can help us with. The process can range from one that is predominantly mental, such as deciding if an application is approved or not, to one that is largely physical, such as assembling products in a factory. The key thing is that the process must be computable, even if we don’t yet know exactly how to go about completing it. And by computable, we mean that the process consists of some finite number of steps that can be applied to some input, to produce the correct output.
Once we have a computable process in mind, we need to develop a deep enough understanding of the full process, in order to formulate a step-by-step model of how to complete it. This involves breaking the process down into manageable chunks, or parts, to help us figure out what specific aspects can even be completed by a computer and then how to go about tackling each part.
Then, based on our understanding, we need to create an algorithm, which is a detailed set of step-by-step instructions for completing the process. The algorithm is similar to a recipe, which transforms specific ingredients (the input) into a tasty meal (the output). The algorithm may be in plain English at this stage, but each step of the process must be broken down in such a way that a computer could effectively carry out the specified task from start to finish.
We need to bear in mind that a computer ONLY computes, and it cannot actually think for itself. The computer will do the exact computations that we specify, even if the instructions will not produce the intended outcome. For instance, let’s say the computer was helping us bake some cupcakes. If the recipe says we should use 3/4 cups of salt instead of 3/4 cups of sugar to make the cupcakes, then we’ll likely just end up with salty cupcakes. The computer doesn’t inherently know the difference between “salt” and “sugar”. These are just pieces of data. Unless we somehow programmed or trained the computer to distinguish between the effects of adding salt versus adding sugar, the computer will blindly follow our instructions.
We also need to think about other factors, such as:
- Which approach is the most efficient?
- Which approach is the most appropriate based on the circumstances?
- How can we ensure the output is correct?
- Is the proposed approach scalable?
- Are there any resource or hardware constraints that we need to consider?
We should, or at least try to, visualize the consequences of each major design decision, so that adjustments can be made to the plan, if needed, before we start implementing it in the Coding Phase.
PHASE 2: CODE 👩🏾💻
Now that we’ve figured out what needs to be done, it's time to tell the computer exactly how to do it.
Once we have our step-by-step model or algorithm, we need to translate that model into a computer program. In other words, we must put the rules or steps into a format that the computer can interpret or compile, in order to convert our instructions into bits, or ones and zeroes, which the computer can then actually read and execute.
For this, we'll need to choose an appropriate computer programming language. A programming language is the medium for expressing our ideas to the computer about how to complete each task. The language used may be Python, JavaScript, C++, or any coding language that is suitable for the specific task and the specific computing platform being used.
Our ideas must then be expressed accurately, in every detail, in order to get the desired output. To help with accuracy (and also speed of implementation), we often rely on established programming idioms, which are standard ways of coding or implementing certain instructions in a programming language. Idioms may exist for common tasks such as inserting an element into a list, generating a random number, or temporarily pausing execution of a program.
We can also improve accuracy and speed by using common data structures, such as lists, trees and graphs, to organize data. And we can use common algorithms, such as those created for sorting, searching and hashing, in order to solve those types of problems.
In many cases, the common solutions have been thoroughly tested and optimized, and so we are more likely to get to the correct output, much quicker, than if we needed to create, and then test and optimize, a brand new solution from scratch. However, we still need to understand what the code is doing and verify that the output is correct in each case.
As our programs get larger, and as we work on more complex problems, we need to develop, or become familiar with, standard principles for controlling the complexity that tends to creep into large systems. Some common techniques for managing complexity include:
- building abstractions, to hide details until we actually need them,
- establishing conventional interfaces, so that we can create standard reusable modules to mix and match in different programs, and also
- establishing new languages that make it easier to express our ideas about different processes.
We also need to develop and use style conventions that are consistent, meaningful and clear. This makes our code easier to read, which not only benefits us whenever we have to re-read our own code at a later date, but also eases the burden on any other programmer who needs to review or maintain the code base.
PHASE 3: EXECUTE 🖥️
Finally, we can have the computer interpret and run the program that we wrote in Phase 2.
But, we’re not quite done yet. Up until this point, we were more or less limited only by our imagination, but now we have to make sure that the code actually does what we want it to do, and we have to deal with any physical limits of the computer. We also have to face the reality of just how extremely finicky computers are, and how frustratingly literal they can be sometimes, when interpreting our instructions.
Testing to find mistakes or bugs in code, and then debugging the code, in order to fix those mistakes, are two processes that can take up a lot of time at this stage.
Sometimes the code may include syntax errors. These occur if we miss a semicolon, or forget to close a pair of parentheses, or maybe just spell a keyword or identifier incorrectly. These may seem like minor issues, but the computer is very strict when it comes to enforcing the grammar rules of a programming language. Syntax errors must be fixed before the code will even run. However, they are usually pretty easy to fix, since the computer tells us exactly where the errors are located.
Sometimes we may also encounter runtime errors. These are cases where the syntax is correct, but the computer is not able to successfully execute the entire program because some of the instructions are impossible to follow. Such errors include instructions that attempt to divide by zero or instructions that try to access files that don't exist. If these errors are not properly handled within the code, then the program may crash when the computer attempts to execute the instructions. As with syntax errors, we typically receive clues from the computer that help us find and fix runtime errors, but these only appear during the execution of the program.
Sometimes though, there are more subtle logic errors in the code. These don't necessarily prevent the program from running, but will prevent us from getting the correct output. Logic errors may include displaying the wrong message to a user, or using the wrong arithmetic operator in a piece of code. For example, the programmer may use the plus (+) operator instead of the minus (–) operator, resulting in two numbers being added instead of subtracted. These types of errors can be tricky and tedious to find, since we are the ones who need to hunt them down, often by reviewing each line of code.
And then, sometimes we also run into issues that are related to the physical constraints of the computer being used. Hardware constraints are typically given some level of consideration in the earlier phases, but the execution phase really tests how well we addressed the issues raised, and highlights any remaining issues that we still need to address. For instance:
- Is there sufficient memory or storage space available for the program to run successfully?
- Does the computer have enough processing power to handle the task?
- How long does the program take to execute? And, is the execution time acceptable?
We may need to go back and make changes to our program design or code, and possibly cycle through these three phases multiple times, until the program works as expected.
But even then, there may still be other times in the future when we’ll need to cycle through these phases again, each time we gain a better understanding of the process being modeled, or discover new issues that need to be resolved, or just think of ways that the program solution can be improved, whether by adding new features, making it run faster or getting it to use fewer resources.
TAKEAWAY
So, this is what computer programmers do in a nutshell: think about different processes that we encounter in life, and convert them into code instructions, so that computers can basically do stuff for us.
This helps us solve not only really complex problems, like those involved in building self-driving cars, or making software to handle critical finance and healthcare decisions, but also smaller and simpler problems, such as those related to automating repetitive and tedious tasks, whether at work or at home. And, don't forget the fun stuff, like creating digital art and games.
Computer programming can be an immensely powerful tool in the hands of anyone who learns to master it.
Sources:
A. J. Perlis, "Foreword," Structure and Interpretation of Computer Programs, 2nd ed., H. Abelson and G. J. Sussman with J. Sussman. Cambridge, Massachusetts: MIT Press, 1996, pp. xiii-xviii. Accessed: Aug. 21, 2022.
[Online]. Available: https://web.mit.edu/6.001/6.037/sicp.pdf
J. M. Wing, “Computational Thinking,” Communications of the ACM, vol. 49, no. 3, pp. 33–35, Mar. 2006. Accessed Aug. 21, 2022. doi:10.1145/1118178.1118215. [Online]. Available: https://www.cs.cmu.edu/~15110-s13/Wing06-ct.pdf
M. Egnor, “Neurosurgeon Outlines Why Machines Can’t Think.” mindmatters.ai. https://mindmatters.ai/2018/07/neurosurgeon-outlines-why-machines-cant-think/ (accessed Aug. 21, 2022).
"Programming-Idioms," programming-idioms.org. https://programming-idioms.org/about (accessed Aug. 21, 2022).
P. Sharma, “Computable and Non-Computable Problems in Toc.” geeksforgeeks.org. https://www.geeksforgeeks.org/computable-and-non-computable-problems-in-toc/ (accessed Aug. 21, 2022).
Q. Kong, T. Siauw, and A. Bayen, “Chapter 10. Errors, Good Programming Practices, and Debugging,” Python Programming and Numerical Methods: A Guide for Engineers and Scientists, 1st ed. Cambridge, Massachusetts: Academic Press, 2020. Accessed: Aug. 21, 2022. [Online]. Available: https://pythonnumericalmethods.berkeley.edu/notebooks/chapter10.01-Error-Types.html#