willworth.dev
View RSS feed

Go Development Checklist: From JavaScript to Idiomatic Go

Published on

Go Development Checklist: From JavaScript to Idiomatic Go

Pre-Implementation Checklist

1. Project Structure

  • [ ] Is the code in the correct package?
  • [ ] Does the package name match its purpose?
  • [ ] Are files organized by feature rather than type?
  • [ ] Have I followed Go's standard project layout?
/cmd # Main applications
/internal # Private code
/pkg # Public library code
/api # API definitions
/web # Web assets
/configs # Configuration files
/test # Test files

2. Error Handling Strategy

  • [ ] Have I identified all possible error cases?
  • [ ] Am I using custom error types where appropriate?
  • [ ] Am I wrapping errors with context where needed?
  • [ ] Have I avoided using panic/recover (except for truly unrecoverable situations)?

3. Concurrency Needs

  • [ ] Does this code need concurrency?
  • [ ] If yes, what's the best pattern?
    • Goroutines and channels
    • sync package primitives
    • errgroup for handling multiple errors
  • [ ] Have I protected shared resources?

Code Implementation Checklist

1. Basic Go Conventions

  • [ ] Are variable names short but descriptive?
  • [ ] Do exported names (capitalized) have comments?
  • [ ] Have I used gofmt to format the code?
  • [ ] Have I used golint to check for style issues?
  • [ ] Have I used go vet to check for subtle bugs?

2. Error Handling Implementation

In JavaScript, you might use try/catch:

try {
doSomething()
} catch (err) {
handleError(err)
}

In Go, use explicit error handling:

result, err := doSomething()
if err != nil {
// Handle specific error types
switch {
case errors.Is(err, ErrNotFound):
return nil, fmt.Errorf("resource not found: %w", err)
default:
return nil, fmt.Errorf("unexpected error: %w", err)
}
}

3. Memory Management

  • [ ] Am I using pointers appropriately?
  • [ ] Have I avoided unnecessary memory allocations?
  • [ ] Am I properly closing resources (files, connections)?
  • [ ] Have I considered using sync.Pool for frequently allocated objects?

4. Performance Considerations

  • [ ] Have I used benchmarks where needed?
  • [ ] Have I profiled the code if performance is critical?
  • [ ] Am I using appropriate data structures?

Consider map vs slice based on usage:

// Frequent lookups: Use map
usersByID := make(map[string]User)

// Sequential access: Use slice
users := make([]User, 0, expectedSize)

Common JavaScript vs Go Patterns

1. Asynchronous Operations

JavaScript approach:

async function fetchData() {
try {
const result = await api.get('/data')
return result
} catch (err) {
console.error(err)
}
}

Go approach:

func fetchData() (*Data, error) {
result, err := api.Get("/data")
if err != nil {
return nil, fmt.Errorf("fetching data: %w", err)
}
return result, nil
}

// For concurrent operations:
func fetchMultiple() error {
var eg errgroup.Group
for i := 0; i < 3; i++ {
i := i // Create new variable for closure
eg.Go(func() error {
_, err := fetchData(i)
return err
})
}
return eg.Wait()
}

2. Object Methods

JavaScript classes:

class User {
constructor(name) {
this.name = name
}

greet() {
console.log(`Hello, ${this.name}`)
}
}

Go structs and methods:

type User struct {
Name string
}

// Value receiver - doesn't modify User
func (u User) Greet() {
fmt.Printf("Hello, %s\n", u.Name)
}

// Pointer receiver - can modify User
func (u *User) SetName(name string) {
u.Name = name
}

3. Error Handling Patterns

// Custom error types
type NotFoundError struct {
Resource string
}

func (e *NotFoundError) Error() string {
return fmt.Sprintf("%s not found", e.Resource)
}

// Error wrapping
if err != nil {
return fmt.Errorf("processing request: %w", err)
}

// Handling errors
var notFound *NotFoundError
if errors.As(err, &notFound) {
// Handle not found case
}

Pre-Commit Checklist

1. Code Quality

  • [ ] Run go fmt ./...
  • [ ] Run go vet ./...
  • [ ] Run golint ./...
  • [ ] Run staticcheck (if installed)
  • [ ] Run go test ./...
  • [ ] Run go test -race ./... if using concurrency

2. Documentation

  • [ ] Are all exported functions and types documented?
  • [ ] Are complex algorithms explained?
  • [ ] Have I added examples where useful?
  • [ ] Have I documented any assumptions?

3. Testing

  • [ ] Unit tests for business logic
  • [ ] Integration tests where needed
  • [ ] Table-driven tests for multiple cases
  • [ ] Benchmarks for performance-critical code

Example benchmark:

func BenchmarkOperation(b *testing.B) {
for i := 0; i < b.N; i++ {
operation()
}
}

Common Review Feedback to Anticipate

1. Error Handling

  • "Don't ignore errors"
  • "Add context to errors"
  • "Use appropriate error types"

2. Naming

  • "Use shorter names for small scopes"
  • "Make exported names descriptive"
  • "Follow Go conventions (e.g., 'ID' not 'Id')"

3. Comments

  • "Document exported items"
  • "Explain complex algorithms"
  • "Remove unnecessary comments"

4. Performance

  • "Avoid unnecessary allocations"
  • "Use appropriate data structures"
  • "Consider using sync.Pool"

5. Concurrency

  • "Protect shared resources"
  • "Use appropriate patterns"
  • "Consider error handling in goroutines"

Tools to Install

1. Essential Tools

go install golang.org/x/lint/golint@latest
go install honnef.co/go/tools/cmd/staticcheck@latest

2. Helpful Tools

go install golang.org/x/tools/cmd/godoc@latest
go install github.com/kisielk/errcheck@latest

IDE Setup (VSCode)

1. Essential Extensions

  • Go (official extension)
  • Go Test Explorer
  • Go Doc

2. Recommended Settings

{
"go.formatTool": "gofmt",
"go.lintTool": "golint",
"go.useLanguageServer": true,
"editor.formatOnSave": true
}

Conclusion

Remember that Go emphasizes:

  • Simplicity over complexity
  • Explicit over implicit
  • Error handling over exceptions
  • Concurrency through clear patterns
  • Performance through simplicity

Keep this checklist handy and refer to it often. As you become more familiar with Go, you'll internalize these patterns and practices, but it's helpful to have a structured approach while learning.