closurecomplexity
About 378 words
Detects complex closures that should be extracted to named functions.
Category
Clean Code
What It Checks
This analyzer detects anonymous functions (closures) that are too complex.
Thresholds:
- Maximum statements: 15
- Maximum nesting depth: 2
- Maximum captured variables: 5
Blocks are not statements of their own, so an if with one statement in its body counts as two. Nesting counts if, for, switch and select levels, including the bodies of case clauses and every branch of an else if chain. A captured variable is a local variable or parameter of an enclosing function or closure; package-level variables and struct fields are not captures.
Exempt Closures:
- Deferred closures (
defer func() {...}()) - Goroutine closures (
go func() {...}()) - Closures returned from functions (handler factory pattern)
- Cobra command handlers (
RunE,Run,PreRunE, etc.) - HTTP handler fields
- Visitor pattern callbacks (
Inspect,VisitAll,Walk,WalkDir, etc.) - Test files (closures in
*_test.gofiles)
Why It Matters
Complex closures:
- Are hard to test in isolation
- Reduce code readability
- Hide important logic
- Make debugging difficult
Examples
Bad: Complex Closure
func ProcessItems(items []Item) error {
return withTransaction(func(tx *sql.Tx) error {
// 50+ lines of complex logic
for _, item := range items {
if item.Type == "special" {
result, err := tx.Query("SELECT ...")
if err != nil {
return err
}
// ... more complex processing ...
for result.Next() {
// ... even more logic ...
}
}
}
return nil
})
}Good: Extract to Named Function
func ProcessItems(items []Item) error {
return withTransaction(func(tx *sql.Tx) error {
return processItemsInTx(tx, items)
})
}
func processItemsInTx(tx *sql.Tx, items []Item) error {
for _, item := range items {
if err := processSpecialItem(tx, item); err != nil {
return err
}
}
return nil
}
func processSpecialItem(tx *sql.Tx, item Item) error {
if item.Type != "special" {
return nil
}
// Clear, testable logic
result, err := tx.Query("SELECT ...")
if err != nil {
return err
}
defer result.Close()
// ...
return nil
}Acceptable: Simple Closures
// Simple closures are fine
sort.Slice(items, func(i, j int) bool {
return items[i].Name < items[j].Name
})
// Short handlers are fine
http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
})Configuration
# .golint-sl.yaml
analyzers:
closurecomplexity: true # enabled by defaultWhen to Disable
- Code with many simple callbacks
- Generated code
analyzers:
closurecomplexity: falseRelated Analyzers
- functionsize - Function length
- nestingdepth - Nesting limits
