Add them to a project as
curl --silent https://gist.githubusercontent.com/suhlig/a96c5bfb22170c5b1a27a724e9621e02/raw/CONVENTIONS.md --output AGENTS.md---conn, err := establishDatabaseConnection()
if err != nil {
return fmt.Errorf("could not establish database connection: %w", err)
}
err = ensureSchemaIsAtLatestVersion(conn)
if err != nil {
return fmt.Errorf("migrating the schema to the lastest version failed: %w", err)
}
return thingcontext.Context as first parameter*sql.DB referenceCreate*, Get*, Update*, Delete*type Repository struct {
db *sql.DB
}
func NewRepository(db *sql.DB) *Repository {
return &Repository{db: db}
}
func (r *Repository) CreateAccount(ctx context.Context, account *caterbill.Account) error {
// Implementation
}fmt.Errorf("creating account: %w", err)func (r *Repository) CreateVenue(ctx context.Context, venue *caterbill.Venue) error {
res, err := r.db.ExecContext(ctx, query, args...)
if err != nil {
return fmt.Errorf("executing insert: %w", err)
}
id, err := res.LastInsertId()
if err != nil {
return fmt.Errorf("getting last insert id: %w", err)
}
venue.ID = id
return nil
}// Composite types using embedding
type GuestWithAccount struct {
caterbill.Guest
caterbill.Account
}
type AccountWithGuests struct {
caterbill.Account
Guests []GuestWithPreferredVenue
}models.go file for a package only if there are a lot of public models, and they would be scattered across many places otherwise.New* (e.g., NewRepository, NewInvoiceBuilder) if the type is non-trivial.func NewInvoiceBuilder(repository *Repository) *InvoiceBuilder {
return &InvoiceBuilder{
repository: repository,
clock: time.Now,
taxRate: 0.07,
}
}ExecContext for INSERT/UPDATE/DELETEQueryContext for SELECTres, err := r.db.ExecContext(ctx, `
INSERT INTO
venues (name, street, postal_code, city)
VALUES
(?, ?, ?, ?)
`,
venue.Name,
venue.Street,
venue.PostalCode,
venue.City,
)slog with JSON-formatted log linesCreate*, Get*, Update*, Delete*New*Is*, Has*Get prefix-er suffix when possible (e.g., Mailer)// CreateAccount creates a new account in the database.
func (r *Repository) CreateAccount(ctx context.Context, account *caterbill.Account) error {// InvoiceBuilder constructs invoices from consumption data.
type InvoiceBuilder struct {
repository *Repository
// clock holds the way to get the current date
clock func() time.Time
// taxRate is static so far. Store that with the product if that ever changes.
taxRate float64 // e.g. 0.07 for 7 %
}func (s *server) CreateAccount(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
if s.logger != nil {
s.logger.ErrorContext(r.Context(), "creating the account failed", "error", err)
}
http.Error(w, "unable to create account", http.StatusBadRequest)
return
}
// Validate and process input
// ...
// Render response
}fmt.Errorf("context: %w", err)) for stack traces.err := doSomething()
if err != nil {
return fmt.Errorf("something went wrong: %w", err)
}As soon as a main program has a second point where it would exit, use the following structure:
package main
import (
"fmt"
"os"
)
func main() {
err := mainE(context.Background())
if err != nil {
fmt.Fprintf(os.Stderr, "Error %s\n", err)
os.Exit(1)
}
}
func mainE(ctx context.Context) error {
// Do what ever is to be done
res, err := r.db.ExecContext(ctx, query, args...)
if err != nil {
return fmt.Errorf("executing insert: %w", err)
}
// On success, return no error.
return nil
}When a main program needs subcommands or more that two or three simple flags, switch to using github.com/spf13/cobra.
Use Ginkgo/Gomega for all tests
Test files should be in separate test packages (e.g., package billing_test)
Import Ginkgo/Gomega with dot imports:
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"Describe, Context, ItBeforeEach and JustBeforeEach for setupAfterEach for cleanup when neededBeforeEach,JustBeforeEach,It statements.Context describes a certain "state of the world" and should have its own variables as the encapsulation of that state.Expect should be in an Itvar _ = Describe("Invoice", func() {
var (
invoice billing.Invoice
invoiceBuilder *billing.InvoiceBuilder
repository *backend.Repository
)
BeforeEach(func(ctx SpecContext) {
// Setup code
})
Context("with daily consumption", func() {
BeforeEach(func(ctx SpecContext) {
// More specific setup
})
It("calculates the correct total", func() {
Expect(invoice.TotalAmount).To(Equal("10,00 €"))
})
It("includes the consumption in line items", func() {
Expect(invoice.LineItems).To(HaveLen(1))
})
})
})Expect(err).ToNot(HaveOccurred()) for error checkingExpect(x).To(Equal(y)) for equalityExpect(slice).To(HaveLen(n)) for length checksAlways add a space after {{ and before }} and all other actions including {{ range }} and {{ end }}; but not for {{/* a comments */}} and {{- trimmed white spacw -}}.
Do NOT do this:
{{if .ActiveTrip}}
{{template "active-trip.html" .}}
{{else}}
{{template "start-view.html" .}}
{{end}}Instead, do this:
{{ if .ActiveTrip }}
{{ template "active-trip.html" . }}
{{ else }}
{{ template "start-view.html" . }}
{{ end }}Apply the conventions listed in the YAML section of this document
Write YAML without newlines between list members:
Avoid this style:
- name: something is available
foo: some
- name: something else is restarted
bar: otherPrefer this style:
- name: something is available
foo: some
- name: something else is restarted
bar: otherWrite task names in the present tense, so that they describe the state of the system after the task is run.
Avoid this style:
- name: Create something
foo: somePrefer this style:
- name: something is available
foo: some