Introducing GoMethods
GoMethods is a linter that’ll help Go developers to keep non-reflection, holistic methods like serializer, validator and comparators up-to-date and complete, even you miss to update them following the field list.
Problem
Let me introduce you the problem.
Instead of using reflection, I usually implement manual methods that needs to visit all fields of a struct. Like validators, serializers or comparators. Eg:
type Request struct {
Age HumanAge
School School
Class StudentClass
}
func (r Request) Validate() map[string]error {
errs := map[string]error{}
if err := r.Age.Validate(); err != nil {
errs["age"] = err
}
// ...
}
func (r Request) String() string {
return fmt.Sprintf(`Age: %q, School: %q, Class: %q`, r.Age, r.School, r.Class)
}
func (r Request) Equals(s Request) bool {
return r.Age == s.Age &&
r.School == s.School &&
r.Class == s.Class
}
The problem is that each such struct adds ~3 new long-term liability to my chore list because each such method needs to be updated with the list of fields of the struct. Adding the 4th field to Request creates a need navigate to the struct’s methods and check if anyone needs update.
I thought why it would not be better to use linter to perform those checks at CI if any such method needs an update?
Solution
Now let me introduce you my solution.
I created a golangci plugin. I call it GoMethods. It is a linter which warns you when a doc-comment-annotated method goes out-of-sync with the receiver’s (or underlying type’s) field list. You opt-in any method for such checks by adding this annotation:
//gomethods:all
func (r Request) String() string
As well as checking all fields, GoMethods can also be directed to only require exported fields:
//gomethods:exported
func (r Request) String() string
Install GoMethods as a standalone linter and invoke from shell as:
go install go.ufukty.com/gomethods@latest
Example
Briefly:
type Node struct {
Exported any
unexported any
}
// gomethods: all
func (n Node) Foo() {}
// gomethods: exported
func (n Node) Bar() {}
Run the linter:
$ gomethods ./...
.../example.go:8:1: missing fields: Exported, unexported
.../example.go:11:1: missing field: Exported
Testing
The solution is both unit and manually tested.
The test case for unit test can be seen on GitHub with // want:-like assertions. Maybe it communicates the problem better than the short example above.
Summary
GoMethods reduces the duration for noticing outdated holistic methods and possbility to ship code with them; thus improving reliability and maintainability.