I'll Be Backend
Code smells · Go

Clean code: replace comments with code

Let the code say what it does, so no comment can drift into a lie.

16 steps5 chapters~6 min at 1.25×#comments#naming#readability#magic numbers
The lesson plays right here. Pause anywhere, step back, or jump to a step below. Watch the Go video on YouTube Code on GitHub

Watch the narrated video

Every step

Problem

Steps 1–3

Refactor

Steps 4–12

Payoff

Step 13

Go bonus

Step 14

Recap

Steps 15–16

The code: before and after

Before

import (
	"math"
	"time"
)

func CancellationFee(b Booking, today time.Time) float64 {
	// days left before check-in
	d := int(b.CheckIn.Sub(today).Hours() / 24)
	// free cancellation 14 days or more before check-in
	if d >= 14 {
		return 0
	}
	// non-refundable or under 2 days out: pay in full
	if b.Rate == 'N' || d < 2 {
		return b.Total
	}
	// otherwise half the total
	f := b.Total * 0.5
	// partner contracts require a one-night minimum
	return math.Max(f, b.Total/float64(b.Nights))
}

After

import "time"

const (
	freeCancellationDays = 14
	loyaltyFreeDays      = 7
	lastMinuteDays       = 2
	nonRefundable        = 'N'
)

func CancellationFee(b Booking, today time.Time) float64 {
	daysBeforeCheckIn := int(b.CheckIn.Sub(today).Hours() / 24)
	if isFreeCancellation(b, daysBeforeCheckIn) {
		return 0
	}
	if paysInFull(b, daysBeforeCheckIn) {
		return b.Total
	}
	halfTotal := b.Total * 0.5
	// partner contracts require a one-night minimum
	return max(halfTotal, nightlyPrice(b))
}

func isFreeCancellation(b Booking, days int) bool {
	if b.Loyal {
		return days >= loyaltyFreeDays
	}
	return days >= freeCancellationDays
}

func paysInFull(b Booking, days int) bool {
	return b.Rate == nonRefundable || days < lastMinuteDays
}

func nightlyPrice(b Booking) float64 {
	return b.Total / float64(b.Nights)
}

Transcript, step by step

Problem

Step 1: A comment on every rule Before

This function works out what a hotel guest pays when they cancel a booking. Cancelling is free from fourteen days out, costs the full price on a non-refundable rate or under two days, and otherwise costs half, but never less than one night. Every rule has a comment above it, and each comment tells you what the next line does. It works, the tests pass, and it looks well documented.

Step 2: The comment still says 14 Code smell

Loyalty members now get free cancellation from seven days out. Someone adds the rule to the if and the tests pass, but the comment above it still says fourteen days or more. A comment that says what the code does is a second copy of that code, and nothing keeps the two copies in sync. This function has four of those copies, here on the day count, here on the free rule, here on full pay, and here on the half. The next developer trusts the comment, and support ends up quoting the old fourteen day rule to a loyal guest. Review missed it too, because the diff touched the code, and nobody checks the sentence sitting above it.

Step 3: Back it out, then name things Plan

Let's back that change out. We'll refactor first and turn each comment that says what into a name in the code, so there's only one copy to keep right. Each step deletes the comment its new name replaces, and then the loyalty rule goes back in.

Refactor

Step 4: Name the day count Rename Variable

The first comment was only there because the variable was called d. So we rename d to days before check in, and the comment has nothing left to say, so it goes. Every line that reads the count now says what it is.

Step 5: Name the fourteen Replace Magic Number

Fourteen was a bare number, and only the comment said it was the free cancellation window. Now it's a constant called free cancellation days, and the if reads it by name. It starts with a lowercase letter, so Go keeps it inside this package, and the const block gives the other numbers a place to go. The comment stays for now, and it goes when the whole rule gets a function of its own.

Step 6: Name the two Replace Magic Number

The number two gets the same treatment, as a constant called last minute days. Both day limits now sit together at the top of the file, so you can read the policy's numbers in one place.

Step 7: Name the rate code Replace Magic Number

The letter N is a code from the booking system, and you'd only know what it means from the comment. Naming it non refundable puts that meaning into the code itself.

Step 8: The free rule gets a name Extract Function

The free cancellation comment named a rule, so we give that rule a function with the same name. Cancellation Fee now asks is free cancellation, and the comment above that if is gone, because the call says it. The function compares the day count to the constant, so the rule lives in one place under one name.

Step 9: Full pay gets a name Extract Function

The full pay rule gets the same move, into a function called pays in full. It needs the booking for the rate and the day count for the deadline, so it takes both. Back in Cancellation Fee, the call reads the way the comment did, so the comment goes.

Step 10: Name the half Rename Variable

The comment over f said it was half the total, so we call it half total and drop the comment. The last line now compares half total with something you still have to work out in your head.

Step 11: Name one night's price Extract Function

Total divided by nights is the price of one night, and the code can say so. We extract it into nightly price, so the last line reads half total or one night, whichever is more.

Step 12: One comment stays Keep the why

One comment is left, and this one stays. The line below already shows what happens, and the comment adds the reason, which is the contract with partner hotels. No variable or function name can carry that reason, so a comment like this earns its place. Delete the comments that say what the code does, and keep the ones that say why it has to.

Payoff

Step 13: Loyalty, in one place Payoff

Now the loyalty rule goes back in, and it has an obvious home. Is free cancellation takes the booking too, and loyal guests get the seven day window from a constant called loyalty free days. The places that had comments now hold names, here the day count, here the free rule, here full pay, and here the half. There's no comment left that could still say fourteen, so this change can't leave a lie behind. Pays in full and nightly price didn't change, and Cancellation Fee only passes the booking along.

Go bonus

Step 14: Built-in max Go bonus

The last line reached into the math package for Max, and the file imported math only for that one call. Since Go one twenty-one, max is built into the language, so the call reads the same and the math import is gone. It works for any ordered type, so the same max would compare two day counts as well as two prices.

Recap

Step 15: Where we started, where we ended Before / after

On the left, every line came with a comment saying what it did. That made two copies of each rule, and the fourteen day one was the first to go stale. On the right, Cancellation Fee reads as three named calls, and the only comment left gives the contract reason. The rules live in small functions and constants, so changing a rule changes its description too.

Step 16: When to reach for it Takeaways

Turn a comment into code when it tells you what the next line does. Keep it when it explains why, like a contract, a bug workaround or a law, and keep the doc comments on exported names. You'll spot the need in a comment over a short block, a one-letter variable, or a bare number in a condition. Go's time package names its units, so fourteen times twenty-four times time dot Hour reads as two weeks without a comment.