go-interfaces — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited go-interfaces (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Interfaces in Go are consumer contracts, not implementation hierarchies. They should be small, discovered late, and owned by the package that uses them — not the package that satisfies them.
var _ I = (*T)(nil) when the relationship must not break silently.| Situation | Verdict |
|---|---|
| Single implementation, no tests need to swap it | No interface. Use the concrete type. |
| Second implementation appears (or is imminent) | Extract an interface in the consumer package. |
| Test needs to fake an external dependency | Define a small interface in the consumer; pass a fake. |
You want to expose optional behaviour (Flusher, ReaderFrom) | Define a tiny interface; check with _, ok := v.(Iface). |
| You want a stable plugin/SPI boundary | Yes, but keep it minimal and version it explicitly. |
Read references/consumer-owned-interfaces.md when migrating a producer-defined interface back to the consumer, or when designing a new package boundary.
// Good — consumer defines what it needs
package notify
type Sender interface { Send(to, body string) error }
type Service struct{ s Sender }
func NewService(s Sender) *Service { return &Service{s: s} }
// Good — producer returns a concrete type
package email
type Client struct{ /* ... */ }
func New(cfg Config) *Client { /* ... */ }
func (c *Client) Send(to, body string) error { /* ... */ }// Bad — producer defines and returns its own interface,
// forcing every consumer to depend on email.Sender.
func New(cfg Config) Sender { return &client{...} }The exception is "expose an interface, hide the implementation": when a type has no exported methods beyond what the interface promises, returning the interface (func NewHash() hash.Hash32) is fine.
Standard library interfaces are the model: io.Reader, io.Writer, io.Closer, fmt.Stringer, error — one or two methods each. Compose larger contracts:
type ReadWriteCloser interface { io.Reader; io.Writer; io.Closer }If you find yourself writing a five-method interface, split it until each piece has a single reason to exist.
var _ io.ReadWriter = (*MyBuffer)(nil)Use when the type must satisfy an interface for correctness (custom JSON marshalling, http.Handler) and no other static use already enforces it. Don't add one for every interface.
Always use the comma-ok form. Type switches re-declare the variable; cases with multiple types fall back to the interface type. Use optional-behaviour assertions to enhance a path without requiring the capability:
s, ok := v.(string) // comma-ok
switch x := v.(type) { case string: /* ... */ } // type switch
if f, ok := w.(http.Flusher); ok { f.Flush() } // optional behaviourStruct embedding promotes the inner type's methods and fields to the outer type. Use it deliberately — every promoted method becomes part of your public API.
type Server struct {
*slog.Logger // exposes Info/Warn/Error on Server
addr string
}| Use embedding when | Use a named field when |
|---|---|
| You want the outer type to be an enhanced version of the inner | You only need the inner type internally |
| The full inner API should be promoted | You want to delegate explicitly to a subset |
Avoid embedding in exported types unless the promotion is the whole point. The inner type's method set is locked in once published.
Read references/embedding-and-receivers.md when designing struct embedding, overriding promoted methods, resolving name conflicts, or choosing between pointer and value receivers.
Constructors take interfaces; tests pass fakes. No DI container required.
type UserStore interface {
FindByID(ctx context.Context, id string) (*User, error)
}
type UserService struct{ store UserStore }
func NewUserService(s UserStore) *UserService { return &UserService{store: s} }The UserStore interface lives in the package that defines UserService. The concrete *pgUserStore lives in a database package and doesn't know UserService exists.
Structs that must not be copied (those holding a mutex, internal pointers, or a sync.WaitGroup) should embed a noCopy sentinel so go vet catches the mistake:
type noCopy struct{}
func (*noCopy) Lock() {}
func (*noCopy) Unlock() {}
type ConnPool struct {
_ noCopy
mu sync.Mutex
/* ... */
}Pass these by pointer (func process(p *ConnPool)), never by value.
Don't reach for `noCopy` reflexively. Plain value types (config structs, request DTOs, immutable snapshots) should be copyable — adding noCopy to them locks consumers into pointer-only APIs for no gain. The rule of thumb: if the struct owns a sync.Mutex, sync.WaitGroup, sync.Pool, internal chan, or a pointer that must stay unique (file handle, OS resource), embed noCopy. Otherwise leave it copyable.
| Anti-pattern | Why it hurts | Do this instead |
|---|---|---|
| Producer-defined interface returned from constructor | Couples every consumer to the producer's package | Return the concrete type; let consumers define interfaces |
| Five-plus method interface | Hard to implement, hard to mock | Split into small interfaces; compose |
| Premature interface with one implementation | Indirection without value | Start concrete; extract when a second consumer appears |
v := x.(T) without ok | Panics on mismatch | v, ok := x.(T) |
| Embedding a concrete type into an exported struct | Inner API leaks into your public surface | Use a named, unexported field |
| Mixing pointer and value receivers on one type | (*T) and T have different method sets — confusing satisfaction errors | Pick one receiver style for the whole type |
ToString() / ReadData() instead of canonical names | Breaks fmt.Stringer / io.Reader discovery | Honour String() / Read(p []byte) (int, error) |
Returning *MyErr instead of error | Typed-nil trap; err != nil is true even when "no error" | Return the interface type |
Before finishing an interface change:
var _ I = (*T)(nil) exists where silent regressions would hurt~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.