Elegant from the Start
These are the first few things you need to know about style in your programming journey.
Preamble: Why does style matter?
Style means readability. People spend ten times as much time reading code as writing code. People will need to read what you write, probably many times. Have mercy on them.
Okay, how do I write readable code?
Glad you asked. These are the most important style practices for you as a beginning programmer.
Naming Things
Programmers have lots of bad jokes. Here is my favorite variation on one of them:
There are only two hard problems in software engineering: cache invalidation, naming things, and off-by-one errors.
Fortunately, naming things is a non-technical problem; it is something you can start getting good at now. Any time we have to name a thing, be it a variable, a function, a file, or a project, we should try to think of a very good name for it. Here are some guidelines on what constitutes a very good name.
Give Descriptive Names
For variables, give names that describe what the variable is—what it contains. It should be a noun, since a variable is an object, a thing which is acted on.
For functions, give names that describe what the function does. It should be a verb, since a function acts.
The naming of files will depend a lot on the programming language and paradigm, but again, aim to be descriptive.
Variable names like x
, thing
, a1
, and num
are not descriptive, they tell me nothing about what the variable contains. Variable names like numUsersWithEmail
, patients
, and recordById
are all descriptive.
Bad example, in pseudojavascript:
function makem(c) {
a = getFlour()
b = getButter()
e = null
cc = findChickenCoop()
if (cc.hasEggs()) {
e = cc.getEggs()
}
d = null
if (c) {
d = getChocolate()
}
mix = mixIngredients(a, b, d, e)
res = bake(mix, temp=375)
return res
}
Good example:
function makeCookies(isChocolate) {
flour = getFlour()
butter = getButter()
eggs = null
eggSource = findChickenCoop()
if (eggSource.hasEggs()) {
eggs = eggSource.getEggs()
}
chocolate = null
if (isChocolate) {
chocolate = getChocolate()
}
batter = mixIngredients(flour, eggs, butter, chocolate)
cookies = bake(batter, temp=375)
return cookies
}
Give Concise Names
A name like numUsersWithoutEmail
is a fantastic name. A name like numberOfUsersWhoSeemToBeWithoutEmailAddresses
is a bad name.
A function named removeStudentsFromDbIfNoNameAndNotAdmin
is actually totally fine, if that's exactly what it does, and that's the shortest way to describe that behavior unambiguously.
Use Abbreviation Sparingly
Don't just abbreviate things willy-nilly. Use only abbreviations that should be obvious to someone skimming your code while drunk.
Some universally acceptable abbreviations include num
for number, pt
for point (or patient in the medical field), loc
for location, lat
and lon
for latitude and longitude, addr
for address, fcn
for function, res
for result, obj
for object, str
for string, avg
for average, db
for database, and tbl
for table.
Some unacceptable abbreviations include u
or usr
for user, psn
for person, pn
for phone number, dshbd
for dashboard, cnty
for county, hmsdwch
for ham sandwich, and covfefe
for coffee.
A bad version of our cookie code from before:
function mkCks(isChoc) {
flr = getFlour()
btr = getButter()
eg = null
eggSrc = findChickenCoop()
if (eggSrc.hasEggs()) {
eg = eggSrc.getEggs()
}
choc = null
if (isChoc) {
choc = getChocolate()
}
batr = mixIngredients(flr, eg, btr, choc)
return bake(batr, temp=375)
}
Another bad example:
InclCensTblPrgnc = renderDT({
cmth = unique(prgncsDB()["loc"]["month"])
cmth = cmth[order(cmth["loc"], decreasing=TRUE)]
return cmth
})
Good example:
PregnanciesIncludingCensusTbl = renderDT({
uniquePts = unique(getPregnanciesDb()["loc"]["month")])
res = uniquePts[order(uniquePts["loc"], decreasing=TRUE)]
return res
})
Really, don't be afraid of concise-but-long names. Our editors do autocomplete. Naming things like nStsClg
does not make you a cool hacker. Naming things like numStudentsInCollege
does.
Commenting
Here's an excellent and brief article on the art of commenting.
Once you are an expert programmer, you will be writing code that reads like
communityDataRaw = importCommunityData()
communityDataClean = parseNumbers(cleanNames(communityDataRaw))
indicators = extractIndicators(communityDataClean)
writeIndicatorsCsv(indicators)
Learning how to write code that is structured this well takes a lot of practice. Until you're there, comment vigorously.
Too many comments is better than too few, especially when you're just starting out.
Unless it is blindingly obvious what a function does and it will only ever be used once, every function deserves a comment. It should at least describe what arguments the function takes, and what the function returns.
function makeCookies(isChocolate) {
/* Makes some fresh-baked cookies.
* Args:
* isChocolate (boolean): whether they should be chocolate cookies
* Returns: cookies, fresh out of the oven
*/
// Gather some ingredients
flour = getFlour()
butter = getButter()
eggs = null
eggSource = findChickenCoop()
if (eggSource.hasEggs()) {
eggs = eggSource.getEggs()
}
chocolate = null
if (isChocolate) {
chocolate = getChocolate()
}
// Prepare cookies from ingredients
batter = mixIngredients(flour, eggs, butter, chocolate)
cookies = bake(batter, temp=375)
}
Code Structure
Functions are great, make lots of them.
A beautiful function is less than five lines long (not including comments and blank lines). An okay function is less than twenty lines long. A function longer than twenty lines is ugly.
We can make a function shorter by refactoring it. You identify a piece of it that seems like you could describe it concisely, and make it its own function.
So we might turn the above example into
function makeCookies(isChocolate) {
/* Makes some fresh-baked cookies.
* Args:
* isChocolate (boolean): whether they should be chocolate cookies
* Returns: cookies, fresh out of the oven
*/
ingredients = getIngredients(isChocolate)
batter = mixIngredients(ingredients)
cookies = bake(batter, temp=375)
}
function getIngredients(needChocolate) {
/* Fetches the ingredients needed for cookies
* Args:
* needChocolate: whether chocolate should be fetched
* Returns: an object with all our ingredients
* Its keys are `flour`, `butter`, `eggs`, and maybe `chocolate`.
*/
ingredients = {}
ingredients.flour = getFlour()
ingredients.butter = getButter()
ingredients.eggs = getEggs()
if (isChocolate) {
ingredients.chocolate = getChocolate()
}
return ingredients
}
function getEggsIfAvailable() {
/* Fetches eggs from the chicken coop if there are any
* Returns: eggs or null
*/
eggSource = findChickenCoop()
if (eggSource.hasEggs()) {
return eggSource.getEggs()
} else {
return null
}
}
Beautiful.
Formatting
Spacing matters. Put blank lines between your functions and other high-level organizational elements. Indentation should reflect levels of containment in parentheses and brackets—indentation is very important. Put spaces around your assignment operators. And so on.
For specific guidance, read the Google Style Guide for the language you're working in (search for, e.g., "google style guide python").
If you can find an auto-styler for your language, use it. For Python use Black. For web languages use Prettier. For Java use Maven Formatter. For R use Styler. Make it run automatically before you commit or when you save a file (but respect the styling of the repository you're in! Don't just auto-format other people's work).
Finally
Go write some code. Participate in code reviews. Be at peace with the fact that you're going to get a lot of constructive feedback, and not much of it will be sugar-coated. Note what's easy to read and what is hard to understand. At your leisure, read these, they are very good and very short.