go
// GOOD: Handle or propagate errors explicitly
result, err := doSomething()
if err != nil {
return fmt.Errorf("doSomething failed: %w", err)
}
// GOOD: Sentinel errors for expected conditions
var ErrNotFound = errors.New("not found")
if errors.Is(err, ErrNotFound) {
// handle expected case
}
// GOOD: Custom error types for rich context
type ScanError struct {
Host string
Port int
Err error
}
func (e *ScanError) Error() string {
return fmt.Sprintf("scan %s:%d: %v", e.Host, e.Port, e.Err)
}
func (e *ScanError) Unwrap() error { return e.Err }
Key principles:
- •Wrap errors with
fmt.Errorf("context: %w", err)for stack context - •Use
errors.Is()anderrors.As()for error inspection - •Define sentinel errors at package level for expected conditions
- •Use custom error types when callers need structured error data </idiom>
go
// GOOD: Small, focused interface defined by consumer
type HostResolver interface {
Resolve(hostname string) ([]net.IP, error)
}
// Consumer accepts the interface
func ScanHosts(resolver HostResolver, hosts []string) ([]Result, error) {
// ...
}
go
// sync.Mutex zero value is unlocked - ready to use var mu sync.Mutex // bytes.Buffer zero value is empty buffer - ready to use var buf bytes.Buffer
<module_structure> Standard Go project layout for CLI tools:
text
project/ ├── cmd/ │ └── toolname/ │ └── main.go # Entry point, flag parsing, minimal logic ├── internal/ │ ├── scan/ # Core scanning logic │ │ ├── scanner.go │ │ └── scanner_test.go │ ├── report/ # Output formatting │ │ ├── report.go │ │ └── report_test.go │ └── config/ # Configuration handling │ └── config.go ├── go.mod ├── go.sum ├── Makefile └── README.md
Key principles:
- •
cmd/contains minimal entry points that wire dependencies together - •
internal/for packages private to this module - •
pkg/only when explicitly designing a public API - •Keep
main.gothin -- parse flags, create dependencies, call intointernal/ - •One package per logical concern </module_structure>
code
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParsePort(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ParsePort(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("ParsePort(%q) = %v, want %v", tt.input, got, tt.want)
}
})
}
}
code
<!-- markdownlint-enable MD040 MD046 -->
</pattern>
<pattern name="benchmark_tests">
```go
func BenchmarkScanPort(b *testing.B) {
scanner := NewScanner(DefaultConfig())
target := "127.0.0.1"
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = scanner.ScanPort(target, 80)
}
}
Run benchmarks: go test -bench=. -benchmem ./...
</pattern>
- •Use
t.Helper()for cleaner error messages - •Use
t.Cleanup()or return cleanup functions - •Use
t.Parallel()for independent tests - •Use
testdata/directory for test fixtures </pattern>
func (m *mockResolver) Resolve(host string) ([]net.IP, error) { return m.resolveFunc(host) }
func TestScanWithResolver(t *testing.T) { mock := &mockResolver{ resolveFunc: func(host string) ([]net.IP, error) { return []net.IP{net.ParseIP("127.0.0.1")}, nil }, } // use mock in test }
code
<!-- markdownlint-enable MD040 MD046 -->
</pattern>
</testing>
<network_security_patterns>
Patterns common in network scanning and security tools:
<pattern name="concurrent_scanning">
```go
func ScanPorts(host string, ports []int, workers int) []Result {
results := make([]Result, 0, len(ports))
var mu sync.Mutex
var wg sync.WaitGroup
sem := make(chan struct{}, workers)
for _, port := range ports {
wg.Add(1)
sem <- struct{}{}
go func(p int) {
defer wg.Done()
defer func() { <-sem }()
result := scanPort(host, p)
mu.Lock()
results = append(results, result)
mu.Unlock()
}(port)
}
wg.Wait()
return results
}
code
conn, err := (&net.Dialer{}).DialContext(ctx, "tcp", target)
if err != nil {
return Result{}, err
}
defer conn.Close()
// ...
}
code
<!-- markdownlint-enable MD040 MD046 -->
</pattern>
<pattern name="input_validation">
```go
func ValidateTarget(target string) error {
if ip := net.ParseIP(target); ip != nil {
return nil // valid IP
}
if _, err := net.LookupHost(target); err != nil {
return fmt.Errorf("invalid target %q: %w", target, err)
}
return nil
}
func ValidatePortRange(start, end int) error {
if start < 1 || start > 65535 {
return fmt.Errorf("invalid start port: %d", start)
}
if end < start || end > 65535 {
return fmt.Errorf("invalid end port: %d", end)
}
return nil
}
</network_security_patterns>
<lint_pitfalls> Common golangci-lint failures to avoid proactively:
- •commentedOutCode (gocritic): Comments resembling code trigger this. Arithmetic-like comments in tests (e.g.,
// Weight(15) + Weight(35) = 50) are flagged. Use natural language instead:// Expected: OUI weight plus BRIDGE-MIB weight. - •prealloc: Preallocate slices when loop length is known:
make([]T, 0, len(source))notvar slice []Twith append in a loop. - •rangeValCopy (gocritic): Structs over 64 bytes copied per iteration. Use
for i := range sliceand access viaslice[i]. Replace ALL loop variable references in the body. - •httpNoBody (gocritic): Use
http.NoBodyinstead ofnilfor GET/HEAD/DELETE requests without a body. - •builtinShadow (gocritic): Never use Go builtins as parameter names (
new,make,len,copy,min,max,clear). Rename ton,count,limit,val, etc. - •nilerr: When a function receives a non-nil error and returns
nilas the error (encoding it into a result struct), the linter flags it. Return both result and wrapped error. - •bodyclose: Always close HTTP response bodies, including from
websocket.Dial()responses. </lint_pitfalls>
<success_criteria> Go code produced with this skill should:
- •Pass
go vet ./...with no warnings - •Pass
golangci-lint runwith standard linters - •Have test coverage for exported functions
- •Use
context.Contextfor cancellable operations - •Handle all errors explicitly
- •Follow standard project layout conventions </success_criteria>