Skip to main content
โšก Calmops

Code Generation in Go

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

  1. Document Generators: Explain what code is generated
  2. Version Control: Commit generated code
  3. Idempotent: Running twice should produce same result
  4. Error Handling: Handle generation errors gracefully
  5. Performance: Keep generation fast
  6. Testing: Test generated code
  7. Maintainability: Keep generators simple
  8. 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