Чтение Go-кода и рефакторинг без изменения поведения

Зачем это на интервью

В задаче на чтение кода проверяют, видите ли вы реальное поведение, а не только «неудачный стиль». Рефакторинг ценен, если сохраняет контракт: результаты, ошибки, порядок побочных эффектов, отмену и совместимость.

Минимум для E4

Начните с экспортируемой функции и ответьте: какие входы допустимы, какой результат обещан, какие зависимости вызываются, какие ошибки возвращаются и кто освобождает ресурс. Затем пройдите все ветви, особенно ранние return, defer, пустые и nil значения. Зафиксируйте поведение тестом до структурного изменения.

Пример: извлечение повторяющейся логики не меняет порядок проверки и сохраняет исходную ошибку.

package user
 
import (
	"context"
	"errors"
	"fmt"
)
 
var ErrNotFound = errors.New("user not found")
 
type Repository interface {
	Find(context.Context, string) (string, error)
}
 
func LoadName(ctx context.Context, repo Repository, id string) (string, error) {
	if id == "" {
		return "", fmt.Errorf("load user: empty id")
	}
	name, err := repo.Find(ctx, id)
	if err != nil {
		return "", fmt.Errorf("load user %q: %w", id, err)
	}
	return name, nil
}

Тест должен проверять errors.Is(err, ErrNotFound), а не точную строку. При извлечении helper нельзя заменить %w на %v: иначе классификация перестанет работать.

Углубление для E5/Senior

Опишите инварианты и границы изменения явно: например, «зависимость не вызывается при пустом id», «контекст передаётся без замены», «ошибка ErrNotFound распознаётся вызывающим кодом». Для рискованного рефакторинга используйте characterization tests, feature flag или параллельное сравнение результатов. Разделяйте рефакторинг и изменение поведения на разные commits/PR: это упрощает ревью, rollback и поиск регрессии.

Проверяйте скрытые контракты: порядок SQL-запросов, идемпотентность retry, порядок закрытия ресурсов, timeouts, метрики и serialized JSON. Производительность меняйте только после профиля и benchmark.

Ключевые понятия

  • Контракт поведения — наблюдаемые входы, выходы, ошибки и побочные эффекты.
  • Инвариант — свойство, сохраняемое во всех допустимых путях выполнения.
  • Characterization test — тест существующего поведения, включая иногда неудобное, до рефакторинга.
  • Малый шаг — компилируемое изменение с понятной причиной и отдельной проверкой.
  • Semantic refactor — не меняет контракт; переименование публичного символа без периода совместимости уже меняет API.

Типовые вопросы

  1. С чего начать чтение незнакомой функции?
    • С её контракта и вызовов: входы, результат, ошибки, побочные эффекты и ownership ресурсов; затем пройти все ветви.
  2. Как доказать, что рефакторинг не поменял поведение?
    • Добавить tests на контракт до изменения, делать маленькие шаги, запускать unit/integration tests и нужные статические проверки.
  3. Почему нельзя сразу «улучшить» ошибку?
    • Её тип, errors.Is, текст, HTTP mapping или метрика могут быть внешним контрактом; сначала нужно выяснить потребителей.
  4. Что проверять вокруг defer?
    • Что defer зарегистрирован после успешного получения ресурса, порядок LIFO корректен, а ошибка Close не потеряна там, где она значима.
  5. Когда переписывание оправдано?
    • Когда есть измеримая проблема или изменение требований и есть план миграции; не потому, что новый стиль субъективно нравится.

Практика

  • Возьмите функцию с тремя ветками ошибок и напишите characterization tests для каждой.
  • Извлеките validation/helper без изменения exported API и покажите git diff только с рефакторингом.
  • Добавьте тест, что repository не вызывается для пустого идентификатора.

Критерии готовности: тесты фиксируют успех, все ошибки и отсутствие лишнего вызова; после изменений проходят gofmt, go vet ./..., go test ./... и при наличии параллелизма go test -race ./....

Частые ошибки и ловушки

  • Менять код до понимания вызывающих сторон и контрактных тестов.
  • Сливать форматирование, переименование и изменение логики в один diff.
  • Терять context.Context, оборачивать его в context.Background() или игнорировать ctx.Err().
  • Удалять seemingly redundant проверку, не доказав, что она не защищает старого клиента или corrupt data.

Связанные темы

Go-код на интервью · Ошибки и observability · Тестирование

Источники