Перейти к содержимому
ГоМастер

Поиск по урокам

Найдите урок, курс, термин или раздел

Урок 24 из 25 ~20 мин +50 XP

Правила и идиомы Go

Запрос «идиомы go» — это не про синтаксис, а про привычки: маленькие решения при именовании, обработке ошибок и устройстве типов, по которым Go-код узнают с первого экрана. Главный сборник этих привычек — документ Effective Go на go.dev/doc/effective_go: команда языка собрала там советы о том, как писать clear, idiomatic Go code, и за пятнадцать лет документ почти не изменился. Двадцать четвертый урок — сборный: каждую идиому вы уже встречали в деле — defer во втором уроке, select в шестом, ошибки как значения в пятнадцатом, время как значения в двадцать третьем — и теперь они сходятся в один чек-лист: именование MixedCaps, геттеры без Get, честный if err != nil, малые интерфейсы, пустая структура struct{}, владелец канала и gofmt. Весь ранимый код урока исполнен обоими движками песочницы и прогнан трижды — на [странице курса](/courses/go-advanced/) это самый практичный урок перед финалом.

Содержание урока · 14 разделов

Идиомы Go: привычки Effective Go#

Идиома — это решение, которое приняли за вас до того, как вы открыли редактор. В других языках споры об именовании и обработке ошибок длятся десятилетиями, потому что «правильно» каждый понимает по-своему. В Go позиция другая: большинство таких решений зафиксировано в документе Effective Go — «tips for writing clear, idiomatic Go code», сборнике советов, который лежит рядом со спецификацией языка. Именно поэтому код двух незнакомых Go-программистов так похож: они читали один и тот же документ, а компилятор и gofmt отсекают остальное.

Что важно понимать про статус этого документа. Во-первых, он не спецификация: нарушить идиому нельзя — компилятор примет и underscore_names, и GetOwner, и гигантские интерфейсы. Нарушение идиомы — социальное событие: ваш код станут читать медленнее, а на ревью попросят переделать. Во-вторых, документ честно стареет: он написан до дженериков (тринадцатый урок), до появления контекста как стандарта и до многих современных линтеров, и его до сих пор не переписали — базовые привычки там верны, а вот к рекомендациям «только каналы, никаких мьютексов» стоит относиться с оговорками, о которых поговорим ниже. В-третьих, Effective Go — не единственный источник: есть Go Code Review Comments, доклады Роба Пайка с Go Proverbs и исходники стандартной библиотеки — лучший образец идиоматичного кода.

Дорожная карта урока: именование (MixedCaps, геттеры без Get, короткие имена в узкой области, групповая форма и iota), ошибки как значения и честный if err != nil, малые интерфейсы, слоган «don't communicate by sharing memory», пустая структура struct{} и финал — простота против cleverness и gofmt. Каждая идиома — короткий блок с отсылкой к уроку курса, где она родилась: здесь ничего нового не изобретается, всё уже было, но по одному фрагменту за раз. Запрос «идиомы go» на самом деле ищет именно это: не новые возможности языка, а умение писать старыми так, чтобы ваш код выглядел как стандартная библиотека.

Именование: MixedCaps и честные слова#

Первая идиома Effective Go — про имена: «the convention in Go is to use MixedCaps or mixedCaps rather than underscores to write multiword names». Многословные имена пишутся горбиком: rateLimit, ParseDuration, upperLimit — но не rate_limit и не Rate_Limit. Подчёркивания в Go живут ровно в двух местах: в начале имени (неэкспортированные хелперы вида _test, пустой идентификатор _) и в именах файлов. В идентификаторах им места нет — и это не эстетика, а способ убрать целую войну стилей: camelCase, snake_case и PascalCase не сосуществуют, победил один горбик.

Регистр первой буквы в Go несёт СМЫСЛОВУЮ нагрузку, которой нет почти нигде: имя с заглавной — экспортированное (видно из других пакетов), со строчной — приватное для пакета. Четырнадцатый урок разбирал устройство пакетов — здесь важно следствие: когда вы пишете StatusOK вместо STATUS_OK, вы одновременно выбираете стиль и решаете, экспортировать ли константу. Пакеты именуются ещё строже — lower case, single-word: «there should be no need for underscores or mixedCaps» — strconv, strings, http. Аббревиатуры держат собственный регистр целиком: ParseURL, а не ParseUrl; тип URL в net/url, пакет utf8, метод ServeHTTP — посмотрите на любую страницу стандартной библиотеки, и паттерн увидите сами.

Вторая половина идиомы — о длине. Правило сообщества формулируется так: чем больше расстояние между объявлением имени и его использованием, тем длиннее должно быть имя. Пакетная константа живёт в сотнях файлов — ей нужно полное имя (StatusTooManyRequests, maxIdleConnsPerHost). Локальный счётчик живёт три строки — ему достаточно i, n, r: короткое имя в узкой области читается мгновенно, потому что всё, что о нём нужно знать, видно на экране. Ошибка новичка — наоборот: i := 0 в двухстрочном цикле никто не обидит, а вот промежуточная переменная данных пользователя с именем d в функции на сто строк заставит читателя листать назад. Имя — это документация с самым коротким сроком устаревания: оно либо честно описывает содержимое, либо врёт в каждой строке, где употреблено.

Геттеры без Get и перечисления на iota#

Effective Go говорит про геттеры дословно следующее: «it's neither idiomatic nor necessary to put Get into the getter's name. If you have a field called owner (lower case, unexported), the getter method should be called Owner (upper case, exported), not GetOwner». Логика проста: имя метода в Go читается как ответ на вопрос «что это или что с этим делать», и префикс Get — шум. Поле email — метод Email(); счётчик p.count — метод p.Count(). Сеттер — единственное исключение, где глагол нужен, чтобы отличить запись от чтения: u.SetEmail("oleg@example.com"). И то же правило продолжается в конвертерах: строковый метод типа называется String, а не ToString — «call your string-converter method String not ToString» — именно поэтому fmt.Stringer выглядит так лаконично, и именно с ним мы соберём малый интерфейс ниже.

Вторая привычка этого блока — групповая форма объявлений. Ключевые слова const и var в Go умеют объявлять СПИСКИ: константы статуса собираются в один const-блок, пакетные переменные — в один var-блок, и это не байка про компактность, а способ показать родство: то, что объявлено группой, читается как одна мысль. Импорты тоже группируются — стандартная библиотека первой группой, внешние пакеты второй (это уже правило gofmt-инструментов вроде goimports). Внутри const-группы живёт и iota — «enumerated constants are created using the iota enumerator», перечислитель, который в каждой следующей строке группы на единицу больше: Low = iota, за ним Mid и High получают 1 и 2 бесплатно. Iota — идиоматичная замена рукописным 0, 1, 2: новое перечисление вставляется в середину группы, и нумерация перестраивается сама.

Практический минимум блока: поле храните неэкспортированным, наружу выпускайте метод с именем поля; группу констант начинайте с имени с iota, если это перечисление; локальные имена — короткие, пакетные — длинные. Всё это проверяется в раннере ниже — он исполняется обоими движками, потому что в коде нет ничего экзотического: только fmt, strings и групповые объявления.

Именование: групповая форма и узкая область

Этот раннер исполняют ОБА движка: в коде только fmt, strings и объявления — ни одного нативного пакета, и выводы совпадают посимвольно (проба прогнана трижды). Именование здесь на каждом экране: StatusOK и StatusErr — экспортированные константы с заглавной (групповая форма), upperLimit — пакетная переменная в MixedCaps, n и name — короткие имена в узкой области цикла. Эксперименты: добавьте в группу const третью строку StatusTooBusy = 429 — групповая форма примет её без единого изменения остального кода; переименуйте upperLimit в upper_limit — программа продолжит работать, потому что подчёркивания легальны, это вопрос идиомы, а не синтаксиса, и именно поэтому за именами следят человек на ревью и линтер; поменяйте upperLimit на 3 — счётчик перепишется сам, а смысл короткого имени n останется прозрачным.

Вывод

Запустите код, чтобы увидеть вывод.

Проверьте себя: именование

В структуре есть неэкспортированное поле email. Как по идиоме Effective Go назвать метод его чтения?

Ошибки как значения: if err != nil честность#

Центральная идиома языка и главного его не-фичи: в Go нет исключений в привычном смысле — ошибки это ЗНАЧЕНИЯ. Функция, которая может не справиться, возвращает последний (или единственный) результат типа error — интерфейс с одним методом Error() string. Пятнадцатый урок разобрал всю механику: fmt.Errorf с глаголом %w для заворачивания, errors.Is и errors.As для проверки цепочки, ошибки-сентинеллы вида var ErrNotFound = errors.New(...). Здесь важна идиома верхнего уровня — КАК выглядит работа с этим значением: err := f(); if err != nil { return err }. Не try/catch вокруг блока, а проверка сразу после каждого вызова, где ошибка имеет смысл.

Effective Go объясняет, почему это красиво: «the code reads well if the successful flow of control runs down the page, eliminating error cases as they arise» — успешный путь идёт вниз по странице, а ошибки отсекаются guard-ветвями по ходу. Отсюда три честных правила. Первое: err != nil — это не формальность, а решение; печатать ошибку и продолжать с nil-результатом хуже, чем честно вернуть её наверх. Второе: игнорировать ошибку молча нельзя; единственная идиоматичная форма сознательного игнорирования — пустой идентификатор, _ = err или _, err := f(), — и он читается как «я знаю, что здесь может быть ошибка, и осознанно её выбрасываю». Компилятор не заставит вас проверять err — это идиома, а не синтаксис, поэтому сообщество написало линтеры (errcheck и компания), а код-ревью ловит пропуски. Третье: panic — не замена ошибке; паника для программиста, сломавшего контракт (нулевая ссылка, невозможное состояние), ошибка — для данных, которые не совпали (файл не найден, ответ не распарсился). Каскад guard-ветвей ниже показывает стиль целиком.

Пара (значение, error) — самый частый контракт в языке: os.Open, http.Get, json.Unmarshal, time.Parse — все возвращают результат и ошибку рядом. У идиомы есть честная цена: boilerplate. Она признана языковым комитетом — дженерики её не убрали, специальные синтаксисы (try, ?) отвергали по разу, потому что явность проверки — та самая «честность»: в месте вызова видно, ошибка обрабатывается, заворачивается или выбрасывается.

Каскад guard-ветвей: успешный путь вниз по странице

GO
package main

import (
        "errors"
        "fmt"
        "strings"
)

// Пароль проверяется guard-ветвями: ошибка — сразу return.
func check(pw string) error {
        if len(pw) < 8 {
                return errors.New("короче восьми символов")
        }
        if !strings.ContainsAny(pw, "0123456789") {
                return errors.New("нет цифры")
        }
        // Успешный путь добрался до конца без вложенности.
        return nil
}

func main() {
        // Три пароля: два отсекаются guard-ветвями, третий доходит до nil.
        for _, pw := range []string{"12345", "безцифрвовсё", "нормпароль12"} {
                err := check(pw)
                if err != nil {
                        fmt.Println("отказ:", err)
                        continue
                }
                fmt.Println("принят")
        }
}

Успешный путь читается сверху вниз: проверка длины, проверка цифр, return nil — и ни одной вложенной ветки else. Ошибки отсекаются по мере возникновения, ровно как советует Effective Go. В песочнице код не исполняется как ранимый (пары (string, error) в резервном движке живут только с групповой формой параметров), но в настоящем Go он печатает три строки — «отказ: короче восьми символов», «отказ: нет цифры», «принят» — и нативная проба урока подтвердила это трижды.

Ошибки как значения: контракт функции

Раннер для ОБА движков: errors.New и fmt.Errorf фолбэк знает, одиночный error-возврат парсит, а выводы совпадают посимвольно (прогнано трижды). Три строчки вывода — три факта идиомы: ошибка печатается своим текстом, потому что это значение с методом Error(); контекст наращивается fmt.Errorf без потери исходного сообщения; успешный вызов возвращает nil, и сравнение с nil — обычная проверка значения. Эксперименты: замените %v на %s — вывод не изменится, error печатается как строка; напишите return nil в ветке ошибки — main замолчит про отказ: ошибка никуда не делась, но её выбросили, ровно то, что ловит ревью. Связка с пятнадцатым уроком: там про заворачивание %w и errors.Is — следующий раннер именно про это.

Вывод

Запустите код, чтобы увидеть вывод.

Соберите контракт с ошибкой

Восстановите идиому: сентинелл-ошибка через errors.New, честный nil на успехе и проверка err != nil в месте вызова.

package main import ( "errors" "fmt" ) func parseAge(s string) error { if s == "" { // Ошибка-значение из текста. return } // Успех — просто nil. return } func main() { err := parseAge("") if != nil { fmt.Println("отказ:", err) } fmt.Println(parseAge("42") == nil) }

Проверьте себя: что делать с ошибкой

Функция load() вернула err != nil. Какая строка — идиоматичный ответ по Effective Go и пятнадцатому уроку?

errors.Is и %w: завёрнутая ошибка

Раннер нативный: резервный движок не знает errors.Is («у пакета errors нет члена Is»), основной (yaegi) исполняет связку пятнадцатого урока честно — прогнано трижды, вывод детерминирован. Три строки — три факта: errors.Is находит ErrNotFound внутри завёрнутой цепочки (true); печать ошибки показывает весь путь с контекстом «пользователь 7: не найдено»; голое == на завёрнутую ошибку возвращает false — сравнивайте цепочки только через errors.Is. Эксперименты: замените %w на %v — errors.Is вернёт false, цепочка разорвана; верните ErrNotFound напрямую из find — тогда и == справится, потому что заворачивать нечего.

Вывод

Запустите код, чтобы увидеть вывод.

Интерфейсы маленькие: один метод — уже контракт#

Идиома о размерах: интерфейс в Go тем сильнее, чем меньше у него методов. Формула Роба Пайка из Go Proverbs: «The bigger the interface, the weaker the abstraction» — а её обратная сторона вам уже знакома по десятому и одиннадцатому урокам: чем меньше методов требует контракт, тем больше типов его удовлетворяют без единой строчки приспосабливания. Экстремум — сам error: интерфейс из ОДНОГО метода Error() string, и любая структура с этим методом становится ошибкой. Дальше fmt.Stringer (String() string), io.Reader и io.Writer (по одному методу), sort.Interface (три) — стандартная библиотека набита контрактами на один-два метода, и это не экономия, а дизайн.

Отсюда две практические привычки. Первая — «принимай интерфейсы, возвращай конкретные типы»: функция printPrice(p Pricer) работает с любым носителем цены, а конструктор NewTicket возвращает конкретный Ticket, потому что вызывающему удобнее получить максимум возможностей, а не минимум обещаний. Интерфейс при этом объявляют ПОТРЕБИТЕЛЬ, а не поставщик: интерфейс живёт в пакете, который его использует, — так контракт точно описывает то, что нужно, а не то, что реализовано. Вторая — строковая идиома: тип, которому предстоят печати и логи, получает метод String() string и автоматически печатается по-человечески в fmt.Println — запомните форму «String, не ToString» из предыдущего блока.

Малый интерфейс — ещё и страховка от переусложнения: если контракт из одного метода вдруг захотел расти, это сигнал, что он смешивает две обязанности. В раннере ниже — Pricer из одного метода и Ticket с методами Price и String: оба движка исполняют пример одинаково, потому что интерфейсная диспетчеризация в песочнице честная. Связка с десятым уроком прямая: там интерфейсы как механизм, здесь — как стиль выбора размера.

Малый интерфейс: один метод и Stringer

Раннер для ОБА движка: интерфейсный вызов, методы и Sprintf фолбэк исполняет честно, вывод совпадает посимвольно (прогнано трижды). Смотрите на две идиомы в действии: Pricer объявлен ПОТРЕБИТЕЛЕМ (printPrice) и требует один метод — Ticket удовлетворил ему, не упоминая интерфейс нигде в своём коде; метод String() (не ToString!) делает печать человеческой без всякого вызова. Эксперименты: добавьте тип Pass{Km int} с методом Price() int — printPrice примет его без единого изменения себя: это и есть сила малого контракта; попробуйте добавить в Pricer второй метод — и сразу посчитайте, сколько типов его перестало удовлетворять.

Вывод

Запустите код, чтобы увидеть вывод.

Don't communicate by sharing memory: владелец канала#

Слоган из Effective Go стоит выучить дословно: «Do not communicate by sharing memory; instead, share memory by communicating» — не общайтесь, разделяя память; делитесь памятью, общаясь. За ним стоит модель, о которой документ пишет честно: «Only one goroutine has access to the value at any given time. Data races cannot occur, by design» — в каждый момент времени значение доступно одной горутине, гонок нет по построению. Практическая форма этой идиомы — владелец канала: данные проходят через канал (четвертый и пятый уроки), а не через общую переменную под замком (восьмой урок). Горутина-владелец единственная пишет в канал, потребители читают; кто создаёт канал, тот и закрывает — close в Go вообще имеет смысл только у отправителя, и range по каналу в настоящем коде завершается именно на этом close.

У идиомы есть канонические проявления, которые вы уже видели: воркер-тикер из двадцать третьего урока владел своим каналом и уходил по ctx.Done() из девятого; таймауты в select из шестого — это тоже коммуникация вместо общего флага. И у неё есть честная оговорка из самого Effective Go: «This approach can be taken too far. Reference counts may be best done by putting a mutex around an integer variable» — зашаренную переменную-счётчик честнее защитить мьютексом из восьмого урока, чем городить вокруг неё канал. Идиома — высокоуровневый подход, а не религиозный запрет: выбирайте владение каналом, когда значение переходит из руки в руку, и мьютекс — когда много читателей щиплет одно поле.

Ниже — задание на порядок: владелец-замыкание шлёт два значения, main читает ровно обещанное. Обратите внимание на чтение фиксированного числа значений вместо range: в резервном движке песочницы range по каналу зависает (close он не знает), поэтому оба раннера урока построены на подсчёте — это и есть пример идиомы, адаптированной под честные ограничения инструмента.

Соберите: владелец канала

Соберите программу про владение каналом: один писатель в замыкании, двое чтений по договорённости, печать суммы. Оба движка дают вывод «7 8 15».

Строки

Ваша программа

Нажимайте на строки слева, чтобы собрать программу

Пустая структура struct{}: тип без памяти#

struct{} — структура без полей, и в этом её сила: она занимает НОЛЬ байт (девятнадцатый урок мерял размеры через unsafe.Sizeof — можно проверить: sizeof(struct{}) == 0), поэтому все её значения буквально совпадают в памяти. Идиоматичных применений три. Первое — множество: map[string]struct{} — это set, где ключ — данные, а значение — пустышка; проверка принадлежности через _, ok := set["go"] читается идеально, и по сравнению с map[string]bool вы экономите не память (нулевой bool и так дешёв), а намерение: map на struct{} ЧИТАЕТСЯ как множество, а не как «словарь флагов». Второе — сигнальный канал: chan struct{} — канал, у которого важно только событие, а не содержимое; done := make(chan struct{}), close(done) — и все читатели получают нулевое значение немедленно. Третье — пустые типы-маркеры: у структуры без полей нечего копировать, поэтому передача Empty{} — бесплатна.

Вы уже держали сигнальный канал в руках, не зная об идиоме: ctx.Done() из девятого урока возвращает именно <-chan struct{} — контекст сообщает «время вышло», и данные тут ни при чём. Точно так же выглядят done-каналы горутин, семафоры на буферизованных каналах и ping-каналы сторожей. Сравните с не-идиоматичными заменами: chan bool приглашает спросить «true — это успех или сигнал?», а map[string]bool не отличает множество от словаря. Пустая структура — идиома намерения: она говорит «содержимое не имеет значения» синтаксически.

Пара (set и канал) собирается в predict ниже: дубликат ключа не увеличил len, а печатается структура без полей честными пустыми фигурными скобками. Оба движка дают одинаковый ответ, потому что map, len и печать структур — базовые вещи; прогнано трижды, как и всё в уроке.

Сигнальный канал done: событие без данных

GO
package main

import "fmt"

func main() {
        // Канал-событие: содержимое не важно, важен сам факт.
        done := make(chan struct{})
        go func() {
                // Работа завершена — сигнал всем читателям разом.
                close(done)
        }()
        // Ждём события: чтение из закрытого канала не блокируется.
        <-done
        fmt.Println("работа завершена")
}

Каноническая идиома ожидания: done — chan struct{}, владелец закрывает его, читатель получает сигнал и идёт дальше; контекст из девятого урока построен ровно на этом (ctx.Done() — тот же <-chan struct{}). В песочнице код не исполняется как ранимый: резервный движок не знает close («неопределённая переменная close»), а основной движок (yaegi) печатает «работа завершена» — нативная проба подтвердила трижды.

Что напечатает set с повтором?

Прочитайте код и выберите, что он выведет:

package main

import "fmt"

type Empty struct{}

func main() {
        set := make(map[string]struct{})
        set["go"] = struct{}{}
        set["rust"] = struct{}{}
        set["go"] = struct{}{}
        e := Empty{}
        fmt.Println(len(set), e)
}

Простота против cleverness: gofmt как общественный договор#

Осталось самое трудное — идиома без синтаксиса. «Clear is better than clever» — ещё одна пословица Роба Пайка, и Go проводит её во всём: в явной проверке ошибок вместо магии исключений, в отсутствии перегрузки операторов, в запрете циклических импортов, в дженериках, спроектированных так, чтобы не породить метапрограммирование ради метапрограммирования. Простота — тоже идиома, и измеряется она не объёмом кода, а скоростью чтения. Практическая форма: код читают на порядок чаще, чем пишут; остроумный однострочник — это налог на каждого следующего читателя, а простой явный цикл — подарок. Идиоматичный Go скучен нарочно: он собирается из названных вещей — малых интерфейсов, ошибок-значений, владения каналом — так, что читатель опознаёт формы, а не расшифровывает новые.

Здесь же — gofmt, идиома, доведённая до машины: «The gofmt program reads a Go program and emits the source in a standard style of indentation and vertical alignment, retaining and if necessary reformatting comments». Форматирование в Go не обсуждается — его делает инструмент (gofmt или go fmt на уровне пакета), и это выносит из ревью целую категорию споров: отступы, пробелы вокруг операторов, порядок перестановки полей. Effective Go добавляет метаправило: «if the answer doesn't seem right, rearrange your program — don't work around gofmt» — если вывод gofmt кажется плохим, проблема в том, что код написан в неудачной форме. Простой язык + простой формат + честные ошибки = код, который стареет медленно: он читается и через десять лет, потому что ничего умного в нём не спрятано.

Финальный штрих — идиомы проверяются линтерами: go vet входит в дистрибутив и ловит механические ошибки (Printf-глаголы, неиспользуемые результаты), gofmt и goimports держат форму, community-линтеры (errcheck, staticcheck) добирают то, что идиома отдала на честное слово. Проверка идиоматичности — часть обычного цикла, а не подвиг: напишите по-простому, прогоните инструменты, поправьте — и код станет понятен незнакомцу.

Чек-лист идиом: сборный#

ИдиомаСутьПримерГде в курсе
MixedCapsмногословные имена без подчёркиванийrateLimit, ParseDurationэтот урок, 14
Видимость через регистрзаглавная — экспорт, строчная — приватностьStatusOK vs countэтот урок, 14
Геттер без Getметод чтения зовут именем поляu.Email(), не u.GetEmail()этот урок
String, не ToStringстроковый конвертер — метод Stringfunc (t Ticket) String()этот урок, 10
Короткие имена в узкой областидальше от использования — длиннее имяfor i, r := range …этот урок
Групповая формародственные объявления — одной группойconst ( A = iota; B )этот урок
iotaперечисления без рукописных чиселLow = iotaэтот урок
Ошибка — значениевозврат error вместо исключенийif err != nilэтот урок, 15
Guard-ветвиошибки отсекаются сразу, успех идёт внизreturn fmt.Errorf(...)этот урок, 15
%w и errors.Isзаворачивай контекст, проверяй цепочкуerrors.Is(err, ErrX)15
Сентинелл-ошибкаименованная ошибка пакетаvar ErrX = errors.New(...)15
Малые интерфейсычем меньше методов, тем шире контрактerror, fmt.Stringer10, 11
Принимай интерфейспотребитель объявляет контрактfunc p(p Pricer)10
Владелец каналапишет один, читают по договорённостиgo owner(ch)04, 05, 08
Пустая структуранулевая память для set и сигналовmap[string]struct{}, chan struct{}этот урок, 09
defer рядом с ресурсомзакрытие объявляют сразу после открытияdefer f.Close()02
Время как значенияDuration вместо магических чисел5 * time.Second23
gofmtформат решает машина, не спорgo fmt ./...этот урок
Clear beats cleverпростота важнее остроумияявный цикл вместо монстраэтот урок

Правила гигиены: идиомы в каждом файле#

  • Имена — в MixedCaps: подчёркивание в идентификаторе — первый маркер неидиоматичного кода на ревью.
  • Регистр первой буквы — решение об экспорте: не экспортируйте то, что не готов объяснять в документации пакета.
  • Геттер — имя поля с заглавной; глагол Get не нужен, сеттер — единственный честный Set.
  • Строковый метод типа — String(), и fmt.Println сам станет печатать по-человечески.
  • Короткие имена (i, n, r) — для узких областей; пакетные имена — длинные и говорящие.
  • Родственные константы и переменные — групповой формой; перечисления — на iota, а не рукописными числами.
  • err != nil проверяйте сразу и решайте: вернуть наверх (можно с контекстом %w), обработать или осознанно выбросить в _ — с комментарием.
  • Печатать ошибку и продолжать с nil-результатом — самая дорогая ошибка из всех «бесплатных».
  • panic — для сломанных контрактов; данные не совпали — это error, а не паника.
  • Интерфейс — один-два метода и объявление потребителем: Pricer, Stringer, io.Reader — образцы размера.
  • Возвращайте конкретные типы: вызывающий получит максимум, интерфейс пусть остаётся параметром.
  • Значение переходит из горутины в горутину — через канал и одного владельца; мьютекс — для общего поля, а не для всего мира.
  • close посылает владелец канала; range по каналу ждёт именно этого close.
  • Множество — map[string]struct{}; событие — chan struct{}; оба варианта говорят «содержимое не важно» синтаксисом.
  • Прогоняйте gofmt (или go fmt ./...) перед коммитом: спорить о формате в Go неприлично — его решает машина.
  • Clear is better than clever: если строчку нужно объяснять вслух, напишите её скучнее.

Что дальше: идиомы становятся рефлексом#

Соберём урок в один абзац. Именование: MixedCaps вместо подчёркиваний, видимость через регистр, геттеры без Get, String вместо ToString, короткие имена в узких областях, групповые объявления и iota. Ошибки: значение error, проверяемое честным if err != nil, guard-ветви и успех вниз по странице, контекст через fmt.Errorf, цепочки через errors.Is — механика в пятнадцатом уроке, стиль здесь. Интерфейсы: один метод — уже контракт, потребитель объявляет, конкретный тип возвращается. Конкуренция: don't communicate by sharing memory — владелец канала, а честный мьютекс для общего поля. Ноль байт: struct{} для множеств и сигналов. И всё это держит gofmt с простотой против cleverness.

Двадцать пятый урок — финал курса: проект-воркер, где идиомы работают на канатах настоящего приложения — владелец канала будет качать задачи, тикер из двадцать третьего урока задаст такт, ошибки-значения из пятнадцатого сложатся в цепочки, defer из второго закроет ресурсы, контекст из девятого урежет всё по дедлайну. Это не экзамен по списку выше, а его применение: вы увидите, как идиомы перестают быть правилами и становятся способом думать.

Если захочется перечитать первоисточники — Effective Go лежит на go.dev/doc/effective_go, а лучшие живые примеры — исходники стандартной библиотеки. Внутри курса вернитесь к уроку про time: время как значения — тоже идиома, Duration вместо магических чисел; к уроку про обработку ошибок: там вся механика %w и errors.Is, на которую опирается стиль этого урока; и к уроку про интерфейсы: малые интерфейсы выросли из его механизмов. До встречи на странице курса — остался один шаг.

Карточки урока

Термин

MixedCaps: правило имён

нажмите, чтобы перевернуть
Определение

Многословные имена — горбиком без подчёркиваний: rateLimit, ParseDuration («MixedCaps or mixedCaps rather than underscores»). Видимость — регистром первой буквы: StatusOK экспортирован, count приватен. Пакеты — lower case single-word: strconv, http. Аббревиатуры целиком: ParseURL, ServeHTTP, пакет utf8. Длина имени растёт с расстоянием до использования: i в цикле, maxIdleConnsPerHost в конфиге.

1 / 6

Песочница урока

Редактируйте код и запускайте его прямо здесь — интерпретатор работает в браузере, ничего устанавливать не нужно. За каждый запуск — +2 XP.

Вывод

Запустите код, чтобы увидеть вывод.

Коротко: что запомнить

  • Effective Go — сборник идиом «clear, idiomatic Go code»: идиому нельзя нарушить синтаксисом, её нарушают для читателя, а ловят ревью и линтеры.
  • Именование: MixedCaps вместо подчёркиваний, видимость через регистр первой буквы, пакеты lower case single-word, аббревиатуры целиком (ParseURL, ServeHTTP).
  • Геттер — имя поля с заглавной (Owner, не GetOwner); строковый метод — String, не ToString; сеттер — честный Set.
  • Короткие имена — в узкой области, длинные — для пакетных объявлений; групповая форма const/var показывает родство, iota строит перечисления.
  • Ошибки — значения: err := f(); if err != nil { return err }; успех идёт вниз по странице, guard-ветви отсекают ошибки сразу.
  • Контекст ошибки наращивают fmt.Errorf с %w, цепочку проверяют errors.Is; печатать и продолжать с nil — не вариант, _ = err — только осознанно.
  • Интерфейсы тем сильнее, чем меньше методов: error и fmt.Stringer — по одному; интерфейс объявляет потребитель; принимай интерфейс — возвращай конкретный тип.
  • Don't communicate by sharing memory: значение в каждый момент у одной горутины; владелец канала пишет, потребители читают, close — у отправителя; мьютекс честен для общего поля.
  • struct{} — ноль байт: map[string]struct{} — множество, chan struct{} — событие (таков и ctx.Done()); печатается пустыми скобками {}.
  • Clear is better than clever: явное лучше остроумного, код читают чаще, чем пишут; gofmt решает форматирование за людей — go fmt ./... перед коммитом.
  • Песочница: оба движка честно исполняют идиомы урока (вывод совпал посимвольно, прогнано трижды); фолбэк не знает errors.Is, close и формы параметров «a int, b int» — их проверяем основным движком (yaegi).

Домашнее задание

Часть первая — именование: возьмите свою старую программу из любого урока курса и переименуйте её по идиомам: подчёркивания — в MixedCaps, геттеры — без Get, локальные счётчики — в короткие имена, константы сгруппируйте, перечисление статусов переведите на iota; объясните в комментарии, какое имя и почему стало длиннее, а какое короче. Часть вторая — ошибки: напишите функцию validate(s string) error с двумя guard-ветвями и вызовите её трижды (два отказа, один успех), каждую ошибку заверните в fmt.Errorf с %w и наверху проверьте errors.Is против своей ErrX — объясните, почему голое == не нашло бы завёрнутую ошибку. Часть третья — интерфейсы: объявите интерфейс из одного метода, реализуйте его двумя типами, напишите функцию, принимающую интерфейс, и функцию, возвращающую конкретный тип; комментарий — почему контракт живёт у потребителя. Часть четвёртая — конкуренция: горутина-владелец шлёт пять значений, main читает ровно пять и печатает сумму; рядом — вариант с мьютексом вокруг общего счётчика, и комментарий, какой вариант честнее здесь и почему. Часть пятая — struct{}: соберите множество из слов (дубликаты — через len), сигнальный done-канал с close и объяснение, почему ctx.Done() возвращает <-chan struct{}. Финал — гигиена: прогоните gofmt и go vet по всему, что написали; каждая правка инструмента — одна строка объяснения. Комментарии в коде — по-русски.

Личные заметкиПусто
Хранятся только в вашем браузере (Go Pro · Правила и идиомы Go)

Вопросы и ответы по уроку

Что такое идиомы Go и где их официальный список?

Идиомы — устоявшиеся привычки стиля: именование, обработка ошибок, размеры интерфейсов, работа с каналами. Главный первоисточник — документ Effective Go (go.dev/doc/effective_go), сборник советов команды языка про clear, idiomatic Go code. Рядом живут Go Code Review Comments, пословицы Роба Пайка (Clear is better than clever, The bigger the interface, the weaker the abstraction) и исходники стандартной библиотеки — лучший образец стиля. Важный нюанс: Effective Go написан до дженериков и контекста и не переписан — базовые привычки там вечны, а рекомендации уровня «только каналы» читайте с оговорками из самого же документа.

Почему в Go геттеры называют без префикса Get?

Effective Go формулирует это так: «it's neither idiomatic nor necessary to put Get into the getter's name». Для неэкспортированного поля owner геттер называется Owner — имя метода отвечает на вопрос «что это», и префикс только шумит. Сеттер — исключение: глагол Set отличает запись от чтения (u.SetEmail(...)). Той же логикой строковый конвертер называется String, а не ToString. Компилятор примет любой вариант — это идиома, то есть договор о читаемости, и на ревью GetEmail попросят переименовать.

if err != nil выглядит как boilerplate — есть ли планы его убрать?

Явная проверка — центральная идиома языка: ошибка это значение, и в месте вызова видно, обработана она, завёрнута (fmt.Errorf с %w) или выброшена (_ = err). Предложения добавить синтаксический сахар (try, оператор ?) обсуждались командой языка и были отвергнуты — в том числе из-за того, что прячут поток ошибок за символами. Практический ответ на многословие — правильная форма: guard-ветви с ранним return, успех идёт вниз по странице; механика заворачивания и проверки цепочек разобрана в пятнадцатом уроке курса.

Работают ли примеры идиом в песочнице самоучителя?

Да — почти все, и честно в обоих движках. Именование, групповая форма, iota, errors.New, fmt.Errorf, одиночный error-возврат, малые интерфейсы с методами, пустая структура, каналы с горутинами исполняются одинаково, выводы совпали посимвольно, каждый пример прогнан трижды. Ограничения резервного движка: пакет errors знает только New (errors.Is даёт «у пакета errors нет члена Is»), пакет sync недоступен, форма параметров «a int, b int» не парсится (нужна групповая «a, b int»), close не знает, а range по каналу зависает — идиому close(done) и цепочки ошибок проверяйте основным движком (yaegi), который исполняет настоящий Go.

Термины урока

Ключевые понятия из этого урока — короткие определения в глоссарии. Наведите на термин, чтобы вспомнить, откройте карточку для подробностей и ссылки на практику.

Следующая ступень учебного пути

Fiber25 уроков~13 чпрактика25 уроков уже доступны

Fiber: экспресс-подобный веб-фреймворк на Fasthttp

Курс Fiber для Go: маршруты, middleware, JSON, CORS, WebSocket, шаблоны, лимитеры и тестирование. 25 уроков с примерами кода и итоговым REST API.

  • Поднимать HTTP-сервер на Fiber за минуты
  • Проектировать маршруты, группы и версии API
  • Писать middleware: логи, CORS, лимиты, recovery
  • Принимать и отдавать JSON с валидацией