Found some Go code on r/AdventOfCode for 2023, Day 1 Part 2. The OP shared their code.
The code worked and gave the correct results for my puzzle input but it was a bit hard to read. So I thought I'd have a go at refactoring it for clarity and simplifying the implementation.
I don't know if this is idiomatic Go but I learned a few things from this refactoring exercise.
- Functions can return more than one value. The examples I've seen that do this use a second return value as an error indicator rather than using a specially designated value like
-1
. In the standard Go library,err
is often a second return value. In this case, I used the second return value to indicate whether a digit was found and named the variable for it accordingly. - The
slices.Backward()
function provides a way to iterate in reverse order. I used it to convert a conventional indexed for-loop to one that usedrange
instead. I personally like how the for-loop with range reads; it looks cleaner to me. It's also more symmetrical with the for-loop infirstDigitIn()
. YMMV. - Using
panic()
to signal an unexpected exception. Should probably be more careful about using it in real-world code but for the purposes here, it's simpler and cleaner. - It's easier to Refactor|Extract things if variable declarations are close to the related code that use them. Any unrelated intervening code tends to inhibit refactoring.
- Separating code that have different concerns makes it easier to simplify logic. When code is all lumped together in one execution block, accidental dependencies crop up and these tend to make the code and logic more complex.
- Be on the lookout for functions that have multiple parameters. If one or more parameters are related to each other, it may be possible to eliminate some parameters by lifting logic around that relationship up one level. This was done to simplify the
digit()
function down to taking a single parameter, fromdigit(string, int32, int)
todigit(string)
. From an API perspective, this makes more sense. - Short declarations in if-statements help to keep the scope of variables as small as possible and makes the code more concise. I used this together with returning multiple values from a function to tighten up the logic in a few spots, the first example being in the
firstDigit()
function,if digit, found := digit(line[index:]); found { ...
.
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand."—Martin Fowler