Язык 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: что изменилось