· 7 мин 👁 1.4k Начинающий

Язык Go - геттеры, сеттеры, интерфейсы

Почему геттер Owner, а не GetOwner, откуда берутся Reader и Writer, и зачем Go сам расставляет точки с запятой.

именованиеинтерфейсыконвенциистиль
Содержание

Продолжаем разбирать соглашения об именовании в Go. Сегодня — три темы, которые часто удивляют тех, кто приходит из Java или C#.

Геттеры без Get

Go не генерирует геттеры и сеттеры автоматически — в отличие от некоторых языков с аннотациями или кодогенерацией. Написать их самому совершенно нормально, но есть одно соглашение: слово Get в геттер не входит.

Если у вас есть приватное поле owner, геттер называется Owner, а не GetOwner:

type Object struct {
    owner string // приватное поле
}

func (o *Object) Owner() string {      // геттер — просто Owner, не GetOwner
    return o.owner
}

func (o *Object) SetOwner(s string) {  // сеттер — SetOwner, всё логично
    o.owner = s
}

В использовании это читается естественно:

owner := obj.Owner()
if owner != user {
    obj.SetOwner(user)
}

Почему не GetOwner? Потому что в Go заглавная буква уже сигнализирует об экспорте — она и так отделяет публичный метод от приватного поля. Лишнее Get только добавляет шум.

Сравните с Java, где getOwner() — это стандарт. В Go контекст другой: само имя Owner() с большой буквы уже говорит «это публичный доступ». Дублировать смысл не нужно.

Интерфейсы с суффиксом -er

Если интерфейс содержит один метод, принято называть его по имени метода с суффиксом -er:

type Reader interface {
    Read(p []byte) (n int, err error)
}

type Writer interface {
    Write(p []byte) (n int, err error)
}

type Stringer interface {
    String() string
}

type Closer interface {
    Close() error
}

Логика простая: Reader — «тот, кто умеет читать». Writer — «тот, кто умеет писать». Агентное существительное.

Это не просто красота — такие интерфейсы встроены в стандартную библиотеку и используются повсюду. io.Reader, io.Writer, fmt.Stringer — если ваш тип реализует метод с такой же сигнатурой, он автоматически совместим.

Отсюда важное следствие: не переименовывайте стандартные методы. Если ваш тип умеет возвращать строковое представление — называйте метод String(), а не ToString(). Тогда fmt.Println сам его подхватит:

type Point struct {
    X, Y int
}

func (p Point) String() string {
    return fmt.Sprintf("(%d, %d)", p.X, p.Y)
}

p := Point{3, 4}
fmt.Println(p) // выведет: (3, 4)

Назови метод ToString — и это не сработает. Go ищет именно String().

MixedCaps вместо underscores

Многословные имена в Go пишутся через MixedCaps (экспортируемые) или mixedCaps (приватные). Никаких snake_case и SCREAMING_CONSTANTS:

// хорошо
maxRetryCount := 3
type HttpResponseWriter interface { ... }
const DefaultTimeout = 30 * time.Second

// не по-гошному
// max_retry_count := 3
// type Http_Response_Writer interface { ... }
// const DEFAULT_TIMEOUT = 30

Исключение — аббревиатуры. Они пишутся либо полностью заглавными, либо полностью строчными, но не смешиваются:

// хорошо
type HTTPClient struct { ... }
type xmlParser struct { ... }
var userID int

// плохо
// type HttpClient struct { ... }
// var userId int

Точки с запятой, которых не видно

Формальная грамматика Go использует точки с запятой как разделители — как в C. Но в исходном коде вы их почти не увидите. Лексер расставляет их автоматически.

Правило простое: если строка заканчивается на идентификатор, литерал или один из токенов:

break  continue  fallthrough  return  ++  --  )  }

— лексер вставляет ; перед переносом строки. То есть «если это может быть концом выражения — ставим точку с запятой».

На практике вы это почти не замечаете. Точки с запятой нужны явно только в for (где несколько частей) и если хотите несколько инструкций на одной строке:

for i := 0; i < 10; i++ { ... }  // три части — нужны ;

x := 1; y := 2  // две инструкции в строке — нужна ;, но так не пишут

Почему это объясняет открывающую скобку

Помните из прошлой статьи — открывающая { всегда на той же строке? Теперь понятно почему. Это не просто вкусовщина, это следствие правила точек с запятой.

// правильно
if i < f() {
    g()
}

// не скомпилируется
if i < f()   // лексер вставит ; здесь — и if останется без тела
{
    g()
}

Лексер видит f() в конце строки — это ), которая входит в список токенов для автовставки. Вставляет ;. И if получается без блока — ошибка компиляции.

gofmt это исправит, но лучше сразу понимать механику.

Итоги

  • Геттер для поля owner — это Owner(), не GetOwner(). Сеттер — SetOwner()
  • Однометодные интерфейсы называются по методу с суффиксом -er: Reader, Writer, Stringer
  • Стандартные имена методов (String, Read, Write, Close) — переиспользуйте, не переименовывайте
  • Многословные имена — MixedCaps, никаких подчёркиваний
  • Точки с запятой лексер вставляет сам, отсюда — открывающая { всегда на той же строке

Следующий шаг: Управляющие конструкции в Go — if, for, switch и select: что изменилось