Go быстро набирает популярность в качестве языка для создания веб-сервисов. Существует множество учебников по синтаксису Go, но знать его недостаточно.
Автор Джон Боднер описывает и объясняет паттерны проектирования, используемые опытными разработчиками. В книге собрана наиболее важная информация, необходимая для написания чистого и идиоматического Go-кода. Вы научитесь думать как Go-разработчик, вне зависимости от предыдущего опыта программирования.
Предполагается, что читатель уже умеет пользоваться таким инструментом разработчика, как система контроля версий (желательно Git) и IDE. Читатель должен быть знаком с такими базовыми понятиями информатики, как конкурентность и абстракция, поскольку в книге мы будем касаться этих тем применительно к Go. Одни примеры кода можно скачать с GitHub, а десятки других — запустить в онлайн-песочнице The Go Playground. Наличие соединения с интернетом необязательно, однако с ним будет проще изучать исполняемые примеры кода. Поскольку язык Go часто используется для создания и вызова HTTP-серверов, для понимания некоторых примеров читатель должен иметь общее представление о протоколе HTTP и связанных с ним концепциях.
Несмотря на то что большинство функций Go встречаются и в других языках, программы, написанные на Go, обладают иной структурой. В книге мы начнем с настройки среды разработки для языка Go, после чего поговорим о переменных, типах, управляющих конструкциях и функциях. Если вам захочется пропустить этот материал, постарайтесь удержаться от этого и все-таки ознакомиться с ним. Зачастую именно такие детали делают Go-код идиоматическим. Простые на первый взгляд вещи могут оказаться очень интересными, если вы присмотритесь к ним получше.
Блоки, затенение переменных и управляющие конструкции
Теперь, когда мы уже рассмотрели переменные, константы и встроенные типы, пора перейти к рассмотрению программной логики и способов организации кода. Сначала вы узнаете, что собой представляют блоки и как они влияют на доступность идентификаторов. После этого мы рассмотрим управляющие конструкции языка Go, а именно операторы if, for и switch. Наконец, мы поговорим об операторе goto и о том единственном случае, в котором его следует использовать.
Блоки
Go позволяет вам объявлять переменные в разных местах: их можно объявлять вне функций, в качестве параметров функции или в качестве локальной переменной внутри функции.
Каждое из тех мест, в которых мы размещаем то или иное объявление, называется блоком. При объявлении переменных, констант, типов и функций вне какой-либо функции они размещаются в блоке пакета. В своих программах мы использовали операторы import для доступа к функциям вывода на экран и математическим функциям (подробнее о которых будет рассказано в главе 9). Операторы import определяют, имена каких других пакетов допускается использовать внутри содержащего их файла. Эти имена находятся в блоке файлов. Все переменные, определяемые на верхнем уровне функции (включая параметры функции), находятся в отдельном блоке. Внутри функции каждая пара фигурных скобок ({}) определяет дополнительный блок, и, как мы вскоре увидим, управляющие конструкции языка Go также определяют собственные блоки.
К идентификатору, определенному в любом внешнем блоке, можно получить доступ из любого внутреннего блока. Это порождает следующий вопрос: что произойдет, если во вложенном блоке будет объявлен идентификатор с таким же именем, как и во внешнем блоке? Это приведет к затенению идентификатора, созданного во внешнем блоке.
Затенение переменных
Прежде чем приступить к разговору о том, что такое затенение, рассмотрим небольшой пример кода (пример 4.1). Вы можете запустить его в онлайн-песочнице (https://oreil.ly/50t6b).
Пример 4.1. Затенение переменных
func main() { x := 10 if x > 5 { fmt.Println(x) x := 5 fmt.Println(x) } fmt.Println(x) }
Перед тем как запускать этот код, попробуйте догадаться, что он выведет на экран:
- не выведет ничего, поскольку не сможет успешно скомпилироваться;
- 10 в первой строке, 5 во второй строке и 5 в третьей строке;
- 10 в первой строке, 5 во второй строке и 10 в третьей строке.
На самом деле этот код выведет следующее:
10
5
10
Переменная является затеняющей, если ее имя совпадает с именем переменной, определенной во вмещающем блоке. При наличии затеняющей переменной вы не можете получить доступ к затененной переменной.
В данном случае мы, очевидно, не собирались создавать новую переменную x внутри оператора if. Вместо этого мы хотели присвоить 5 переменной x, объявленной
на верхнем уровне блока функции. При первом вызове функции fmt.Println внутри оператора if еще есть возможность получить доступ к переменной x, объявленной на верхнем уровне блока функции. Однако в следующей строке переменная x затеняется путем объявления новой переменной с таким же именем внутри блока, образованного телом оператора if. При втором вызове функции fmt.Println обращение к переменной с именем x выводит затеняющую переменную, которая содержит значение 5. Закрывающая фигурная скобка тела оператора if завершает блок, в котором присутствует затеняющая переменная x, и поэтому при третьем вызове функции fmt.Println обращение к переменной с именем x выводит переменную, объявленную на верхнем уровне блока функции и содержащую значение 10. Обратите внимание, что эта переменная x никуда не исчезла и не получила новое значение: мы просто не могли получить к ней доступ, поскольку она была затенена во внутреннем блоке.
В предыдущей главе я говорил о том, что стараюсь не использовать оператор := в тех случаях, когда это может привести к неясности в отношении того, какая именно переменная применяется. Это объясняется тем, что при использовании оператора := легко случайно затенить переменную. Как вы помните, с помощью оператора := можно создавать сразу несколько переменных с присвоением значения. Кроме того, оператор := применяется даже в том случае, когда не все переменные слева от него являются новыми. Достаточно, чтобы хотя бы одна из указанных слева переменных была новой. Рассмотрим еще одну программу (пример 4.2), которую вы можете запустить в онлайн-песочнице (https://oreil.ly/U_m4B).
Пример 4.2. Затенение в случае присвоения нескольких значений
func main() { x := 10 if x > 5 { x, y := 5, 20 fmt.Println(x, y) } fmt.Println(x) }
Запустив этот код, вы получите следующие результаты:
5 20
10
Переменная x затеняется внутри оператора if, несмотря на наличие определения переменной x во внешнем блоке. Это объясняется тем, что оператор := заново использует переменные, объявляемые в текущем блоке. Поэтому при применении оператора := следует убедиться в том, что слева от него не указаны переменные, объявленные во внешней области видимости, если у вас нет намерения их затенить.
Также нужно проследить за тем, чтобы не был затенен импорт пакета. Об импорте пакетов мы подробно поговорим в главе 9, однако уже сейчас воспользуемся пакетом fmt для вывода на экран результатов своих программ. Посмотрим, что произойдет, если мы объявим переменную с именем fmt внутри функции main, как показано в примере 4.3. Попробуйте запустить этот код в онлайн-песочнице (https://oreil.ly/CKQvm).
Пример 4.3. Затенение имен пакетов
func main() { x := 10 fmt.Println(x) fmt := "oops" fmt.Println(fmt) }
Попытавшись выполнить этот код, мы получим сообщение об ошибке:
fmt.Println undefined (type string has no field or method Println)
Обратите внимание, что проблема состоит не в присвоении переменной имени fmt, а в попытке обращения к тому, чего нет у локальной переменной fmt. Как только локальная переменная fmt объявлена, она затеняет пакет с именем fmt в блоке файлов, делая невозможным использование этого пакета в оставшейся части функции main.
Выявление затененных переменных
Учитывая то, насколько трудноуловимыми могут быть вносимые затенением ошибки, будет очень полезно убедиться в отсутствии затененных переменных в ваших программах. Хотя ни команда go vet, ни инструмент golangci-lint не предлагают вам инструментов для выявления затенения, вы можете включить это выявление в свой процесс компиляции, установив на своей машине линтер shadow:
$ go install golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow@latest
При компиляции с помощью make-файла включите линтер shadow в перечень задач цели vet:
vet: go vet ./... shadow ./... .PHONY:vet
Если вы попробуете выполнить команду make vet для приведенного выше кода, то увидите, что затененная переменная будет обнаружена:
declaration of "x" shadows declaration at line 6
Всеобщий блок
На самом деле существует еще одна, немного странная разновидность блоков: «всеобщий блок» (universe block). Как вы помните, Go — небольшой язык, в котором имеется лишь 25 ключевых слов. Что интересно, в этот список не входят встроенные типы (такие как int и string), константы (true и false), функции (make и close) и значение nil. В таком случае где же они?Все это считается в Go не ключевыми словами, а предопределенными идентификаторами и определено во всеобщем блоке, включающем в себя все остальные блоки.
Тот факт, что эти имена объявлены во всеобщем блоке, означает, что их можно затенить в других областях видимости. Как это может происходить, можно увидеть, запустив в онлайн-песочнице код из примера 4.4 (https://oreil.ly/eoU2A).
Пример 4.4. Затенение значения true
fmt.Println(true) true := 10 fmt.Println(true)
Запустив этот код, вы увидите на экране следующее:
true
10Никогда не допускайте переопределения идентификаторов, определенных во всеобщем блоке! Если вы случайно это сделаете, ваш код будет вести себя совсем не так, как вы ожидали. В лучшем случае это приведет к ошибкам на этапе компиляции. В более тяжелом случае вам придется долго выискивать источник своих проблем.
Если вы подумали, что ошибки со столь серьезными последствиями должны выявляться средствами статического анализа (линтерами), то хочу вам сообщить, что, как ни удивительно, они этого не делают. Даже линтер shadow не выявляет затенение идентификаторов, объявленных во всеобщем блоке.
Оператор if
Оператор if в языке Go ведет себя практически так же, как в других языках программирования. Учитывая то, насколько общеизвестным является этот оператор, я уже использовал его в предыдущих примерах кода, не волнуясь о том, что кто-то не поймет его назначение. Пример 4.5 демонстрирует более полный образец его использования.
Пример 4.5. Оператор if в сочетании с оператором else
n := rand.Intn(10) if n == 0 { fmt.Println("That's too low") } else if n > 5 { fmt.Println("That's too big:", n) } else { fmt.Println("That's a good number:", n) }
Наиболее заметное отличие оператора if языка Go от других языков состоит в том, что здесь не нужно заключать условие в круглые скобки. Однако у оператора if в Go есть еще одна особенность, которая позволяет лучше управлять переменными.
Как уже говорилось в разделе, посвященном затенению переменных, любая переменная, объявленная внутри фигурных скобок оператора if или else, существует только внутри этого блока. В этом нет ничего необычного; так же обстоит дело и в большинстве других языков. Однако, в отличие от других языков, Go также позволяет объявить переменные, область видимости которых будет включать себя условие и блоки операторов if и else. Посмотрим, как будет выглядеть предыдущий пример, если мы перепишем его, используя эту возможность (пример 4.6).
Пример 4.6. Объявление переменной внутри оператора if
if n := rand.Intn(10); n == 0 { fmt.Println("That's too low") } else if n > 5 { fmt.Println("That's too big:", n) } else { fmt.Println("That's a good number:", n) }
Наличие этой особой области видимости может быть очень полезным. Это позволяет создавать переменные, которые будут доступны только там, где они нужны. В коде, следующем за цепочкой операторов if/else, переменная n становится неопределенной. Вы можете убедиться в этом, запустив в онлайн-песочнице код из примера 4.7 (https://oreil.ly/rz671).
Пример 4.7. Попытка обращения к переменной за пределами области видимости
if n := rand.Intn(10); n == 0 { fmt.Println("That's too low") } else if n > 5 { fmt.Println("That's too big:", n) } else { fmt.Println("That's a good number:", n) } fmt.Println(n)
Попытка выполнить этот код приведет к ошибке компиляции:
undefined: n
nВ принципе, перед условием оператора if можно разместить любой простой оператор, включая такие вещи, как вызов не возвращающей значение функции или присвоение нового значения существующей переменной. Однако так поступать не стоит. Во избежание путаницы используйте эту возможность только для определения новых переменных, область видимости которых будет ограничиваться операторами if/else.
Также не забывайте о том, что, как и в любом другом блоке, переменная, объявленная внутри оператора if, будет затенять переменные с таким же именем, объявленные в других блоках.
Четыре вида оператора for
Как и другие C-подобные языки, Go использует для организации циклов оператор for. Однако, в отличие от других языков, в Go применение оператора for является единственным способом организации циклов. Это обеспечивается за счет использования четырех разновидностей оператора for.
- Полная форма оператора for в стиле языка C.
- Оператор for, использующий только условие.
- Бесконечная форма оператора for.
- Оператор for-range.
Оформить предзаказ бумажной книги можно на нашем сайте
- Обратите внимание, сейчас проходит Распродажа от издательства «Питер».
«Дисконт» — это старые издания, которых давно нет в продаже, и новинки с незначительным браком:
- потертости;
- мятые страницы;
- наклеенные ценники или следы от них;
- побитые при транспортировке углы;
- магнитная защитная наклейка.
ВАЖНО: в этих книгах нет типографских браков! Конкретно по каждой книге сказать, в чем брак нет возможности.
На все книги категории «Дисконт» — скидки! Некоторые издания можно купить с выгодой до 70%! Так же можно применять купоны к книгам в дисконте!
ссылка на оригинал статьи https://habr.com/ru/company/piter/blog/666908/
Добавить комментарий