The Shoken of Python #1: Write Code You Want to Read


Code should be written once and read many times


Posted by Pyntheus Programmers on December 03, 2023

If you enjoy this article, support us!



TLDR: do the hard yards upfront to write code that your future self can pick up, easily understand, maintain and update with no memory of the problem space. A few tips to do this include: (1) use variable names that do what they say on the tin, (2) divide your code into small chunks that do one thing only, (3) document your code, (4) use comments and (5) prioritise readability over efficiency.

In the last article, I introduced the “Shoken of Python” (a term I coined which encompasses a set of beginner-friendly principles for writing well-designed and well-structured code. If you haven’t already, give it a read!). In this article, I’ll expand a little on the first principle outlined in the Shoken of Python – beauty is simplicity.

The below graph (crudely drawn by me – but nonetheless, it’s a graph, so it must be true, right?) outlines my view on the relationship between the proficiency of a programmer and the complexity of their code. I believe that there’s an inverse relationship here – I know, this sounds very counterintuitive, but stick with me!

 

A picture containing chart

Description automatically generated

Allow me to explain in a little more detail by way of an example. Three coders are going to the big city of Las Vegas to try their hand at some gambling, specifically at a game (which I've just made up) called “Dice Roulette”. The rules of the game are simple:

  • You have two die (to be clear, I’m saying you have the plural of dice, not that you’ve got to pass on to the afterlife) which you roll on each turn.
  • If the sum of the numbers you roll on any given turn is greater than 9, you win 3.75x your stake, woohoo!
  • If the sum of the numbers you roll on any given turn is less than or equal to 9, you lose your initial stake, boo!

Our three coders want to figure out if this game would be profitable to play in the long-term. They could, of course, do this with some high-school level statistics, but they’re coders, so instead, they're going to do what they do best - unnecessarily over-complicate the problem by building a Monte Carlo simulation and plot a histogram of their potential profits (sound familiar?).

Now, with the rules out of the way, let’s introduce our characters:

  • Beginner Bob – a recently hired intern that’s just starting to learn to code.
  • Intermediate Isabella – she’s been programming for a while now, and even knows a few neat tips and tricks.
  • Proficient Polly – she’s the senior developer on this team and has been coding for many years.

So, without further ado, let’s see how they each got on with solving this problem (sidenote: I’ve rarely written the word “ado” before, and it got me questioning where this came from. If you’re interested, it’s actually a contraction of “at do”, influenced by an old Norse practice of marking the infinitive by using prepositions. I thought I’d throw this fun fact in here just in case you learnt nothing else from this article …)

 

Beginner Bob

Beginner’s Bob solution is relatively simple – it doesn’t use any fancy functions, classes, third party libraries or neat tricks.  However, this does have some complexity to it – the structure of an if statement embedded within a for loop embedded within another for loop makes it easy to get lost in here. In addition, it's not immediately clear what the code is trying to achieve (without prior knowledge of the problem space), nor is it obvious what any of the variables mean.

 

Intermediate Isabella

Intermediate Isabella looks at Bob’s 21-line solution and says “ha! I can do that in less than half of the lines!” – and she sure doesn’t disappoint. She utilises a number of tricks – including collapsing a control flow and a for loop into a single list comprehension, utilising the walrus operator for assignments within another list comprehension and some fancy list indexing techniques. In addition, Isabella's variables are named far better, and her code is a little easier to follow than Bob's.

This code is fiercely lean and extremely pythonic – and likely considered the best of all three solution by most pythonistas. With that said, in my view, there a few issues with this approach:

  • The code does an awful lot of stuff in an awfully short number of characters. If I were to revisit this code snippet a year from now, it might take more than a little brainpower to figure out what the code is doing.
  • The code isn’t particularly flexible – if I wanted to change the rules of the dice game or how I win, I’d likely need to start all over again rather than simply changing a piece of the code (in some ways this isn’t terrible – the code is so short that it wouldn’t take long to bin it and start over).
  • The code isn’t particularly explicit about what it’s doing. Whilst a seasoned pythonista could likely understand this code just fine, it perhaps wouldn’t be easily understood by someone picking up the project that's from a less experienced background, or someone who’s programming knowledge is rooted in another programming language.

Let’s see if Proficient Polly can solve some of these issues in her solution!

 

Proficient Polly

Proficient Polly has been in this game for too long to encounter the pitfalls of Beginner Bob and Intermediate Isabella. She knows it’s best to put in the hard yards upfront to make her code simpler and more readable – sure it’ll take longer to write and likely consume more lines of code in total, but her future self will thank her when she has to revisit it!

 

Conclusion

Let’s take a look at why Proficient Polly’s code – which, at first sight, may appear more complex – is in fact far more readable:

  • The main part of the code (that defined below the line if __name__ == ‘__main__’) reads almost as if it’s pseudocode, with just a handful of variables and functions that are very intentionally named to reveal exactly what they do in plain English.
  • The main heavy-lifting of the code has been separated out into functions – with each function responsible for a single step in the process.
  • Each function has detailed “doc strings” (comments which describe precisely what the function does, any arguments the function takes and anything the function returns) which allow any coder, that could be entirely unfamiliar with the problem space, to easily understand how to use the code.
  • The code is extremely flexible! Want to change the rules of the game? No problem, just edit the “check_if_won” function. Want to change the amount won per roll? Fine – there’s a parameter for that in the “perform_simulation” function.

Don’t worry too much if Proficient Polly’s solution looks quite overwhelming at this stage – we’ll introduce and dissect many of the principles her solution embodies in future articles. Whilst it may not look the most readable at the moment – I hope that, with some practice, it’ll soon be your preferred solution too.

For now, let’s look at five simple takeaways of writing simple code:

  • Use explicit, intention-revealing variable names that “do what they say on the tin”
  • Split your code into small chunks (with each chunk performing one task, and one task only) and divide these up into functions
  • Provide detailed and informative documentation for each function
  • Provide informative comments where necessary to clarify or explain the intent of your code
  • Whilst python “tricks” have their place in code – always prioritise readability over efficiency

 

Phew! That was a long article, with some pretty involved concepts covered. For those of you who stuck with me until the end, good job! I want to end with this quote which, in my view, encapsulates a great ethos about writing readable code:

“Always code as if the person who ends up maintaining your code will be a violent psychopath who knows where you live.”

Until next time!


If you enjoy this article, support us!

310 views