Code Quality Best Practices¶
Cyclomatic Complexity Management¶
Goal: Keep all functions under complexity 15 (Go Report Card A+ grade requirement)
Root Causes of High Complexity¶
- Giant Switch Statements - Nested switches create exponential complexity
- Inline Logic - All logic in one massive function instead of extracted helpers
- Complex Conditionals - Multiple
if/elsechains without extraction
Solution Pattern: Extract Helper Methods¶
Before (Bad - Complexity 37):¶
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch msg.String() {
case "a":
if m.tab == 0 && len(m.items) > 0 {
// 10 lines of logic
return m, someCmd
}
case "b":
if m.tab == 1 && m.selected < len(m.items) {
// 10 lines of logic
return m, anotherCmd
}
// ... 20 more cases
}
case OtherMsg:
// ... more nested logic
}
return m, nil
}
After (Good - Complexity <15):¶
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
return m.handleWindowSize(msg)
case tea.KeyMsg:
return m.handleKeyPress(msg)
case DataMsg:
return m.handleData(msg)
}
return m, nil
}
func (m Model) handleKeyPress(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
switch msg.String() {
case "a":
return m.handleActionA()
case "b":
return m.handleActionB()
// ... each case delegates to a focused function
}
return m, nil
}
func (m Model) handleActionA() (tea.Model, tea.Cmd) {
if m.tab == 0 && len(m.items) > 0 {
// Focused logic for action A
return m, someCmd
}
return m, nil
}
Benefits of This Pattern¶
- Lower Complexity: Each function does one thing well
- Better Testing: Can test individual handlers in isolation
- Easier Maintenance: Clear function names document behavior
- Better Navigation: Jump to
handleActionA()instead of searching a 500-line function
When to Extract¶
Extract a helper function when you see: - Nested switches (switch inside switch) - Long case blocks (>5 lines per case) - Repeated patterns (similar logic in multiple cases) - Complex conditionals (multiple && or || operators)
Naming Conventions¶
- Message handlers:
handleXxx(msg XxxMsg) (tea.Model, tea.Cmd) - Key handlers:
handleKeyA(),handleEnterKey(),handleEscKey() - Action handlers:
handleUpdatePolicy(),handleEnableFeature() - Data handlers:
handleIdleData(),handlePolicyAction()
TUI-Specific Pattern (BubbleTea)¶
All TUI Update() methods should follow this pattern:
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.WindowSizeMsg:
return m.handleWindowSize(msg)
case CustomDataMsg:
return m.handleCustomData(msg)
case tea.KeyMsg:
return m.handleKeyPress(msg) // Delegates to key-specific handlers
}
return m, nil
}
CLI-Specific Pattern¶
Large CLI command handlers should be split:
// Before: 200-line function with nested flag parsing
func (a *App) AMI(args []string) error {
// 200 lines of flag parsing and logic
}
// After: Clean dispatcher with focused handlers
func (a *App) AMI(args []string) error {
if len(args) == 0 {
return a.showAMIHelp()
}
switch args[0] {
case "list":
return a.handleAMIList(args[1:])
case "validate":
return a.handleAMIValidate(args[1:])
case "cleanup":
return a.handleAMICleanup(args[1:])
default:
return fmt.Errorf("unknown AMI command: %s", args[0])
}
}
Real Example from CloudWorkstation¶
File: internal/tui/models/idle.go Before: IdleModel.Update() - Complexity 37 After: Extracted 14 handler methods - Complexity <15
See the actual implementation for the pattern in action.
Additional Best Practices¶
1. Early Returns¶
Use early returns to reduce nesting:
// Good
func process(item *Item) error {
if item == nil {
return errors.New("nil item")
}
if !item.IsValid() {
return errors.New("invalid item")
}
// Main logic here
return nil
}
2. Guard Clauses¶
Extract complex conditions into named functions:
// Before
if len(items) > 0 && items[0].Type == "special" && items[0].Status == "active" {
// do something
}
// After
if hasActiveSpecialItem(items) {
// do something
}
func hasActiveSpecialItem(items []Item) bool {
return len(items) > 0 &&
items[0].Type == "special" &&
items[0].Status == "active"
}
3. Table-Driven Logic¶
Replace long if/else chains with maps:
// Before
func getAction(key string) string {
if key == "a" { return "add" }
if key == "d" { return "delete" }
if key == "u" { return "update" }
// ... 20 more
}
// After
var keyActions = map[string]string{
"a": "add",
"d": "delete",
"u": "update",
// ...
}
func getAction(key string) string {
return keyActions[key]
}
Automated Checking¶
Run gocyclo -over 15 . before committing to catch complexity issues early.
Add to CI/CD pipeline:
Summary¶
- Target: All functions < 15 cyclomatic complexity
- Method: Extract helper functions for nested logic
- Pattern: One switch level max, delegate to focused handlers
- Result: A+ Go Report Card grade, maintainable code