02. Writing good code
Good coding practices
Some basic good habits will get you a long way in programming.
- Document what you’re doing in comments
- Document functions
- Keep track of sources of data—I try to have downloading data be something I do programmatically in a script, whether it’s from an API, downloaded from an open data portal, scraped from a website,
curlcalls, or anything else. - If you base your code off a Stack Overflow answer, a tutorial, a chatbot, or anything else, leave yourself a comment with a link to the source. It keeps you honest and will point you back to where you got help in the first place.
- Organize your work into projects with a consistent structure
- Use naming conventions that describe what you’re doing or what something is
- Write informative git commit messages
- If you get into larger projects, manage orchestration with build tools
- READ THE DOCUMENTATION
Documenting code
One of the most important things you can do as a programmer is to document your code. This can be hard to do well, but it’s essential to making sure your code is clear and accountable and that your work can be reproduced or repurposed. (If you’ve followed the “replicability crisis” in the sciences over the past decade or so, you’ve seen what can go very wrong when your work isn’t documented accurately for yourself and others!)
A common suggestion is to write your code assuming you’ll come back to it in 6 months and need to be able to pick up where you left off. I usually also assume a coworker or colleague will need to rerun or reuse my code, so even if I’m doing something that I’ll remember 6 months from now, they might not know what things mean. It also gets me out of spending unnecessary amounts of time walking interns through an analysis if I can say, “I tried to document everything really well, so read through it, run all the code, and let me know if you need help after that.” Documenting code also helps ease the transition into package development, which requires a lot of documentation.
I don’t document everything—plenty of my work is routine and straightforward enough—but some of the things I try to always take note of:
- Any sort of analysis or process that’s out of the ordinary or complex. Don’t assume you’ll remember later why you used a new approach.
- Anything I know someone else will need to be able to reference. Sometimes I do EDA on something that a coworker will then finish up or need to write about. I need to make sure they can do that accurately.
- Outside sources that don’t come from that specific project. If your project is contained within a set of folders, and you’ve copied data in from some other project, make a note of where it comes from so if you need to update it you know where to get it from.
- Decision-making that you might need to keep track of or argue for later. e.g. a comparison of categories between datasets with a note that says “these categories changed significantly since the previous data collection” will be helpful when someone asks why you didn’t include trends in an analysis.
- References. If I came up with some code based on a Stack Overflow post or a blog post somewhere, or I’m building off of someone else’s methodology, I’ll usually include a link in my comments.
This also applies to simple things like organizing your projects. If you have a bunch of folders called things like “data analysis 1” and they all contain a jumble of different notebooks and scripts for different purposes, and the scripts are all called “analysis_of_stuff.R”, you’re going to lose things easily and not know how different pieces build on each other. Similarly, don’t spend time doing an analysis only to write your data out to a file called “data.csv” and a plot called “map.png”. This might seem obvious, but I’ve seen people do all of these things.
Git commit messages
We’ll use git for version control with our work in this class, as you have in previous classes. If you need a refresher or references, I recommend:
- Chacon, S., & Straub, B. (2014). Pro Git (2nd ed). Apress L. P. https://git-scm.com/book/en/v2 for detailed references,
- Dang It, Git?! for things that commonly go wrong (more commonly known by its other name, Oh Shit, Git?!, and available as an illustrated zine)
- Bryan, J. (2025). Happy git and GitHub for the useR. https://happygitwithr.com/
When you finish some portion of a task in writing code or documentation, you’ll want to save, add, and commit your changes to the version history. You’ll either do this in a GUI, like the one included in RStudio, GitHub Desktop, or some similar software, or on the command line. Either way, your commit message should be fairly specific and informative.
If you use a time tracker for work, like log how much time you spend on specific projects, think of commit messages as being part of a similar system. When I’m billing a client, I need to report something more specific than “Wrote code,” and something a little less specific than “Wrote the script income_analysis_by_geography.R to conduct the following analyses…” A happy medium would be something like “Income analysis by geography for viz relaunch.” Your commit messages should serve a similar purpose: they give a brief record of what you did in that chunk of work so that you, your colleagues, and any clients who might see your code know what you did and why you changed it.
Translating that to commit messages:
- “Push to GitHub” is not an informative commit message.
- “Add the utility functions dollar, dollar_thousands, dollar_change, lots_of_dollars, dollars_with_cents to utils/helper_functions.R” is too much detail and repeats info tracked in the commit itself
- “Add functions to format dollar amounts to helpers” is a good middle ground
Reusable code
One rule of thumb I’ve heard is that it’s fine to repeat your code to do the same thing twice, but if you need to do it a third time, you should write a function. It might mean taking a step back from what you’re working on at the moment, but it’s pretty much always worth the time. Alongside documenting your code in general, it’s important to document your functions—what they do, what the arguments mean, what types of values arguments can take. Try to your functions and their arguments in ways that make it clear what they mean as well.
Organization
Come up with a structure of directories you like for a project, and stick with it. The starter repo for each project in this class will have a pared down version of what I usually use, but a full version of what I might have, even for a small project, looks like this:
cool_project
¦--analysis # EDA, notebooks, and scripts that create output
|--design # scripts *only* for creating publishable charts
¦--fetch_data # raw data, often downloaded in a script
¦ ¦--permits_xlsx # folders for each raw data source
¦ °--housing_pums
¦--input_data # cleaned data that is sourced for the project
¦--output_data # data that's a product of analysis in this project
¦--plots # plots that can be distributed or published
¦--prep_scripts # scripts that download, clean, reshape data
°--utils # misc scripts & bits of data to use throughout the project
Know where your files are & how to get to them
When you run code in notebook formats like Rmarkdown and quarto, the code is executed relative to that file. When you source a script, code is executed relative to where it’s sourced from, likely the root directory of the project. This can lead to errors when you render a notebook, because your code can no longer find files you’re referring to. The helper function here::here points to the root of the project and builds paths from there.
Build tools are outside the scope of this class, but for larger projects especially or projects that will need to be updated over time, they’ll save you a lot of headaches. I have some projects that I rebuild once a year when new ACS data comes out, and I’ve got things down to where I can make one or two calls on the command line flagging the year as a variable, and all the data wrangling and analyses are ready to go. In fact, this site rebuilds from a frozen list of packages every time I push to GitHub, and if that build is successful, it publishes automatically.
Some tools I use:
- GNU Make, the OG build tool
- Snakemake, like GNU Make but written in Python and designed for data analysis
- GitHub actions, including ones specifically for R
- Docker, build a small isolated environment for your projects, some designed for R
- Package & environment managers: mamba or uv for Python, renv for R
Lab
In the lab for this topic, we’ll get started with developing helper functions that we’ll use and iterate on throughout the course.