liveBook · Manning

Refactoring

As explained in Shipping Go: Develop, deliver, discuss, design, and go again

Refactoring is the restructuring of existing source code to improve its internal design without changing its externally observable behavior.

A passing test does not mean the code is finished. The implementation or its tests may still be repetitive or hard to extend. Refactoring changes that structure to make the next small change easier while keeping the tested behavior intact. section 3.3

Keep refactoring close to the work it supports:

  • Write a test for one expected result, run it, and make the smallest change that passes it. section 3.2
  • Simplify the code or tests, then run the tests again to check that the restructuring worked. section 3.2 section 3.3
  • Treat a newly discovered edge case as a new test and implementation step, not as proof that the refactor alone added the needed behavior. section 3.3

Make translation tests easier to extend

Separate assertions for English, German, Finnish, and Dutch translations repeat the same check. Put the inputs and expected outputs in a table instead, so another case can be added without another copy of the assertion. Run the tests after the change. A case such as translating “bye” is a separate check of behavior: a passing table does not guarantee that every untested input is handled correctly. section 3.3

Listing 3.7 translator_test.go
package translation_test
 
import (
    "testing"
    "github.com/holmes89/hello-api/translation"
)
 
func TestTranslate(t *testing.T) {
    // Arrange
    tt := []struct {                                             #1
        Word        string
        Language    string
        Translation string
    }{
        {                                                        #2
            Word:        "hello",
            Language:    "english",
            Translation: "hello",
        },
        {
            Word:        "hello",
            Language:    "german",
            Translation: "hallo",
        },
        {
            Word:        "hello",
            Language:    "finnish",
            Translation: "hei",
        },
        {
            Word:        "hello",
            Language:    "dutch",
            Translation: "",
        },
    }
 
    for _, test := range tt {                                    #3
        // Act
        res := translation.Translate(test.Word, test.Language)   #4
 
        // Assert
        if res != test.Translation {                             #5
            t.Errorf(
                `expected "%s" to be "%s" from "%s" but received "%s"`,
                test.Word, test.Language, test.Translation, res)
        }
    }
}

The Go test listing shows translation scenarios collected as rows with Word, Language, and Translation fields. section 3.3

Keep the feedback loop short: restructure one piece, rerun the tests, then add the next case. Tests near implementation help catch unintended business-logic changes before those bugs reach QA. section 3.3 section 3.0

How Shipping Go: Develop, deliver, discuss, design, and go again covers it

How deep it goes
Works through a test-structure change and its verification, rather than a broad catalog of refactoring techniques.
Examples
Uses Go translation tests that replace repeated assertions with cases for English, German, Finnish, and Dutch.
What it assumes
Assumes the ability to read Go functions and unit tests while explaining the test-first cycle.
What you can do after
Practice/perform it by simplifying a passing test or implementation and rerunning tests before adding another behavior case.
How it connects
Links refactoring to test-driven development, unit tests, table-driven tests, and feedback on business-logic changes.

Also explained in

Compare how 22 books explain refactoring →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Shipping Go: Develop, deliver, discuss, design, and go again. The section links point to the parts of the book it draws on.