Duplicate String Literals

ID

go.duplicate_string_literals

Severity

high

Remediation Complexity

medium

Remediation Risk

low

Remediation Effort

medium

Resource

Code Smell

Language

Go

Tags

code-style, duplication

Description

Reports a string literal that appears three or more times in one file, so it can be extracted to a named constant. One finding per literal, at its first occurrence — the repetition is one fact about the file, and one edit fixes every copy.

Only literals in expression position are counted. Import paths and struct tags are excluded by the shape of the parse tree, and const declarations and composite literals are excluded deliberately. See Configuration for the thresholds.

Rationale

func observe(hits int) error {
	fmt.Println("cache miss")                        // FLAW — repeated 3 times in this file
	if hits == 0 {
		return errors.New("cache miss")
	}

	return fmt.Errorf("%s: %d", "cache miss", hits)
}

The copies have to be kept in step by hand, and nothing in the source says they were ever meant to agree. When the value changes — a metric name, a header key, a status string a client matches on — whoever makes the change has to find every copy by text search and get all of them. Missing one produces a bug that no compiler catches and that only shows up in whichever code path used the stale copy.

A named constant turns that search into a rename the compiler checks, and it gives the value a name that says what it means at each use site.

Remediation

Extract the value to a package-level const and use it everywhere.

const eventCacheMiss = "cache miss"

func observe(hits int) error {
	fmt.Println(eventCacheMiss)
	if hits == 0 {
		return errors.New(eventCacheMiss)
	}

	return fmt.Errorf("%s: %d", eventCacheMiss, hits)
}

When the repetition is genuinely a data table rather than a magic value, write it as one: a slice or map literal is excluded by the rule, because "extract it to a constant" is not advice that can be followed for a column of rows that happen to share a word.

Configuration

Property Meaning Default

minOccurrences

Copies of the literal in one file before it is reported.

3

minLength

Shortest literal considered. Short strings — "", ",", "id" — are rarely worth a constant, and naming them usually makes the call site harder to read, not easier.

5

The following are never counted, and are not configurable:

  • Import paths and struct tags — the same text in every file that needs it, and not a value any constant could replace. Duplicated imports are covered by go.duplicate_imports.

  • Literals in a const declaration — already constants; naming a constant after another constant is not a fix.

  • Literals inside a composite literal — a slice, map or struct literal is a data table, not logic.

  • A bare identifier passed as a call argument — one ASCII word of letters, digits and underscores, as in mod.Lookup("InitPlugin"), labels.Get("dest_server") or flags.String("cleanup", def, usage). The word names the symbol, field, flag or log key being addressed and already reads as itself at every call site, so a constant would replace a name the reader recognises with one they have to look up. Prose and format strings in the same position are still counted — those are messages, and a message repeated three times will drift. The same word compared against, returned or used as a map key is still counted: there it is a magic value being matched, not a name being passed.

  • Test files (_test.go) — a table-driven test names its cases with repeated strings on purpose, and the repetition is what makes each row readable on its own.

The interpreted form "x" and the raw form x count as the same string, since they are the same value to the compiler and to every reader.