Never guess. Ground every claim in actual files, docs, or command output. If you're unsure about a dependency's size, run go get and check. If you're unsure about an API, read the source or docs. If you're unsure about behavior, write a test. State what you verified and how — don't speculate.
Whenever possible, apply TDD. Write the test first, watch it fail, write the code, watch the test pass! This includes unit tests AND e2e tests!
You must never ignore an error. If this is a production error, log it. If it is a test error, assert/require on it.
// BAD
_ = someFunction()
// GOOD
err := someFunction()
if err != nil {
// ...
}Tests will use the testify library, with assert and require. Do not use t. functions to do assertion.
// BAD
if str == "" {
t.Fatal("str is empty")
}
// GOOD
require.NotEmpty(t, str)Use squirrel for all queries. Never write raw SQL strings in Go code.
// BAD
db.conn.QueryContext(ctx, "SELECT id, name FROM blogs WHERE id = ?", id)
// GOOD
sq.Select("id", "name").From("blogs").Where(sq.Eq{"id": id}).RunWith(db.conn).QueryRowContext(ctx)Schema changes go in migration files under internal/storage/migrations/ using golang-migrate. Migrations are .sql files embedded via //go:embed at compile time.
When we have to pick between the right way or the easy way, we always pick the right way. Never use //nolint directives to silence linters — fix the actual issue instead.
client := &http.Client{Timeout: timeout}
// BAD
response, err := client.Get(blogURL)
// GOOD
req, err := http.NewRequestWithContext(ctx, http.MethodGet, blogURL, nil)
// handle error here
response, err := client.Do(req)- Ensure your files are correctly formatted by running
golangci-lint. - Ensure the tests are ok by running
gotestsum