Code Generation in Go
Code generation automates repetitive code creation, improving productivity and reducing errors. Go provides excellent support through the go generate command.
Go Generate Basics
The go generate command runs code generators specified in comments.
Good: Using go generate
package main
//go:generate echo "Generating code..."
func main() {
println("Hello, World!")
}
Run with: go generate ./...
Writing Code Generators
Good: Simple Code Generator
// gen.go
package main
import (
"fmt"
"os"
)
func main() {
// Generate code
code := `package main
import "fmt"
func GeneratedFunction() {
fmt.Println("This was generated!")
}
`
// Write to file
err := os.WriteFile("generated.go", []byte(code), 0644)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
fmt.Println("Code generated successfully")
}
Use in your code:
//go:generate go run gen.go
package main
func main() {
GeneratedFunction()
}
Template-Based Generation
Use templates for flexible code generation.
Good: Template-Based Generator
package main
import (
"fmt"
"os"
"text/template"
)
type Method struct {
Name string
ParamType string
ReturnType string
}
type Generator struct {
PackageName string
Methods []Method
}
func main() {
gen := Generator{
PackageName: "main",
Methods: []Method{
{Name: "Add", ParamType: "int", ReturnType: "int"},
{Name: "Multiply", ParamType: "int", ReturnType: "int"},
},
}
tmpl := template.Must(template.New("methods").Parse(`
package {{.PackageName}}
{{range .Methods}}
func {{.Name}}(a, b {{.ParamType}}) {{.ReturnType}} {
// TODO: Implement {{.Name}}
return 0
}
{{end}}
`))
err := tmpl.Execute(os.Stdout, gen)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
Stringer Code Generation
Generate String() methods automatically.
Good: Stringer Example
package main
//go:generate stringer -type=Color
type Color int
const (
Red Color = iota
Green
Blue
)
func main() {
println(Red.String()) // "Red"
println(Green.String()) // "Green"
println(Blue.String()) // "Blue"
}
Install stringer: go install golang.org/x/tools/cmd/stringer@latest
Enum Generation
Generate type-safe enums.
Good: Enum Generator
package main
import (
"fmt"
"os"
"text/template"
)
type EnumValue struct {
Name string
Value int
}
type Enum struct {
Name string
Values []EnumValue
}
func main() {
enum := Enum{
Name: "Status",
Values: []EnumValue{
{Name: "Pending", Value: 0},
{Name: "Active", Value: 1},
{Name: "Inactive", Value: 2},
},
}
tmpl := template.Must(template.New("enum").Parse(`
package main
type {{.Name}} int
const (
{{range .Values}}
{{.Name}} {{$.Name}} = {{.Value}}
{{end}}
)
func (s {{.Name}}) String() string {
switch s {
{{range .Values}}
case {{.Name}}:
return "{{.Name}}"
{{end}}
default:
return "Unknown"
}
}
`))
tmpl.Execute(os.Stdout, enum)
}
Mock Generation
Generate mocks for testing.
Good: Mock Generator
package main
import (
"fmt"
"os"
"text/template"
)
type MockMethod struct {
Name string
ParamTypes []string
ReturnType string
}
type MockInterface struct {
Name string
Methods []MockMethod
}
func main() {
mock := MockInterface{
Name: "UserRepository",
Methods: []MockMethod{
{Name: "GetUser", ParamTypes: []string{"int"}, ReturnType: "*User"},
{Name: "SaveUser", ParamTypes: []string{"*User"}, ReturnType: "error"},
},
}
tmpl := template.Must(template.New("mock").Parse(`
package main
type Mock{{.Name}} struct {
{{range .Methods}}
{{.Name}}Called bool
{{.Name}}Result interface{}
{{end}}
}
{{range .Methods}}
func (m *Mock{{$.Name}}) {{.Name}}({{range .ParamTypes}}p {{.}}, {{end}}) {{.ReturnType}} {
m.{{.Name}}Called = true
return m.{{.Name}}Result.({{.ReturnType}})
}
{{end}}
`))
tmpl.Execute(os.Stdout, mock)
}
Builder Pattern Generation
Generate builder methods automatically.
Good: Builder Generator
package main
import (
"fmt"
"os"
"text/template"
)
type Field struct {
Name string
Type string
}
type Builder struct {
StructName string
Fields []Field
}
func main() {
builder := Builder{
StructName: "User",
Fields: []Field{
{Name: "Name", Type: "string"},
{Name: "Age", Type: "int"},
{Name: "Email", Type: "string"},
},
}
tmpl := template.Must(template.New("builder").Parse(`
package main
type {{.StructName}}Builder struct {
{{range .Fields}}
{{.Name}} {{.Type}}
{{end}}
}
{{range .Fields}}
func (b *{{$.StructName}}Builder) With{{.Name}}({{.Name}} {{.Type}}) *{{$.StructName}}Builder {
b.{{.Name}} = {{.Name}}
return b
}
{{end}}
func (b *{{.StructName}}Builder) Build() *{{.StructName}} {
return &{{.StructName}}{
{{range .Fields}}
{{.Name}}: b.{{.Name}},
{{end}}
}
}
`))
tmpl.Execute(os.Stdout, builder)
}
Database Code Generation
Generate database models and queries.
Good: Database Model Generator
package main
import (
"fmt"
"os"
"text/template"
)
type Column struct {
Name string
Type string
GoType string
DBType string
}
type Table struct {
Name string
Columns []Column
}
func main() {
table := Table{
Name: "users",
Columns: []Column{
{Name: "id", Type: "int", GoType: "int", DBType: "INTEGER PRIMARY KEY"},
{Name: "name", Type: "string", GoType: "string", DBType: "TEXT"},
{Name: "email", Type: "string", GoType: "string", DBType: "TEXT"},
},
}
tmpl := template.Must(template.New("model").Parse(`
package main
type {{.Name}} struct {
{{range .Columns}}
{{.Name}} {{.GoType}} ` + "`" + `db:"{{.Name}}"` + "`" + `
{{end}}
}
func (t *{{.Name}}) TableName() string {
return "{{.Name}}"
}
`))
tmpl.Execute(os.Stdout, table)
}
Best Practices
- Document Generators: Explain what code is generated
- Version Control: Commit generated code
- Idempotent: Running twice should produce same result
- Error Handling: Handle generation errors gracefully
- Performance: Keep generation fast
- Testing: Test generated code
- Maintainability: Keep generators simple
- Automation: Integrate into build process
Common Pitfalls
- Complex Generators: Hard to understand and maintain
- Slow Generation: Slows down build process
- Brittle Code: Generated code breaks easily
- Poor Documentation: Hard to understand what’s generated
- Version Conflicts: Generated code out of sync
Resources
Summary
Code generation automates repetitive code creation. Use go generate to run generators. Write generators using templates for flexibility. Keep generators simple and well-documented. Test generated code thoroughly. Integrate code generation into your build process for maximum productivity.
Comments