Fostering Improved Code Quality

in Statistics and Data Science Students (and Their Instructors)

Randall Pruim and Nicholas J. Horton

2024-06-11

Fostering Improved Code Quality

Before we get started…

… introduce yourself in the chat.

  • Name

  • Affiliation

  • What do you hope to get out of this session?

    (Why are you here?)

If you want these slides, you can get them here

(This will appear again at the end.)

Who are we?

Nicholas Horton,
Amherst College

Randall Pruim,
Calvin University

Many thanks to Maria-Cristiana Gîrjău for her work on this project.

Outline: 5 Questions

  • Who?

  • Why?

  • When?

  • What?

  • How?

Outline: 5 Questions

  • Who are you?

  • Why bother about code quality?

  • When should we foster code quality?

  • What is quality code?

  • How do we foster code quality?

    • Principles

    • Strategies

    • Tactics

Why foster code quality?

  • Good coding practices lead to more reliable and maintainable code.
  • Saves time.
  • Promotes better collaboration (including with future self).
  • Beautiful code is beautiful.

When?

A: Early and often (i.e., throughout the entire program)

 

Why foster code quality early?

  • It is easier to learn good habits early than to break bad habits later.
  • It is easier to learn from high quality code.

A progressive approach

  • Focus on a few important things early; become more demanding as time goes on

    • For students, instructors, and programs
    • Goal: Be better at the end of the semester than at the beginning
  • Instructor code should meet higher standards than is demanded of students.
  • It works better if all instructors in a program are on the same page

    • But don’t let your fellow instructors be an excuse for your own bad habits.

What makes good code good?

Example 1

An April 2021 twitter post (Meyer 2021) commented:

“It is really painful when taking a graduate level data science course and the instructor’s code is considerably below any acceptable standard in the real world. Here is some real life code from a demo offered for the current homework…”

data <- read.csv(fname)
train <- data[0:(length(data[,1])-8),]
train.ts <- ts(train[,c(2,3,4,5)],,start=c(2014,1),freq=52)
test <- data[(length(data[,1])-7):length(data[,1]),]

What makes good code good?

Example 1 (cleaned up)

Original:

data <- read.csv(fname)
train <- data[0:(length(data[,1])-8),]
train.ts <- ts(train[,c(2,3,4,5)],,start=c(2014,1),freq=52)
test <- data[(length(data[,1])-7):length(data[,1]),]

This code, while short, is exceptionally hard to read. There are many ways that this could be improved, some of which are demonstrated below.

n_test <- 8
data <- readr::read_csv(fname)  
train <- head(data, - n_test)
test <- tail(data, n_test)
train_ts <- ts(
  dplyr::select(train, 2:5), # even better: use var names here
  start = c(2014, 1), 
  freq = 52)

What makes good code good?

The Four C’s

1. Correctness

  • Necessary, but not sufficient.
  • Other characteristics support correctness.

What makes good code good?

The Four C’s

1. Correctness

2. Clarity

  • Easy for humans to tell what the code is supposed to be doing.
  • Easier to maintain/modify.

What makes good code good?

The Four C’s

1. Correctness

2. Clarity

3. Containment

  • Keep separate things that should be separate; together things that should be together.
  • “low coupling, high cohesion.” Vartanian (2022)
  • Examples:
    • Prefer a data frame over several individual vectors,
    • Use functions to contain reusable code,
    • Keep code used across files or projects in a module or package.

What makes good code good?

The Four C’s

1. Correctness

2. Clarity

3. Containment

4. Consistency

  • of style, of naming conventions, and of other coding practices.

    • “A foolish consistency is the hobgoblin of little minds.” (R W Emerson)

    • A foolish inconsistency is worse.

Example 2: Loose vectors

This code (found online) illustrates the grid method for approximating the posterior.

# define grid
p_grid <- seq( from=0 , to=1 , length.out=20 )
# define prior
prior <- rep( 1 , 20 )
# compute likelihood at each value in grid
likelihood <- dbinom( 6 , size=9 , prob=p_grid )
# compute product of likelihood and prior
unstd.posterior <- likelihood * prior
# standardize the posterior, so it sums to 1
posterior <- unstd.posterior / sum(unstd.posterior)

Example 2: Contained vectors

Proportion_Grid <-
  tibble::tibble(
    p = seq(from = 0, to = 1, length.out = 20),
    prior = 1,
    likelihood = dbinom(6, size = 9, prob = p),
    unstd_posterior = likelihood * prior,
    # standardize the posterior, so it sums to 1
    posterior = unstd_posterior / sum(unstd_posterior)
)
  • Many of the comments in original are unnecessary.
  • Data frame forces that all vectors have same length.
  • Less risk of “bleed through” from one example to another.
  • Easy display of the grid for consistency checking.
Proportion_Grid 

Example 2: Writing a function

Proportion_Grid <- function(successes = 6, trials = 9, grid_size = 20) {
  tibble::tibble(
    p = seq(from=0, to=1, length.out = grid_size),
    unstd_prior = 1,
    prior = unstd_prior / sum(unstd_prior),
    likelihood = dbinom(successes, size = trials, prob = p),
    unstd_posterior = likelihood * prior,
    # standardize the posterior, so it sums to 1
    posterior = unstd_posterior / sum(unstd_posterior)
  )}

Proportion_Grid(successes = 8, trials = 10, grid_size = 100) 
  • Teaching functions as a way to generalize code works well
    • Only minor changes to previous example
  • Explains “magic numbers” 6, 9, and 20 from previous example
    • Argument naming is important!
  • Allows rapid exploration of many examples with no copy-and-paste

Example 2: Using a function

Proportion_Grid(successes = 8, trials = 10, grid_size = 50)

Proportion_Grid(successes = 80, trials = 100, grid_size = 100)

Top Ten List

From Pruim, Gîrjău, and Horton (2023)

  1. Choose good names.

  2. Follow a style guide consistently.

  3. Create documents using tools that support reproducible workflows.

  4. Select a coherent, minimal, yet powerful tool kit.

  5. Don’t Repeat Yourself (DRY).

  1. Take advantage of a functional programming style.

  2. Employ consistency checks.

  3. Learn how to debug and to ask for help.

  4. Get (version) control of the situation.

  5. Be multilingual.

How do we do it?

Assuming you are convinced that code quality is important, how do you move towards better code quality (yours and your students’)?

  • You can contribute suggestions, comments, questions, etc. to our Google doc.

  • Continue the discussion on the CAUSE slack

  • We will share a few example ideas to get things moving.

How #1: Parsons Problems

Parsons problems: Correctly order lines of code to do a task.

Variants:

  • 2d: Indentation may be removed (Python, especially)
  • Distractors: Additional lines of code that are not needed may be included
  • Faded: Some lines of code may have “fill-in-the-blank” parts.

A Twist on Parsons Problems

Twist (untested):

  • Provide some tasks with code that follows good coding practices and other tasks that violates these principles.

    • meaningful names vs meaningless names
    • indented vs. unindented
    • appropriate use of space vs “broken space bar” code
  • Hypothesis: Students will find the tasks easier when code quality is high.

  • Hope: This will help motivate students to write higher quality code.

How #2: Tell students how to lose points

Vic Norman (Calvin, CS) is trying something new in his CS courses this coming semester.

  • Each programming assignment will have a section listing things students lost points for in the past.

Include code style items like

  • poor variable names
  • incorrect indentation
  • insufficient/superfluous use of comments
  • mixing R “dialects”
  • etc., etc.

Send a clear to signal to students about what matters.

A plug for online grading tools

Online grading tools provide a handy way to assemble these lists.

  • Key feature:

    • Create rubrics before or while grading
    • Grade by selecting rubric items
    • Can get a list of all the rubric items (and how many students were flagged for each).

Examples

Gradescope Rubric Summary

Grading App Screenshot

https://cs.calvin.edu/apps/GradingApp/

How this works (view the demo video for details):

  • Export information from Moodle
  • Import into Grading App and grade
  • Export results from Grading App
  • Import results back into Moodle

How #3: Code Review

There are many ways to do code review.

 

Important elements

  • Students see other students’ code
  • Students give and receive feedback
  • Students have a chance to revise/improve code

 

Combines well with GitHub!

Code Review – Example methods

 

Simple instructions for homework assignment

Look over the examples submitted by students in this class and

  1. Identify one thing that you especially like about someone’s code.
  2. Identify one thing that you think could be improved and suggest an improvement.

 

In class activity

  1. Have students work in small groups to perform some coding task.

  2. Project solutions from various groups and have whole-class discussion about things that could be improved, different approaches/styles used, etc.

What Next?

The most important thing is …

  • Don’t wait to be perfect to be better.
  • Make it a goal to improve something each semseter.

What next?

 

  • More discussion

    • Continue the discussion on the CAUSE slack.

    • Vote for the “hot topic” of the day.

    • Talk to your CS colleagues.

   

  • More reading

    • Pruim, Gîrjău, and Horton (2023)

    • See references below for some places to start.

References and Resources

Aruliah, Dhavide A., C. Titus Brown, Neil P. Chue Hong, Matt Davis, Richard T. Guy, Steven H. D. Haddock, Katy Huff, et al. 2012. “Best Practices for Scientific Computing.” PLOS Biology 12. https://doi.org/10.1371/journal.pbio.1001745.
Du, Yuemeng, Andrew Luxton-Reilly, and Paul Denny. 2020. “A Review of Research on Parsons Problems.” In Proceedings of the Twenty-Second Australasian Computing Education Conference, 195–202. ACE’20. New York, NY, USA: Association for Computing Machinery. https://doi.org/10.1145/3373165.3373187.
Ericson, Barbara J., Lauren E. Margulieux, and Jochen Rick. 2017. “Solving Parsons Problems Versus Fixing and Writing Code.” In Proceedings of the 17th Koli Calling International Conference on Computing Education Research, 20–29. Koli Calling ’17. New York, NY, USA: Association for Computing Machinery. https://doi.org/10.1145/3141880.3141895.
Meyer, Austin. 2021. “Austin Meyer on Twitter.” https://twitter.com/austingmeyer/status/1380942918593183744.
Pruim, Randall, Maria-Cristiana Gîrjău, and Nicholas J. Horton. 2023. “Fostering Better Coding Practices for Data Scientists.” Harvard Data Science Review 5 (3). https://hdsr.mitpress.mit.edu/pub/8wsiqh1c.
Sandve, G. K., A. Nektrutenko, J. Taylor, and E. Hovig. 2013. “Ten Simple Rules for Reproducible Computational Research.” PLoS Computational Biology. https://doi.org/10.1371/journal.pcbi.1003285.
Stegeman, Martijn, Erik Barendsen, and Sjaak Smetsers. 2016. “Designing a Rubric for Feedback on Code Quality in Programming Courses.” https://dl.acm.org/doi/10.1145/2999541.2999555.
Vartanian, Erica. 2022. “6 Coding Best Practices for Beginner Programmers.” https://www.educative.io/blog/coding-best-practices#lowhigh.
Wilson, Greg, Jennifer Bryan, Karen Cranston, Justin Kitzes, Lex Nederbragt, and Tracy K. Teal. 2017. “Good Enough Practices in Scientific Computing.” PLOS Computational Biology 13 (6): 1–20. https://doi.org/10.1371/journal.pcbi.1005510.