Coding conventions and best practices, primarily Go-flavored. Based on review feedback and Uber’s Go style guide.
Naming
- Short variable names (
b,c) are fine when the scope is narrow. Loop iteration variables should be short — see Go Code Review Comments: Variable Names. - No snake_case in Go. Use camelCase or PascalCase.
- Receiver names should be one or two letter abbreviations (e.g.
tforTeam). - Struct field names shouldn’t repeat the type. If the struct is
Team, useIDnotTeamID. - Initialisms and acronyms (
URL,NATO,ID) should have consistent casing — all caps or all lower, not mixed. isXXprefix implies a bool return. For non-bool checks, usevalidateXXinstead.- Remove redundant type identifiers in variable/field names.
Error Handling
- Handle errors immediately, not in batches. The reason try-catch exists in exception-based systems is that errors should be dealt with on the spot. If the error is in a simple internal function, handling one layer up is acceptable.
- When calling an external service, wrap the error immediately. Internal upper layers can just return the error as-is.
"failed to"is unnecessary in error context — it’s already obvious something failed, and you’ll end up with a chain of"failed to"when wrapping multiple errors.- Use
%wfor error propagation so callers can unwrap:var ErrNotFound = errors.New("not found") fmt.Errorf("user %s is not found in organization: %w", login, ErrNotFound) fmt.Errorfvserrors.New—fmt.Errorfis useful when wrapping a downstream error with%w. Otherwise,errors.Newis enough.- Tell the user what to do in error messages. E.g., if archiving a repository fails, tell them they must resolve secret scanning alerts first.
- Always validate input arguments before using them. An invalid argument shouldn’t crash the app — an attacker can exploit this to break the system.
Function Design
- Avoid side effects. Functions should not mutate arguments passed by reference unless the function name makes this explicit. Code with many side effects becomes harder to maintain as complexity grows.
- Avoid edit/update arguments and pass-by-reference as much as possible. Prefer returning new values.
- Break up large API methods into smaller internal functions that distribute the load.
- For complex responses, build the result at the end of the function. Define sub-types (error slices, nested structs) at the top, populate them through the function, and assemble the final response at the end:
var errors []*Error // ... processing ... return &Response{ Field1: value1, Errors: errors, } - Reduce redundant references. If you’re accessing
fileErrors.Messagerepeatedly, pull it into a local variable likevar messages []string.
Go-Specific
- Use nil slices for initialization instead of empty slices (
var s []stringnots := []string{}). - Table-driven tests: prefer
map[string]struct{}for test data because:- Reduces a field in the struct.
- Map iteration order is non-deterministic in Go, which avoids implicit dependencies between test cases based on ordering.
Comments
- Always leave comments when using implicit specifications. Explain why something works, not just what it does.
// Repository name is always at index 1 because file paths // follow the structure: org/repo/... repositoryName := strings.Split(file, "/")[1]
Service Architecture
- Service boundaries create interdependencies if not carefully considered. In microservices this is fatal. Even in a modular monolith, prefer some duplication over inter-service communication.
- Internal layers should have no knowledge of external layers. No inter-service dependencies within internal layers either.
- PubSub event payloads should be minimal. Only include data that doesn’t need verification and won’t have breaking effects on downstream subscribers. For example, when syncing a repository, send just the repo name in the event — let the subscriber fetch the full spec internally. If you include the spec in the request payload, an attacker could send a malicious spec and cause unintended changes.