Базовые вопросы по Golang
Характеристики Go
Императивный
Императивное программирование — это парадигма, основанная на составлении алгоритма действий (инструкций/команд), которые изменяют состояние (информацию/данные/память) программы.
Декларативное программирование — это парадигма, при которой описывается желаемый результат, без составления детального алгоритма его получения. В пример можно привести HTML и SQL.
Компилируемый в нативный код
Статическая типизация Стати́ческая типиза́ция — приём, широко используемый в языках программирования, при котором переменная, параметр подпрограммы, возвращаемое значение функции связывается с типом в момент объявления и тип не может быть изменён позже.
Нет классов, но есть структуры с методами
Есть интерфейсы
Нет наследования, но есть встраивание
Функции - объекты первого класса. Функции первого класса - это означает, что язык поддерживает передачу функций в качестве аргументов другим функциям, возврат их как результат других функций, присваивание их переменным или сохранение в структурах данных
Есть замыкания Замыкание - это функция, которая ссылается к переменным вне ее тела. Функция имеет доступ к связанным переменным, а также может присваивать им значения; в этом смысле функция "связана" с этими переменными.
Функции могут возвращать больше 1 значения
Есть указатели, но нет адресной арифметики
Обширные возможности для конкурентности
Сборка в 1 бинарный файл
Набор стандартный инструментов
Какие технологические преимущества экосистемы Go вы можете назвать?
Прекрасная стандартная библиотека
Чем вам нравится golang?
Простой, надежный и продуктивный.
Открытый исходный код.
Быстрая компиляция и выполнение.
Простое управление зависимостями.
Качественно составленные полезные пакеты stdlib.
Простая обработка ошибок.
Один бинарный файл для управления всеми другими.
Кросс-компиляция.
Сборка мусора.
Читабельность.
Встроенное тестирование.
Профилирование.
Статическая или динамическая типизация? строготипизирован или нет
Что вас огорчает в системе типов Go?
Отсутствие перечислимых типов. Вместо них используются группы констант, но все константы-значения фактически являются целыми числами, группы эти синтаксически не объединены, и компилятор не может контролировать их использование. Невозможно описать тип, связанный с перечислением, например, массив, содержащий по одному элементу на каждый элемент перечисления (который в Паскале описывается конструкцией вида
type EnumArray = array[EnumType] of ElementType
), создать цикл по перечислению, компилятор не может контролировать полноту списка альтернатив в конструкцииswitch
, когда в качестве селектора используется значение перечисления.нет обобщённых типов (generics)
Почему на Go практически не пишут расширений для других языков и динамических библиотек?
Go - императивный или декларативный? А в чем разница?
Golang - императивный язык программирования
Императивное программирование — это парадигма, основанная на составлении алгоритма действий (инструкций/команд), которые изменяют состояние (информацию/данные/память) программы. Первыми языками программирования, основанными на таком подходе, были машинные коды и ассемблеры. Фактически, программа на этих языках — это код, который выполняется компьютером сразу, без предварительной компиляции. Из языков высокого уровня, требующих компиляции исходного кода программы в машинный код (или интерпретации), к императивным можно отнести C, C++, Java.
Декларативное программирование — это парадигма, при которой описывается желаемый результат, без составления детального алгоритма его получения. В пример можно привести HTML и SQL. При создании HTML мы с помощью тегов описываем, какую хотим получить страничку в браузере, а не то, как нарисовать на экране заголовок статьи, оглавление и текст. В SQL, если нам нужно посчитать количество сотрудников с фамилией «Сидоров», мы напишем SELECT count(*) FROM employee WHERE last_name = 'Сидоров';
. Тут ничего не сказано про то, в каком файле или области памяти находятся данные по сотрудникам, как именно выбрать из них всех Сидоровых и нужно ли вообще это делать для подсчёта их количества.
Заповеди Роба Пайка
https://habr.com/ru/post/272383/
Don't communicate by sharing memory, share memory by communicating.
«Не общайтесь разделением памяти. Разделяйте память через общение.»
Concurrency is not parallelism.
«Конкурентность — это не параллелизм»
The bigger the interface, the weaker the abstraction.
Чем больше интерфейс, тем слабее абстракция
Новички в Go, особенно пришедшие с Java, часто считают, что интерфейсы должны быть большими и содержать много методов. Также часто их смущает неявное удовлетворение интерфейсов. Но самое важное в интерфейсах не это, а культура вокруг них, которая отображена в этом постулате. Чем меньше интерфейс, тем более он полезен. Пайк шутит, что три самых полезных интерфейса, которые он написал — io.Reader, io.Writer и interface{} — на троих в среднем имеют 0.666 метода.
Make the zero value useful.
Делайте нулевое значение полезным
interface{} says nothing.
Пустой интерфейс ни о чём не говорит
Этот постулат говорит о том, что интерфейсы — «поведенческие типы» — должны что-то означать. Если вы создаёте интерфейс, это что-то означает и служит конкретной цели. Пустой же интерфейс (interface{}) ничего не означает и ни о чём не говорит. Есть ситуации, когда его нужно использовать, но они чаще исключение — не используйте interface{} без повода. Новички часто переиспользуют пустые интерфейсы, и масса вопросов на Stack Overflow именно о них.
Gofmt's style is no one's favorite, yet gofmt is everyone's favorite.
Стиль Gofmt — это не чье-то предпочтение, но gofmt предпочитают все
Многие новички, борясь с привычками, жалуются на то, что код в Go отформатирован не так, как им нравится. Gofmt — это единый стиль, он не является чьим-то личным предпочтением. Даже сам Грисмайер, автор утилиты go fmt, сам предпочёл бы другой стиль форматирования. Но с того момента, как формат был утверждён и gofmt используется везде и всегда — просто примите это как должное. Опытные программисты считают go fmt спасением, так как он очень упрощает работу с чужим кодом — ад с разными стилями форматирования в других языках ушёл в прошлое.
A little copying is better than a little dependency.
Небольшое копирование лучше небольшой зависимости
Syscall must always be guarded with build tags.
Код с syscall-вызовы должен содержать build-теги
"Channels orchestrate; mutexes serialize"
Каналы — оркестрируют, мьютексы — сериализируют
Cgo is not go
Cgo это не Go
Многие радуются тому, как легко в Go использовать C-код. Иногда, это, конечно, необходимо, но Пайк признается, что сам никогда не использовал Cgo, и в мире Go есть четкое понимание памяти, стабильность, безопасность, сборка мусора… и с Cgo всё это идёт в топку. В 90% случаев в Google, когда кто-то говорит «моя программа крашнулась, помогите разобраться» — дело оказывается в Cgo.
"With the unsafe package there are no guarantees"
С пакетом unsafe нет гарантий
Clear is better than clever
Ясно лучше, чем заумно
Постулат о том, что в Go ясность и читабельность кода стоит на первом месте. И вы должны также стремиться писать ясный и понятный код, вместо заумного. Ваш код рано или поздно будут читать, улучшать и рефакторить другие программисты, и чем яснее он будет, тем проще и быстрее будет результат. Go будет всеми способами и силами вам в этом помогать и способствовать, весь дизайн языка пропитан этой целью.
Reflection is never clear
Рефлексия никогда не понятна
Errors are values
Ошибки это значения
Dont’ just check errors, handle them gracefully
Не просто проверяйте ошибки, обрабатывайте их красиво
Ошибки очень важно не просто проверять, но и думать, что с ними делать, чтобы ошибочная ситуация была обработана корректно. Возможно нужно дополнить ошибку какой-то информацией, возможно запомнить для обработки позднее, что-нибудь ещё. Огромная и очень важная часть программирования — это как вы обрабатываете ошибки. Не просто проверять и пробрасывать наверх, а думать о том, как правильно обработать эту ошибку. Это справедливо для всех языков, но поскольку в Go ошибки это просто значения, то это проще сделать и проще реализовать красивую и правильную обработку ошибок в коде.
"Design the architecture, name the components, document the details"
Продумывайте архитектуру, называйте компоненты, документируйте детали
Тут речь о важности правильного именования компонентов вашего кода, методов, функций и переменных. Эти имена будут потом находится в коде пользователей вашего пакета. И если имена удачны и понятны, дизайн программы будет более ясен и прост для понимания. Но, конечно, всегда есть детали, которые нужно объяснить — и вот их нужно помещать в документацию.
"Documentation is for users"
Документация — для пользователей
О том, что документация к функции должна описывать не что эта функция делает, а для чего она нужна. Это очень важное различие, которое зачастую упускается из виду. Когда пишете документацию, посмотрите на неё глазами пользователя вашего пакета.
Что такое type-switch?
switch
может быть использован для определения динамических типов интерфейсных переменных. Так, типизированный switch
использует синтаксис приведения типов, с ключевым словом type
внутри скобок. Если switch
объявляет переменную в выражении, то переменная будет иметь соответствующий тип в каждом пункте.
Переключатель типов сравнивает типы, а не значения. В остальном он аналогичен переключателю выражений. Он помечается специальным выражением-переключателем, которое имеет форму утверждения типа, используя зарезервированное слово type, а не фактический тип:
Затем случаи (cases) сопоставляют фактические типы T с динамическим типом выражения x. Как и в утверждении типа, x должен иметь интерфейсный тип, и каждый неинтерфейсный тип T, указанный в случае (case), должен реализовывать тип x. Типы, перечисленные в случаях переключения типов, должны быть разными.
Вместо типа случай (case) может использовать предварительно объявленный идентификатор nil; этот случай выбирается, когда выражение в TypeSwitchGuard имеет нулевое значение интерфейса. Может быть не более одного нулевого случая.
Пример:
"fallthrough" утверждение запрещено в переключателе типов.
Как сообщить компилятору, что наш тип реализует интерфейс?
Неявная имплементация
Если это выглядит как утка, плавает как утка и крякает как утка, то это, вероятно, утка и есть.
В Go структура с методами будет удовлетворять интерфейсу просто самим фактом объявления метода. Это кажется не особо важным на маленьких программах или искусственных примерах, но оказывается ключевым в больших проектах, где приходится думать дважды перед тем как изменить какой-то класс, многократно унаследованный другими классами. Возможность легко и просто неявно реализовать различные интерфейсы позволяет программам безболезненно расти, без необходимости продумывать все возможные интерфейсы наперёд и не утопать во множественном наследовании. Это к слову о том, что Go задумывался для облегчения жизни в больших проектах. Важное и не сразу очевидное различие этого заключается в том, как, в итоге, вы строите архитектуру вашей программы — в Java или C++ вы, скорее всего, начинаете с объявления абстрактных классов и интерфейсов, и далее переходите к конкретным реализациям. В Go же наоборот — вы пишете сначала конкретный тип, определяете данные и методы, и только в том случае, если действительно появляется необходимость абстрагировать поведение — создаете отдельный интерфейс. Опять же, масштаб этого различия более ощутим на больших проектах.
Значение nil в Golang
На заметку: В 1978 Тони Хоар также впервые описал принцип взаимодействующих последовательных процессов, или «communicating sequential processes», CSP. Его идеи лежат в основе конкурентности Go.
Однако, пока вычисления не закончены, куда должны указывать указатели? Это тот случай, когда пригодится nil. Nil может значить ближайшую звезду, пока реальная звезда не будет найдена.
В какой еще ситуации указатель в никуда может быть полезен?
Вызывает ли nil сбои в Golang?
Если указатель никуда не указывает, попытка разыменования указателя не сработает, что показано в Листинге 1. Разыменование указателя nil приведет к сбою программы. Обычно пользователям такое совсем не нравится.
Я называю это моей ошибкой в миллиард долларов.
Тони Хоар
Листинг
12345
var nowhere *int if nowhere != nil { fmt.Println(*nowhere)}
По правде говоря, сбой в программе может стать следствием многих причин, не только разыменования указателя nil. К примеру, деление на ноль также приведет к сбою, и решение проблемы будет схожим. Даже так, подумайте обо всех программах, написанных в течение последних 50 лет, количество случайных разыменований указателя nil должен быть весьма значительным.
Существование nil обременяет программиста необходимостью принятия дополнительных решений. Должен ли код проверять наличие nil, и если да, то где? Что код должен делать, если какое-то значение равно nil? После всего вышесказанного, так уж ли плох nil?
Вовсе не обязательно постоянно пытаться избегать nil. По правде говоря, в некоторых случаях nil весьма полезен. В дополнение ко всему указатели nil в Go не так популярны, как указатели null в некоторых других языках, и есть способы избежать их использования в случае нужды.
Вопрос для проверки:
Каким будет нулевое значения типа *string
?Ответ
Защита методов в Golang
1234567891011121314
type person struct { age int} func (p *person) birthday() { p.age++ // разыменование указателя nil} func main() { var nobody *person fmt.Println(nobody) // Выводит: <nil> nobody.birthday()}
Скорее всего, сбой вызван после выполнения строки p.age++
. Удалите данную строку, тогда программа запустится.
На заметку: Сравните это с аналогичной программой в Java, где приемник null приведет к сбою программы сразу после вызова метода.
Go вызывает методы даже в том случае, если у приемника значение nil. Приемник nil ведет себя так же, как и параметр nil. Это значит, что методы могут защищать от значений nil, как показано в следующем примере.Листинг 4Gofunc (p *person) birthday() { if p == nil { return } p.age++ }
123456
func (p *person) birthday() { if p == nil { return } p.age++}
Вместо проверки на наличие nil
перед вызовом метода birthday
предыдущий листинг защищает от приемников nil внутри метода.
На заметку: В Objective-C автоматический запуск метода для nil не приводит к сбою, но при вызове метода будет возвращаться нулевое значение.
С тем, как управлять nil
в Go, разобрались. Методы могут возвращать нулевые значения, возвращать ошибки или приводить к сбою.
Вопрос для проверки:
Что делает доступ к полю (p.age)
, если p
является nil?Ответ
Значения функций nil в Golang
Last updated
Was this helpful?