Публичный API Go-пакета
Зачем это на интервью
Публичный пакет — это долгосрочный контракт для кода, который вы не контролируете. На интервью важно показать, что вы ограничиваете поверхность API, делаете нормальный путь простым и заранее думаете о nil, zero value, ошибках и миграции.
Минимум для E4
Экспортируйте только необходимое; имена должны объяснять назначение без чтения реализации. Экспортируемый идентификатор требует doc comment, начинающийся с его имени. Предпочитайте конкретные типы и функции-конструкторы, скрывайте поля, если инвариант нельзя сохранить при произвольной записи. Определите, что значит zero value и допустим ли nil.
package retry
import (
"context"
"errors"
"fmt"
)
// Do calls fn at most Attempts times until it succeeds or ctx is canceled.
type Doer struct {
Attempts int
}
// Do runs fn and returns its last error. A non-positive Attempts means one call.
func (d Doer) Do(ctx context.Context, fn func(context.Context) error) error {
if fn == nil {
return errors.New("retry: nil function")
}
attempts := d.Attempts
if attempts <= 0 {
attempts = 1
}
var err error
for i := 0; i < attempts; i++ {
if err = ctx.Err(); err != nil {
return err
}
if err = fn(ctx); err == nil {
return nil
}
}
return fmt.Errorf("retry after %d attempts: %w", attempts, err)
}Здесь zero value Doer{} осмыслен и безопасен. Do не принимает nil callback молча, сохраняет отмену context и документирует число попыток. Решение о retry по классу ошибки — отдельный контракт, который нужно добавить явно, а не угадывать.
Углубление для E5/Senior
Проектируйте API для эволюции: добавление поля в публичную struct может быть совместимо для именованных literals, но ломает неименованные; добавление метода к экспортируемому интерфейсу ломает реализации. Не возвращайте конкретные типы зависимости, если это навязывает клиентов. Для options используйте functional options лишь когда параметров действительно много и у них есть безопасные defaults; не заменяйте ими ясный конструктор из двух аргументов.
Применяйте semantic import versioning при breaking changes. До major upgrade рассмотрите новый метод/тип, deprecated-обёртку и период миграции. Документируйте concurrency safety, ownership io.Reader/io.Closer, допустимость повторного вызова и поведение при partial failure.
Ключевые понятия
- Surface area — все экспортируемые имена; чем она меньше, тем проще поддерживать контракт.
- Zero-value usability — нулевое значение готово к полезной, безопасной работе или явно документировано как непригодное.
- Constructor invariant — свойство, которое можно обеспечить только при создании через конструктор.
- Functional option — функция настройки; удобна для редких опций с defaults, но усложняет discoverability и валидацию.
- Semantic import versioning — несовместимая major-версия использует путь модуля
/vNдляN >= 2.
Типовые вопросы
- Нужно ли всегда делать конструктор?
- Нет: если zero value полезен и нет инвариантов, достаточно типа; конструктор нужен для обязательных зависимостей/валидации.
- Почему не стоит экспортировать поля?
- Клиенты получают возможность обходить инварианты, а изменение представления становится breaking change.
- Как документировать nil?
- Явно: допускается ли
nil, что он означает и какая ошибка/паника будет при недопустимости.
- Явно: допускается ли
- Почему нельзя добавить метод к публичному интерфейсу?
- Все внешние реализации перестанут компилироваться; лучше вводить новый меньший interface или адаптер.
- Когда использовать functional options?
- Для нескольких независимых необязательных параметров с разумными defaults; обязательные зависимости лучше оставить явными аргументами.
Практика
- Спроектируйте клиент с обязательным
baseURL, timeout по умолчанию и документированным nil-контрактом. - Добавьте tests для zero value, nil callback, отменённого context и последней ошибки.
- Найдите экспортируемое поле, замените его методом/конструктором и опишите миграцию клиента.
Критерии готовности: все export comments проходят golint-эквивалент в проекте, zero value или обязательность конструктора документированы, новые ошибки классифицируемы, а изменение не ломает API без версии или адаптера.
Частые ошибки и ловушки
- Экспортировать типы и поля «на всякий случай».
- Делать
nilнеявным отключателем без документации. - Добавлять option после начала работы объекта, хотя она должна быть immutable.
- Использовать
panicдля ошибки входных данных библиотеки вместо возврата error.
Связанные темы
Go-код на интервью · Контракты и mockability · Совместимость API
Источники
- Effective Go: Names — проверено 2026-10-02.
- Go modules: Semantic Import Versioning — проверено 2026-10-02.
- Go package documentation guide — проверено 2026-10-02.