Lesson 17 of 17 · Week 09 · Thu Nov 19 · Tue Nov 24

Writing Good Software

Questions

Objectives

Write code for people

Code is read many more times than it is written: by classmates reviewing it, by your instructor, and by you in three months. A computer runs messy code just as happily as clean code; people can't. This week is about habits that make your code easy to read, check and change.

Adapted from Software Carpentry's “Writing Good Software” episode (CC BY 4.0, The Carpentries). Text, examples and exercises have been rewritten for this course.

Structure a script

# clean-survey.R
# Reads the raw survey, fixes known data-entry problems, writes results/survey_clean.csv.
# Author: Ana Rojas, 2026-11-19

library(dplyr)                       # 1. packages at the top

raw <- read.csv("data/survey.csv")   # 2. read inputs (relative paths)

# 3. clean -------------------------------------------------------------
clean <- raw |>
  mutate(age = if_else(age == -99, NA, age))   # -99 means "not answered"

# 4. write outputs -----------------------------------------------------
write.csv(clean, "results/survey_clean.csv", row.names = FALSE)

A header saying what the script does, packages first, then input → processing → output. Comments explain why (“-99 means not answered”), not what. The code already says what. In Positron, a comment ending in four or more dashes becomes a section you can jump to from the outline.

Don't repeat yourself

Copy-pasted code is where bugs hide: you fix one copy and forget the others, or change a name in one place only. When you see the same lines twice, write a function (Week 05) and call it twice. Put numbers that mean something in named objects (kmh_per_knot <- 1.852) instead of repeating “magic numbers”.

Names and style

Follow one style consistently. This course uses the tidyverse style guide:

Positron can reformat a file for you (right-click → Format Document). It fixes spacing, but it can't choose good names.

Test your assumptions

stopifnot(nrow(clean) == nrow(raw))          # cleaning shouldn't drop rows
stopifnot(all(clean$age >= 0, na.rm = TRUE))

# a tiny test for a function: known inputs, known answers
stopifnot(isTRUE(all.equal(standardize(c(1, 2, 3)), c(-1, 0, 1))))

A test is code that stops loudly if something you believe is false. Put checks after each important step, and write a few tests with known answers for every function. Good tests fail on wrong code; a test that can't fail isn't testing anything. For larger projects, the testthat package organises tests.

Version control and reproducibility

Exercises

Each exercise gives you up to 10 checked attempts. Use Run as many times as you like to experiment first, then click Check answer when you're ready. Your exercises use numbers/subsets unique to you, so answers won't match a classmate's exactly.

Exercise 1 — Stop repeating yourself

0 / 10 attempts used

Click Run to see copy-pasted code that standardizes three columns of gm (subtract the mean, divide by the standard deviation). One copy has a bug. Replace it with a function standardize(x), and use it to create z_life, z_gdp and z_pop (for lifeExp, gdpPercap and pop). Did you spot the bug?

Pasting isn't accepted here — please type your answer so it reflects your own work.

      

Exercise 2 — Names that say what they mean

0 / 10 attempts used

survey came from a spreadsheet with awkward column names (click Run). Give it new column names with names(survey) <- c(...). Each new name must be:

  • lower_snake_case: lowercase letters, digits and single underscores, starting with a letter;
  • descriptive: at least 3 characters, and not a vague word like x, var, data, col or temp;
  • unique.

Keep units where they matter (e.g. height_cm).

Pasting isn't accepted here — please type your answer so it reflects your own work.

      

Exercise 3 — Tests that catch bugs

0 / 10 attempts used

pct_change(old, new) should return the percentage change from old to new, e.g. pct_change(50, 60) is 20 and pct_change(80, 60) is -25. Write a function test_pct_change(f) that tests any candidate function f with stopifnot(), using inputs whose answers you know. It must run silently for a correct f and stop with an error for buggy ones. I'll try it on the correct function and on three different buggy versions.

Pasting isn't accepted here — please type your answer so it reflects your own work.

      

Exercise 4 — Make it readable

0 / 10 attempts used

Click Run to see a working but badly styled snippet. Rewrite it inside the raw string as tidy_code so that it follows the style rules above: <- for assignment, spaces around <-, =, +, *, /, > and after commas, TRUE not T, lines under 80 characters, and a better name than X for the result. It must still compute the same values.

Pasting isn't accepted here — please type your answer so it reflects your own work.

      

Key Points

Proof of completion

Once you've attempted every exercise above, download a PDF summary of your work on this lesson — your name, the exercises you passed, attempt counts, timing, and your final code for each.