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.

Four Clean Code Tips

Clean Code: A Few Key Rules to Follow

Clean, High Quality Code