Coding 102: Writing code other people can read
We’re all coders now
In the 1980s, personal computers became common in the workplace. With the release of the Apple II in 1977 and the IBM PC four years later, a tool once reserved for scientists and the military proliferated in accounting offices, universities, and hospitals. You no longer had to be an engineer to use a computer.
Something similar has happened with programming over the past decade. In everyday offices, classrooms, and laboratories, code is now being written and maintained by people who don’t think of themselves as programmers.
The good news is that it’s easier than ever to write code that works. There’s no shortage of beginner-friendly programming tutorials, and high-level languages today are more readable than ever. So, if someone at work hands you a Python script to run over some data, between Stack Overflow and Codecademy you can probably figure out how to get it to work.
The bad news is that after you write the code you just learned to write, someone is going to have to read it (that someone might be you in six months). To a beginner, getting a working solution is the finish line. To a developer, it’s only the starting line. And since more of us are writing and collaborating on code than ever before, it’s becoming crucial to write better code.
There’s a lot to learn about basic programming beyond “making it work.” For professional developers, writing maintainable code is fundamental to the job. But these practices aren’t usually discussed in “Coding 101”. Beginner tutorials usually focus on syntax.
So we’ll assume you already know how to pull together a working solution — it’s time to level up to Coding 102: writing code with and for other humans.
Prerequisites
We’ll keep our examples short and simple and write them in pseudocode (which should seem familiar if you’ve written in a popular language). This post is aimed at people who’ve spent time writing code, but don’t write it professionally full-time. It helps if you are familiar with variables, functions, and if/else statements.
Why writing better code matters
There’s an adage in programming that maintaining code is 75% of its total cost. Once you write the code, other people (including future you) will invest two to three times as much time, on average, in reading, updating, and fixing it. This doesn’t mean your code was badly written or buggy! Requirements evolve, other parts of the code change, and sometimes… well, yeah, sometimes there are bugs.
At one of my first (non-programming) jobs, I wrote a script that switched between browser tabs with user accounts and pasted in discount codes. At the time, this task was done by hand for hundreds of users each month.
I hacked the script together in a few hours, and, to my manager’s delight, it pasted hundreds of codes in a few minutes. But the following week, the script went haywire and applied the code to 300 wrong accounts. It took me two hours to code the script, but eight hours to undo the damage it caused and fix my convoluted code. That’s the maintenance cost.
Good design “reduces the cost of future changes.” Let’s say my company changed the layout of the discount code page. How long would it take someone to update my script to work with the new layout? If another developer offered to help, how long would it take them to get up to speed on my work? That depends on how well the code was designed.
In order to make code easy to maintain, update, and fix, it should be modular, easy to follow, and easy to reason about. Writing “clean” code (as it’s often called) will help you think through the problem. If other people you work with can embrace these practices, you’ll spend less time struggling and more time collaborating productively.
Coding 102
Now that we understand why maintainable, clean code matters, let’s talk about how to write it.
1. Write small functions
Aim to write small functions that do one thing. Let’s say you’re writing a function that takes a list of widget prices and returns some text with the average of those prices. For example, you give it [13, 28, 17, 143, 184, 72.3] and it should return “The average price for 6 widgets is $72.22.”
A first attempt at this code might be a function called get_price_average(data)
that:
- Takes some data
- Gets the length of the number list
- Gets the sum of the prices
- Divides the sum by the number of widgets to get the average
- Rounds the average
- Assembles all the stats into a string of text and prints it
Depending on the language, some of these operations might be built in. You put these steps in one function, and it works. This is a fine, typical first version.
On the plus side, the code is cohesive: everything we’re doing related to the stats text is in one place. So if you need to change anything having to do with this text, you know where to look.
The problem is that if you do need to make a change (add a stat, clarify which numbers should be averaged, or change the decimal points on the average), you’ll need to read and understand the whole function. And adding the change will just balloon it further.
The solution is to break our oversized get_price_average()
, which does many things, into several small functions that each do one thing. A good guideline is:
> A function should be small, do just one thing, and ideally shouldn’t impact anything outside the function
Let’s try to see this by example. Here’s how we might break the function up.
get_widget_price_stats(data)
: we’re renaming our wrapper function,get_price_average()
. It now takes some data, extracts the relevant stat and returns texts with that stat. We’ll talk about names shortly!get_max(list_of_numbers)
: gets the largest number in a list. Depending on the language you use, this might be part of the standard library.get_min(list_of_numbers)
: gets the smallest number in a list. Depending on the language you use, this might be part of the standard library.get_sum(list_of_numbers)
: gets the sum in a list of numbers.get_average(list_of_numbers, decimal_points)
: takes a list of numbers and returns the average, rounded todecimal_points
.
Did you find some of those function descriptions obvious? For example, it might be pretty clear that get_max(list_of_numbers)
gets the largest number in a list. Good! That’s by design. When you write small functions that have clear single responsibilities, you can give them names that clearly convey what they do. Then, your future readers don’t have to read all the code in the function to understand what it does.
How does this design respond to changes? Let’s say instead of the average, you need to return the standard deviation. In the initial version, we’d need to find the section of the code that calculates the average and overwrite it with a standard deviation calculation.
In the small-function version, we could create a new function, get_standard_deviation(list)
, then go into get_widget_price_stats()
and replace the call to get_average()
to get_standard_deviation()
.
To recap, the functions we break out should be small, self-contained, and do one thing. This makes it much easier to read and change your code. This practice alone will improve your code significantly.
2. Pick names carefully
There’s a running joke that “there are two hard things in computer science: cache invalidation and naming things.” We’re going to leave cache invalidation for another post and focus on naming things.
A related idea is that “good code is self-documenting.” In essence, this means that good code conveys what it does. Names are a big part of this: a good name is precise, easy to understand, and gives the code context without having to read every line.
Here are some good naming practices:
- Describe intent: Try to describe the variable or function you’re naming as specifically as you can. For example, the function we looked at above was called
get_price_average
. However, since the string we’re returning is actually an average of widget prices, we could name the function more precisely:get_widget_price_average()
. Later, we wanted it to serve as a wrapper function that returned some stat, so we renamed it toget_widget_price_stats()
From this function, we extracted a generalaverage
function that takes a list of any numbers and returns the average.
Try to avoid general names likedate
,slice
,part
,numbers
. Instead, depending on context,date
might becomecreated_at
,slice
might becomeoutdated_prices
,part
might becomepart_to_be_fixed
, andnumbers
might becomecurrent_row
. - Be consistent: Whichever convention you decide to use, stick with it. If the codebase uses
get_
for functions that return values, each function should beget_[stat]
:get_average
,get_max
, etc. If you name a function that returns standard deviationstandard_deviation
, another dev will wonder how it’s different from all theget_[stat]
functions. - Give the right amount of information. A variable called
first_name
conveys a good amount of information. The alternative,name
is too vague (first? last? middle? full?) andn
orfn
is downright indecipherable. These are examples of under-explaining; it’s not clear what the variable does.
It’s also possible to over-explain:user_first_name
doesn’t offer any additional information, sinceuser
is fairly generic, and it’s not clear what the alternative is (does the app havebots
with first names?) Similarly,first_name_for_user_record
, orget_price_average_by_dividing_prices_by_quantity
offer redundant information.
Deciding on the right level of specificity is a skill you’ll build over time. It requires you step back and think deeply about what the variable is doing. This’ll make your code better in the long run and make you a better developer with it.
Speaking of how you shouldn’t call your variable fn
, here’s a list of naming practices to avoid:
- Don’t include type: Focus on the purpose of the variable, not its implementation. Common names that break this rule are
array
,number/num
,list
,word
,string
. Syntax is usually clear from the code, so you don’t need to addfunction
or the type to the variable name:get_average_function
orarray_of_prices
. As a general rule, your name shouldn’t include the type of the variable (this is called “Hungarian notation”). If you need to sayadmission_numbers
, keep looking for a better name (daily_admissions
.) - Don’t use abbreviations: Is
det
“detail”, “detrimental”, “deterministic”, “Detroit”, or something else? Our earlierfn
falls into this category. - Don’t use one-letter names: These are abbreviations on steroids—they’re completely incomprehensible! Instead, describe what the variable’s purpose is.
There are a few uncommon exceptions to this:- Loops, where it’s common to refer to the index as
i
. - Scientific and mathematical conventions, as long as you’re certain they’ll be clear to other developers.
- Domain-specific conventions (for example, in JavaScript, webpage events are often referred to as
e
instead ofevent
).
- Loops, where it’s common to refer to the index as
These are exceptions—99% of your variables should have descriptive, multi-letter names!
3. Avoid reassigning variables
The following problematic function takes some input
, transforms it with seasonal_multiplier
, adds a mystical 14, and returns result
.
function get_result(list, seasonal_multiplier) {
int result = list;
result = list.sum / list.length;
result = result * seasonal_multiplier;
result = result + 14;
return result;
}
Putting aside the opaque variable names, result
represents four different things during the course of this function. It starts out as the input, then becomes input per participant, then input per participant adjusted for the seasonal multiplier, and is then finally returned with a magical 14 tacked on. It’s doing too much work!
Most languages allow you to assign a new value to a new variable. In practice, this is usually not a great idea. A variable should represent one thing, and change its identity during its lifespan. When you look at a variable name, you should have a good sense for what its purpose is.
There are two ways to clear up this code:
1. Be clear about what the changes are. A better way is to be explicit about what each transformation is, declaring a new variable at each step. This might seem like over-explaining at first, but it makes the function much easier to comprehend.
function get_result(list, seasonal_multiplier) {
output_per_participant = list.sum / list.length;
seasonal_output_per_participant = output_per_participant * seasonal_multiplier;
adjusted_seasonal_output_per_participant = seasonal_output_per_participant + 14;
return adjusted_seasonal_output_per_participant;
}
As a final step, I’d probably rename the function get_adjusted_seasonal_output_per_participant()
.
2. Combine the math into one line. You can avoid this problem altogether by combining all the steps into one calculation. The reader then has to follow a lot of math at once, but at least the intent of the variable is clear, and one variable has one meaning throughout the function
function list, seasonal_multiplier(input) {
adjusted_seasonal_output_per_participant = list.sum / list.length * seasonal_multiplier + 14;
return adjusted_seasonal_output_per_participant;
}
You might also wonder what that 14 represents. That’s what we call a magic number.
4. Avoid magic numbers
A number used without any context is called a “magic number,” and it makes the code harder to comprehend. Here’s a bit of incomprehensible code:
int yearly_total = monthly_average * 12 + 67.3;
monthly_average * 12
probably refers to the number of months in a year (we deduce from the better named yearly_total
). But we shouldn’t have to deduce. And what the heck is 67.3? It might have made perfect sense to the code’s author, but we’re left to scratch our heads.
A better approach is to assign numbers to well-named variables, then use those in calculations.
int total_at_beginning_of_year = 67.3;
int months_per_year = 12;
int yearly_total = monthly_average * months_per_year + total_at_beginning_of_year;
5. Use few parameters
Functions take inputs (referred to as “parameters” and “arguments”). In most languages, the order of the inputs matters. Let’s say you have a function that takes a fruit, a country, and a year, and tells you the average price for this fruit in that country and year.
function getAverageFruitPrice(fruit, country, year) {
// get and return the price
}
This seems good so far. To get a price, you can call get_average_fruit_price(“peach”, “Albania”, 2007)
. But what if we want to narrow the price further by adding an option to only count fruit sold industrially (for example, to juice companies)?
No problem! We can just add a new boolean parameter, function get_average_fruit_price(fruit, country, year, sold_to_industry)
. But remember, we’ll need to pass in the arguments in the right order, and that’s an easy thing to mess up. We’ll need to check the function definition every time we want to call it! If you mess up and write get_average_fruit_price(“peach”, 2007, “Albania”, false)
, year
is now “Albania”!
A good general rule is to keep your input count to two to three at the most. This way, developers building on top of your code don’t have to constantly check for the correct orders, and are less likely to make mistakes.
There are two ways to improve a function that takes too many inputs:
- Break it up into smaller functions. Too many parameters could be an indicator that your function is doing too much.
- Use an options object. If you can pass the parameters in an object (called a hash or a dictionary in some languages), you don’t have to worry about property order. This object is often called
options
and referred to an “options object.” The downside to this approach is that it’s less explicit about the expected parameters. Here’s how this looks in practice:
function get_average_fruit_price(options) {
// use options.fruit, options.country, options.year, options.sold_to_industry
}
6. Write good comments
There are a lot of great, thorough rules around code comments, but we’ll focus on the low-hanging fruit…
Use comments sparingly to explain code that’s not otherwise clear. If you can improve the code instead, do that! Here’s an example of a bad comment covering up for unclear code:
int f = 75; // f is the temperature in Fahrenheit
Let the code speak for itself by renaming f
to temp_in_farenheit
!
You won’t always have time to improve the hard-to-comprehend code you see. You might also write code you’re not thrilled with due to time constraints. Consider whether the code will need to be “touched” (read or modified) by someone else. Be honest — you don’t want to pass on code that’s hard to maintain!
If the code isn’t likely to be modified by anyone else, you can leave a comment explaining what a particularly challenging bit of code does. Ideally, your function and variable names should already do this. But when the functions contain logic that’s hard to comprehend and the function name doesn’t help, leave a comment!
7. Don’t repeat yourself
The DRY principle stands for “don’t repeat yourself.” If you find yourself using the same code in multiple places, see if you can pull it out into a variable or a function.
string company_description = “Fruit Trucking & Shipping International ships fruit on our modern fleet of trucks and ships, anywhere in the world!”
string company_slogan = “Fruit Trucking & Shipping International: where the best fruits get on the best ships”
string company_name = “Fruit Trucking & Shipping International”
string company_description = company_name + “ ships fruit on our modern fleet of trucks and ships, anywhere in the world!”
string company_slogan = company_name + “: where the best fruit gets on the best ships”
If the company ever changes its name (for example, if it decides to only ship apples…) we can now do this in one place. Conversely, if you don’t follow DRY, you’ll need to find all the places you have duplicated code and remember to make changes in each of them.
Here’s another example: let’s say all your input temperatures are in Farenheit, but you need your output values to be in Celsius. Everytime you work with a temperature, you’ll need to F - 32 * 5/9
. If you find out your organization now uses Kelvin , you’ll have to update this to F - 32 * 5/9 + 273.15
every place you reference a temperature.
A better solution is to pull this conversion into a function:
function fahrenheit_to_celsius(temp_in_farenheit) {
return temp_in_farenheit - 32 * 5/9;
}
Now to switch to Kelvin, you can update the calculation in one place. In this particular example, you could probably just create a new (small, single-purpose) function called fahrenheit_to_kelvin()
, and all the calls to fahrenheit_to_celsius()
to fahrenheit_to_kelvin()
. Now it’s very clear where you’re updating (you don’t have to look for mentions of “temperature” all throughout your code.)
It’s possible to overdo DRY. You don’t want to over-optimize too early, so a good alternative to DRY is WET, or “write everything twice”. If you need to write some code a third time, it’s probably a good idea to try and pull this out into its own function or variable. Over time, you’ll develop judgment for when to reuse the same piece of code over and over (like the example above) and when a few repeated lines are a part of separate processes.
Next steps
Reading about good coding practices is like reading about working out. You have to follow the practices to reap the benefits!
Just because you learned about all the muscles doesn’t mean you need to exercise them all right away. Start small: the next time you code, try to be precise with your names. Then, try to write smaller functions. Then, reference this article to see what else you might have missed! Think of it like starting your fitness regimen with a few exercises a week.
Remember, we’re writing code for our future colleagues, and, importantly, our future selves. We don’t need to be dogmatic about the rules; we just need to focus on being as clear as we can.
You’ll quickly find that writing clean code involves making tradeoffs. For example, if you’re under time pressure writing a one-off script to parse some data and are confident nobody will see the script again, you can invest less time into writing clear code. But over time, thinking about code maintainability and readability will empower you to solve problems more effectively and will set you apart from many beginner developers..
By implementing some basic software dev principles, you’re leveling up from someone hacking through code to someone maintaining and building software. And that’s something to be proud of!
Tags: career development, coding, comments, DRY
21 Comments
Good article.
Unfortunately, the people who need to read it, won’t.
3.2 and 5 are objectively horrid and contradictory to most of the text. Heres why:
3.2) Considering a math equation where one has an arbitrary definition and multiple variables, let’s say to calculate the room temperature based off a thermistor’s resistance based off a voltage divider and its datasheet parameters – which returns you the temperature in Kelvin needs to be converted to Celsius – you’ll make a gigantic attribution that is by all means *unreadable* and *untouchable*. You can much more clearly attribute values to different named variables, small functions that return to said variables, isolating and clarifying the magic properties, and then finally returning the temperature through visible steps that can be followed and with previous procedures reusable and testable. If something goes wrong, it’s much easier to debug what and where it went wrong, otherwise you’re simply looking at that one line, telling me you think less lines means cleaner code, which will be false (if you still believe this, hope you meet ternary operators within nested lambda functions in your lifetime).
5) I’ve seen number 5 in the wild and it’s always confused and frustrated staff that had the distaste of maintaining it. It contradicts the immediately former suggestion of making smaller functions and hides bad code under visual sleight of hand. Consider the fact one can document via Docstring/Qt/Javadoc/ESDoc comments and use an IDE to describe each parameter in a function and receive automated feedback of everything they need as one calls it. When you hide all of that under an object most IDEs won’t pick it up, you’ll then have to define this new object in *the higher scope* somewhere to know what “options” is and perform checks for it outside of the function call just to use the function which may check things again. Every time you cast this function you’ll need to either create another instance of this object within the higher scope or worse yet, modify an already existing instance of the object which further obfuscates what is actually going into the function to the reader. The better alternative is optional arguments which are available in most languages or indeed breaking it into smaller functions.
I’ll agree with the former, however optional arguments are another potential problem. If you have more than one or at most two optional args, you should probably just create an overload instead.
The options object is a reasonable and scalable option when there are too many settings to parameterize, and it would create too many overloads. Make it optional and set the defaults in class definition or constructor and be done with it.
5 is not objectively horrible. It just sucks when it’s not used well. Generally speaking, your API will be much more malleable and stand the test of time if you can essentially change function parameters without changing function *signatures*. It also becomes easier, in certain languages, to initialize default values for various options. But that doesn’t mean you use #5 for every function you write. It’s good in functions that are likely to change in the future, and in functions where the parameter count gets very high, especially if a lot of the parameters are optional. A lot of its appropriateness depends on which language you’re using, as well. In JS it’s pretty much the norm to pass various options as a single object with properties, for example, while in C++ it requires a little more caution as there’s a bit of overhead when you start defining structs all over the place.
3.2 is also not objectively horrible, it’s just not appropriate for the specific situation you described.
As with all programming, what you do depends on your situation and there are no hard and fast rules, so most things aren’t “objectively horrible”. Rather, they have a time and a place, and you always have to think about your specifics when making a decision.
I appreciate the remarks Jason but unfortunately I find your text too is self-contradictory.
What matters here is how difficult it is to understand the code based off a unfamiliarized subject observing it. It seems the observations from your points are for the one writing it, and here’s the situation: Code Authors who work in small or even solo teams for projects naturally see readability as they themselves understand the code without needing explanation; as they wrote it and thus become familiarized. This aggravates when the code is unstable and unplanned, growing sporadically as requirements pour in which points to more faults beyond what is presented in the article. The funny part is that authors can later become unfamiliarized again, so an effort in readability pays much more than an effort in making it easier to write.
What one can objectively measure, and what I argue, is how much effort is in the code itself, in aiding the unfamiliarized subject understanding the codebase. Reducing writer effort is not part of the discussion and will often contradict the topic because writing as little as possible for pay is a natural writer’s goal.
For 5) An “options” argument is, again, a trick to *pretend* your function is closer to the Uncle Bob’s “niladic” ideal; this is very visible in codebases maintained by people who were *already* familiarized with the code. One still needs to define the object, declare it, and check it in some way. Glancing over the function provides zero information, and often accompanies zero documenting (as a familiarized developer in their own codebase is unlikely to do anyway). It being “malleable”, able to change the routine by modifying how “options” is structured, sounds actually cadaverous; as an unfamiliarized reader may wonder: How many other functions depend on this options object? One for each function? Every other function? Is there an options type at all? A *constructor*? If yes and one modifies the options type declaration, where in the code will this change cause a break as the signatures didn’t change, despite being admittedly different now? I can keep going. A longer parameter list would answer this at *a glance*, the Uncle Bob meme won’t. If one has multiple function calls, they’re either calling the same instanced object and changing it, or declaring it altogether in the function call which defeats the purpose of making it “easy to call”. Longer parameters in code may be annoying to write once, but it will be read many many times and with proper, or even automatic, function comment generation it becomes bulletproof in expressing what it needs and what you one needs to do to modify it. Even in the event you find a good use for this practice, it has *every* potential to go wrong as per your commentary, so not using it at all is the de facto appropriate approach.
The 3.2) criticism follows the same conundrum. This is very visible in Scientific computing and Engineering. One would shove everything into one variable because they understand the entire equation and feel no need to put effort in making it readable for someone unfamiliar, or to even test it. It’s, again, pretty objective: It spares effort *exclusively* for the writer, not for the reader. And, as stated, the quality for the unfamiliarized reader is what is being discussed. If the code is only among the already familiarized and will be thrown away and set ablaze later, feel free to employ the practice.
I’d be careful with the function names. If you get too descriptive you’ll end up with functions hundreds of characters long in name only!
Welcome to Objective-C
I’m a little sceptical about the value of a string of functions whose names all start with “get_”: apart from feeling like a DRY issue, I tend to prefer functions (i,e, routines that return something) to be named for what they return, saving verb-object names for routines that make something happen (IO, for example, or transforming an argument) without producing a specific nameable result. But maybe that’s just me?
Need to complement this article with “how to design APIs that other people can use”, aka “how not to create constant frustration that ultimately demoralizes everybody else on your team”.
“Literate programming”, Donald Knuth, 1984. If only schools still taught the classics.
See also publications by Jon Louis Bentley, including the “Programming Pearls” column from CACM.
More than once, the programmer that had to refactor or upgrade code 10 years later was me. It’s important to learn how to ‘program’ properly, and the title they use of ‘coder’ refers to someone that makes something work, using whatever they can find. These days, I comment EVERYTHING no matter how trivial, so that if someone needs to work on it they can do so easily. It’s also necessary to note that PROGRAMMING is a group effort, even if you’re working alone for now. Later on you could be working in a group, on a project that requires others to comprehend what you did, and why you did it. The code is one thing, and the programming of that code requires the knowledge to perform that task.
Please put brackets around the computation of the Fahrenheit conversion: Fahrenheit – 32 * 5/9 is just plain wrong.
I also chuckled at this one! Other code review thoughts on the one-liner: It should be `temperature` instead of `temp`, and `fahrenheit` instead of `farenheit`…
On number 6, I put lots of comments in the code. The code may say what it does, but the comment says what I think it does, and why I did that.
Comments are especially vital in any “clever” code due to Scalzi’s Law.
As a maintenance programmer coming along 10 years later, the first thing I would do is erase all of the comments. There’s no guarantee that the comments have any relevance to the actual code, or than anyone over those 10 years has maintained the comments. They cannot be trusted, and 35 years of practical experience tells me that 99% of them are rubbish anyways.
The biggest impact of comments is that they take up screen space and disrupt the flow of the code so that it is harder to understand.
I have read millions of lines of other people’s crappy code (including my own from years before), and I can only think of one or two times when comments actually helped anything.
A nicely written piece.
As a former mainframe programmer from when dinosaurs still roamed the Earth, we were always encourage to write code that a junior programmer could read, understand, and modify.
As a result, I have always written rather easy to read code.
As a senior software engineer today, who retired from corporate development in 2014, I tend to follow the KISS principal (Keep it simple, stupid…) and so though I am quite fluent with C#, I still do most of my development with VB.NET. The syntax is just more readable than C# code with all its semi-colons and curly braces. In addition, the C# language has been significantly updated to make it more arcane than readable.
The result of this is that I still use the “old ways” of coding where I rarely use any arcane syntax in my code. Why bother? Whatever can be done with the newer syntax can be just as easily accomplished with the older, original syntax.
Your first suggestion is oversimplified resulting in code that is unreadable. When Dijkstra railed against the GOTO, he was not complaining about the language construct; he was complaining about the need for the reader to stop, mark the place where he was, and goto the location referenced by the GOTO. This break in reading is exactly what too many subprograms does. The reader is made to navigate to different locations. Thus negatively impacting readability.
Great article, sorry about that you missed the pun in referrring to the two hard things in programming. Maybe look it up and add the full text?
With an non-typed language sonetimes hinting types in the naming convention if arguments might make errors stand out in the clear. That, of course might be a problem the compiler detects.
Nice approach, frienly and clear use of language.
Thank you!
It is great article! A readable code is a asset while unreadable code is mystery!
“there are two hard things in computer science: cache invalidation, naming things, and off-by-one errors.”
The first example seems unclear in its intent. When I read the description I thought about two operations: `get_average` and `print_average`. The first one takes the list of numbers and applies a basic mathematical formula to get a final number. The second function does the formatting, rounding, to get the output string. The “For example…” sentence suggests that this is all that we need.
But there is this mysterious “widget” that seems to inflate the operations that need to be perform on it to get the data. This seems to be a thing of web programming or mobile programming or something else, certainly not an entity native to pseudocode. I work in C++ and this term is foreign to me.
I would also like to argue, that splitting big code into separate functions is good, but splitting too much and overthinking about what might happen in the future is worse. I agree, that – for example – it is good to split a task between loading data, doing math and presenting the output. But anything more, it is best to leave alone until the need arises. And if it does arise – refactor then! After refactoring we could have for example a function `get_widget_price_stats(data, operation)` which you call with `get_widget_price_stats(data, average)` or `get_widget_price_stats(data, standard_deviation)`, depending on your needs. But this form of `get_widget_price_stats` should exist only if you actually have 2 or more different use cases.