Use Docker to develop and test the application. Do not rely on locally installed Go toolchains.
- Go version: 1.24 (latest stable)
- Dependency management: Go modules (
go mod)
# Build the image
docker build -t mbproxy .
# Run the proxy
docker run --rm -e MODBUS_UPSTREAM=192.168.1.100:502 mbproxy
# Run with debug logging
docker run --rm -e MODBUS_UPSTREAM=192.168.1.100:502 -e LOG_LEVEL=DEBUG mbproxy# Run all tests with race detector (uses full golang image)
docker build --target test .
# Or run tests interactively (without race detector in alpine)
docker run --rm -v $(pwd):/app -w /app golang:1.24 go test -v ./...
# With race detector (requires full golang image, not alpine)
docker run --rm -v $(pwd):/app -w /app golang:1.24 go test -v -race ./...Note: Race detector requires CGO, which is not available in alpine images. The Dockerfile test stage uses the full golang image for this reason.
Run the full CI suite locally before committing:
# Run all CI checks (format, vet, test with race detector)
docker run --rm -v $(pwd):/app -w /app golang:1.24 sh -c "go fmt ./... && go vet ./... && go test -v -race ./..."The CI pipeline runs:
- Format check:
go fmt ./...followed bygit diff --exit-code(fails if formatting changes files) - Vet:
go vet ./... - Test:
go test -v -race ./...
- Run
go fmton all code before committing - Run
go vetto catch common issues - Keep functions small and focused
- Use meaningful variable names
- Add comments for non-obvious logic
- Use present tense: "add feature" not "added feature"
- Keep subject line under 50 characters
- Format:
<type>: <description> - Types:
feat,fix,refactor,test,docs,chore - Always commit topic by topic (one logical change per commit)
- Never push automatically; always wait for explicit user approval
Examples:
feat: add request coalescing for cache misses
fix: handle upstream connection timeout
refactor: extract cache logic to separate package
Set LOG_LEVEL=DEBUG to enable verbose logging:
LOG_LEVEL=DEBUG docker run --rm -e MODBUS_UPSTREAM=... mbproxyDebug logs include:
- Cache hits/misses
- Upstream request timing
- Connection events
- Request coalescing activity
- Always handle errors; never discard with
_ - Return errors instead of panicking for normal error handling
- Error strings should be lowercase, no punctuation:
fmt.Errorf("connection failed")not"Connection failed." - Indent error handling, keep happy path at minimal indentation:
// Good if err != nil { return err } // normal code // Bad if err != nil { // error handling } else { // normal code }
- Variable names: short for local scope (
cnotlineCount), descriptive for wider scope - Receiver names: 1-2 letter abbreviation (
cforClient), neverthis/self/me - Initialisms:
ID,URL,HTTPstay uppercase (userIDnotuserId)
- Document when and whether goroutines exit
- Ensure goroutines don't leak by blocking on channels
- Prefer synchronous functions; let callers add concurrency if needed
- Group standard library first, blank line, then external packages
- Use
goimportsto auto-format
- Prefer
var t []stringovert := []string{}for empty slices - Define interfaces where used, not where implemented