Language tools · 9 MIN READ
Interactive refactoring in gopls: make the decision before the edit
The Go team’s 16 October article explains interactive refactoring. We examine how to review interfaces, generics and tags while keeping shipped features separate from previews.
Event: · Reporting and practical analysis. Sources listed below.

The news is a conversation with the tool
On 16 October 2026, the Go team published an article about interactive refactoring in gopls. It describes operations that ask for the programmer’s choices before producing an edit, including interface work and struct tags. The article discusses gopls 0.24.0, whose release tag appeared on 15 October. Supporting VS Code integration was already available in the 0.56 series; this is not a capability first invented by today’s 0.58 extension release.
Keep the status of each example clear. Interface and tag support predates 0.24, while the newer discussion includes generic-interface work. Moving a declaration is presented as a preview, and changing a signature is a future direction. An example in a forward-looking article is not a promise that every editor can perform that operation today. Read the Go team’s article and 0.24 release notes for the exact feature boundaries.
Our analysis focuses on the engineering decision behind a refactor. An interactive prompt can make that decision easier to express, but the developer still needs to know what behaviour should remain stable and what evidence will demonstrate it.
State the invariant before selecting a code action
A refactor is easier to review when its intended invariant is written down first. For example: callers should observe the same JSON fields, cancellation should still stop the same work, or a consumer should require only the same two operations from a dependency. That sentence gives the reviewer a reason to reject an attractive but unrelated change.
Without an invariant, a code action can become a design decision by accident. The tool offers a valid transformation, the diff compiles, and the team accepts a broader interface or an altered representation without discussing it. Syntax-aware tooling is valuable precisely because it can make edits quickly; speed increases the need to make the intended boundary explicit.
Use a small disposable branch or an otherwise reversible working state for exploration. Apply one operation, inspect the result and compare it with the invariant. If the operation touches more of the repository than expected, stop and understand the scope. Do not stack several automated transformations until the original reason for the change is hard to recover.
Interfaces should describe a consumer’s need
An interface can make a dependency easier to substitute, but it can also preserve accidental complexity if it simply mirrors a large implementation. Before generating one, identify the consumer that needs the abstraction. Which operations does that consumer actually call? Which error semantics does it depend on? Who owns any resource returned by those operations?
A small interface is not automatically a good interface. A single method can still hide an unclear contract, especially when it returns a cursor, stream or callback whose lifetime is ambiguous. Name the operation in terms of the consumer’s purpose and document the behaviour that the type system cannot express. A test double is useful only if it follows those same semantics.
After generating an implementation stub, search for placeholder behaviour and review each method body. A method set that satisfies the compiler can still panic, ignore a context or return a misleading empty value. Link the method to a specific test that demonstrates its intended behaviour. This is where a mechanical edit becomes an implemented contract.

Generic interfaces need concrete examples
Generics can express useful relationships between types, but a generic declaration is not an explanation of why those relationships are needed. Write two concrete use cases before abstracting. If the second case requires a different ownership model or error contract, sharing a type parameter may not be the most important design problem.
When reviewing a generated generic interface, inspect the constraints, method signatures and the places where type arguments are selected. Ask what a caller can do with the promised operations. A constraint that merely permits a broad family of types may not express the operation the implementation actually performs. Conversely, a narrow constraint can make a generally useful function unnecessarily difficult to reuse.
Keep the first test small and concrete. Use ordinary values that make the result easy to inspect, then add a second type only when it demonstrates the intended generality. The Go specification is the reference for language rules; an editor prompt should help express those rules, not replace understanding of them.
Tags move a refactor across a data boundary
A tag change can alter encoding or persistence without changing the visible control flow. That is why a prompt asking for tag names and options should be treated as a contract decision. Identify the library that reads the tag and the external representation that depends on it before accepting a generated edit.
For JSON, choose an example whose zero value matters. Test whether the field remains present and whether its name is stable. For persistence, consider the stored shape and compatibility with existing records. If one type serves multiple boundaries, inspect all relevant tag namespaces rather than assuming a JSON-oriented command is the only consumer of the declaration.
Our VS Code 0.58 upgrade analysis includes a small encoding contract test. Use that pattern as a starting point, then adapt the assertion to the representation your application promises. A generated tag that looks idiomatic can still be wrong for a client that relies on a particular field being present.
Separate moving code from moving responsibility
A declaration can move between files or packages while appearing unchanged to a casual reader. Yet a package move can affect import direction, visibility and the place where dependencies are created. That makes a future move-declaration tool interesting, but it does not make package design an automatic operation. Remember that the cited article presents this work as a preview.
Before moving a declaration manually or with tooling, draw the imports before and after. Identify whether an implementation detail becomes exported merely to make the move compile. Check whether tests now require a wider package boundary or a new dependency. A clean-looking directory tree is not useful if it creates a harder dependency cycle or exposes a private contract.
The architecture learning area offers a useful lens: choose boundaries according to the changes and failure modes you need to contain. Moving a declaration is one possible implementation step. The design decision is who should know about it and which direction the dependency should point.

Test behaviour at the affected boundary
Choose tests that correspond to the refactor’s stated invariant. If the change concerns encoding, inspect encoded data. If it concerns cancellation, start work and cancel it. If it concerns an interface, exercise the consumer through both the real implementation and an appropriate test double where that comparison is meaningful.
Do not write a test that simply repeats the generated implementation. Such a test can preserve the same mistake in two places and still pass. Prefer an observable input and result or a clearly stated state transition. A table-driven test is useful when the cases represent different conditions, not when every row is a cosmetic variation of the same happy path.
Run a focused test early and the repository’s required gate before completing the change. Keep the result scoped to what actually ran. A successful package test is valuable evidence about that package; it is not a claim that every platform, integration and deployment path has been validated. Clear test reporting makes automated assistance easier to trust and review.
Interactive does not mean identical in every editor
A language server and an editor client negotiate capabilities. Even when a server supports an operation, the client may need specific integration to present a prompt and apply the result. Check your actual editor, extension and language-server versions before diagnosing a missing action as a broken Go module.
Use a minimal example in the intended workspace. Confirm that package loading succeeded and that the cursor or selection is in a place where the action is applicable. If the action remains unavailable, compare the documented client support with the versions in use. A screenshot of a command from another editor is not enough to establish that your client should expose the same workflow.
When reporting a problem, include the operation, the selection, the expected result and a small reproducer. Keep private source and credentials out of traces. Our editor upgrade checklist describes a bounded way to collect this evidence without changing every setting or clearing every cache at once.
Coding agents should explain the transformation
An agent can help prepare a refactor by finding consumers, identifying tests and proposing a small sequence of edits. Give it the invariant and ask for the review surface before implementation. For example, ask which JSON contracts depend on a declaration or which goroutines own a resource that an interface returns.
After the edit, require the agent to explain the changed boundary and identify the commands it actually ran. A model can produce a convincing description of a test that never executed, so keep the evidence concrete. Review the diff yourself at the places where behaviour is external: encoding, storage, concurrency and errors.
The Go coding-agent workflow and concurrency review guide apply the same principle. Automation is most useful when it reduces mechanical work while making decisions more visible. It becomes less useful when it hides a large change behind a confident summary and a compiler result that does not test the contract.
Try a small refactoring lab
Take a small Go package with one consumer and one concrete dependency. Write down the two operations the consumer needs, including error and ownership semantics. Add a focused test for the current behaviour. Then explore an interface-oriented code action supported by your actual tools, or perform the equivalent small edit manually.
Inspect the generated method set, replace any placeholders, run the test and compare the final diff with the original invariant. Next, change one requirement: perhaps cancellation must stop a long operation, or a returned stream must be closed by the caller. Decide whether the interface still communicates the contract. This second step exposes whether the abstraction helps with change rather than merely satisfying a style preference.
Finish by explaining the result aloud in Interview preparation, or return to the relevant course branch. The useful skill is a deliberate loop: select a bounded change, answer the design question, review the proposed edit, apply it and test the behaviour. Interactive refactoring makes the conversation with the tool richer; understanding the program remains the developer’s job.
Primary sources & editorial method
We checked these primary references on 20 October 2026. Release facts are attributed; recommended workflows and examples are GopherForge analysis. Illustrations are conceptual, not vendor screenshots or benchmarks.