Библиотека Go для собеса | вопросы с собеседований
6.88K subscribers
225 photos
7 videos
1 file
435 links
Вопросы с собеседований по Go и ответы на них.

По рекламе: @proglib_adv

Учиться у нас: https://proglib.io/w/0b524a15

Для обратной связи: @proglibrary_feeedback_bot

Наши каналы: https://t.me/proglibrary/9197
Download Telegram
💬Какова цель функции init() в Go?

📌Функция init() в Go вызывается автоматически при инициализации пакета. В Go нет конструкторов в классическом понимании, как в некоторых других ЯП, но функция init() предлагает возможность выполнять необходимую начальную настройку.

📌Несколько ключевых моментов:

1. Автоматический вызов: функция init() вызывается автоматически перед вызовом main() и не требует явного вызова.

2. Использование: функции init() можно использовать для инициализации глобальных переменных, проверки или установки конфигурации, установки соединений с базами данных и других целей.

3. Несколько функций init(): в одном пакете можно иметь несколько функций init(). Они будут вызваны в том порядке, в котором объявлены в файле.

4. В случае зависимостей между пакетами, функции init() из импортированных пакетов выполняются перед функцией init() из основного пакета.

👉 Подробнее
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5
💬Как переобъявить переменные с помощью коротких объявлений?

📌В одной области видимости нельзя переобъявлять переменные, но это можно делать в объявлении нескольких переменных (multi-variable declarations), среди которых хотя бы одна — новая. Переобъявляемые переменные должны располагаться в том же блоке, иначе получится затенённая переменная.

📌Неправильно:

func main() {
one := 0
one := 1
// ошибка компиляции
}


📌Правильно:

func main() {
one := 0
one, two := 1,2

one,two = two,one
}
👍10
💬В чем разница между пакетами и модулями Go?

📌В Go, пакет — это коллекция исходных файлов .go в одной директории и с одинаковой директивой package, в то время как модуль — это дерево пакетов. Имя модуля задаётся в go.mod.

📌Файлы для одного пакета должны находиться в одной директории. Полное имя для пакета строится из имени модуля и пути к директории с файлами.

◆ Например, в go.mod указано module example.org/mylib, тогда все пакеты из модуля example.org/mylib должны быть в дочерних директориях относительно go.mod, и путь к директории определяет имя пакета.

◆ Например, в дереве исходников нашей библиотеки есть файлы в директоории ./cmd/root. Тогда эти файлы должны быть либо с директивой package root, либо package root_test. И полное имя пакета для этих файлов будет либо example.org/mylib/cmd/root, либо example.org/mylib/cmd/root_test (тесты для пакета example.org/mylib/cmd/root).
👍12
💬Предположим, что мы хотим выполнить код Go в какой-то момент в будущем или повторно через определенный интервал. Что необходимо использовать в таком случае?

📌Встроенные функции timer и ticker упрощают обе задачи, при этом таймеры предназначены для кейсов, когда мы хотим сделать что-то один раз в будущем, а тикеры — для кейсов, когда мы хотим сделать что-то повторно через определенные промежутки времени.

🔹Таймеры (первый пример):

◆ Таймеры представляют собой способ ожидания определенного времени перед выполнением действия.
◆ Создание таймера выполняется с помощью функции time.NewTimer(); в нее передаем длительность времени, которую необходимо ожидать.
◆ В примере таймер имеет канал C, в который будет отправлено значение после истечения заданного времени.
◆ Также возможно остановить таймер перед его активацией с помощью метода Stop().

🔹Тикеры (второй пример):

◆ Тикеры используются для выполнения действий через регулярные промежутки времени.
◆ Создание тикера также выполняется с помощью функции в пакете time, в данном случае time.NewTicker(), с указанием интервала между «тиками».
◆ Аналогично таймерам, тикеры имеют канал C, в который отправляется значение на каждом «тике».
◆ Тикеры можно остановить, используя метод Stop(), что предотвратит дальнейшее отправление значений.
👍9🥱4
💬Как обрабатывать сигналы Unix в Go?

🔸Обработка сигналов UNIX в Go обычно выполняется с использованием пакета os/signal.

📌Простой пример:

1. Импортируем пакет os/signal и syscall:

import (
"os"
"os/signal"
"syscall"
)


2. Создаем канал, который будет использоваться для получения сигналов:

signals := make(chan os.Signal, 1)

3. Используем функцию signal.Notify, чтобы указать, какие сигналы мы хотим обрабатывать:

signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM)

4. Используем select в горутине или в основном потоке программы для ожидания сигналов и реагирования на них:

select {
case sig := <-signals:

// обработка сигнала
fmt.Printf("received signal %v\n", sig)
}


📌Программа ожидает сигналы SIGINT и SIGTERM, и когда она их получит, выведет полученный сигнал. Это базовый пример того, как можно обрабатывать сигналы в Go.

👉 Подробнее
👍13
💬 Что возвращает функция len() в Go, если ей передана строка в кодировке UTF-8?

🔸Функция len() в Go возвращает количество байтов в строке, а не количество рун (символов Unicode).

🔸Если строка закодирована в UTF-8, каждый символ может занимать от 1 до 4 байтов.

🔸Таким образом, если в строке UTF-8 присутствуют многобайтовые символы, функция len() вернет значение, большее, чем количество символов в строке.


import (
"fmt"
"unicode/utf8"
)

func main() {
s := "世界"
fmt.Println("Byte length:", len(s))
fmt.Println("Rune count:", utf8.RuneCountInString(s))
}


🔸Результат:
Byte length: 6
Rune count: 2
👍6
💬Какие риски возникают при использовании нескольких тегов полей в одной структуре?

📌Основная проблема при использовании нескольких тегов полей в одной структуре заключается в том, что это может привести к тесной связанности между различными слоями нашего приложения.

📌Простой пример:


type Post struct {
Title string `json:"title" bson:"title"`
SubTitle string `json:"subtitle" bson:"subtitle"`
}


🔸В примере у структуры Post для каждого поля есть два тега поля: json и bson. Эти теги могут использоваться в разных целях, например, для отправки HTTP-ответов (используя json) и обработки демаршализации MongoDB (используя bson).

🔸При использовании подобных тегов слой HTTP-ответа (веб-сервер) и слой хранения (MongoDB) становятся тесно связанными. Если мы хотим изменить title на, например, shortTitle, нам нужно будет обновить и HTTP-ответ (что также может повлиять на клиентов, обрабатывающих ответ), и хранение в MongoDB.
👍1
🧑‍💻 Статьи для IT: как объяснять и распространять значимые идеи

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

Что: семь модулей, посвященных написанию, редактированию, иллюстрированию и распространению публикаций.

Для кого: для авторов, копирайтеров и просто программистов, которые хотят научиться интересно рассказывать о своих проектах.

👉Материалы регулярно дополняются, обновляются и корректируются. А еще мы отвечаем на все учебные вопросы в комментариях курса.
💬Где полезен встроенный метод recover в Go?

Встроенный метод recover действительно полезен в отложенных функциях, но не рекомендуется вызывать его напрямую с помощью ключевого слова defer.

// Плохо

func main() {
defer recover()

panicCode()
}

// Лучше


func handlePanic() {
if panicInfo := recover(); panicInfo != nil {
fmt.Println(panicInfo)
}
}

func main() {
defer handlePanic()

panicCode()
}


📌Кроме того:

🔸Обработка ошибок: в ситуациях, когда паника является возможной и ожидаемой, например, при работе с внешними ресурсами или библиотеками, которые могут вызывать панику, recover может быть использован для возврата ошибки вместо завершения программы.

🔸Неотложное восстановление: в демонизированных приложениях или долго работающих процессах, где стабильность имеет критическое значение, recover может использоваться для обеспечения того, чтобы временная ошибка не привела к полному сбою системы.

🔸Откат транзакций: в операциях, которые должны быть атомарными, таких как обновления базы данных, recover может использоваться для обнаружения паники и выполнения отката транзакции, чтобы поддерживать целостность данных.

🔸Логирование и отладка: recover может использоваться для перехвата паники, логирования диагностической информации и затем повторного вызова паники, чтобы стандартный процесс обработки ошибок мог продолжить работу.
8👍1
💬Для чего в Go предназначена директива "//go:embed"?

📌Начиная с Go 1.16, директива //go:embed представляет собой специальный комментарий, который используется для встраивания файлов и директорий непосредственно в скомпилированный бинарный файл Go.

📌Во время выполнения, эти ресурсы становятся доступны через переменную, к которой применяется директива. Нам не нужно заботиться о механизмах пути к файлу или открытии и чтении файлов во время выполнения, что упрощает распространение программ, требующих доступа к внешним файлам.

Простой пример:


package main

import (
"embed"
"io/fs"
"net/http"
)

//go:embed static/*
var staticFiles embed.FS

func main() {
// Используем встроенные файлы напрямую
http.Handle("/", http.FileServer(http.FS(staticFiles)))
http.ListenAndServe(":8080", nil)
}


Все файлы в директории static встраиваются в бинарный файл. Директива //go:embed должна быть расположена непосредственно перед объявлением переменной без пустых строк между комментарием и объявлением. Это позволяет использовать staticFiles как файловую систему внутри Go-кода.

Этот функционал особенно полезен, когда мы хотим создать самодостаточное приложение, которое не зависит от внешних файлов во время выполнения.

👉 Подробнее
👍13🔥2
💬Что в себя включает реализация кастомного обработчика логов с log/slog?

🔸Пакет стандартной библиотеки log/slog состоит из двух частей: внешней и внутренней. Внешняя часть реализована типом Logger, собирает структурированную информацию логов, и передает их внутренней части, реализации интерфейса Handler.

🔸Пакет slog поставляется с двумя встроенными обработчиками, которых обычно должно быть достаточно. Но нам может потребоваться написать кастомный обработчик.

📌Для создания кастомного обработчика существует 4 метода:

1️⃣ Enabled: представляет собой оптимизацию, позволяющую избежать ненужной работы. Метод вывода Logger будет вызываться Enabled перед обработкой любого из своих аргументов, чтобы проверить, следует ли продолжать работу.

Enabled(context.Context, Level) bool

2️⃣ Handle: этому методу передается файл Record, содержащий всю информацию, которая должна быть зарегистрирована для одного вызова метода вывода Logger.

Handle(context.Context, Record) error

3️⃣ WithAttrs. Одной из оптимизаций производительности slog является поддержка атрибутов предварительного форматирования. Метод Logger.With преобразует пары ключ-значение в Attrs, а затем вызывает Handler.WithAttrs.

Обработчик может сохранить атрибуты для последующего использования методом Handle или может воспользоваться возможностью отформатировать атрибуты сейчас, один раз, а не делать это повторно при каждом вызове Handle.

WithAttrs(attrs []Attr) Handler

4️⃣ WithGroup: Logger.WithGroup вызывает Handler.WithGroup напрямую, с тем же аргументом, именем группы. Обработчик должен запомнить имя, чтобы использовать его для определения всех последующих атрибутов.

WithGroup(name string) Handler

👉 Подробнее
2👍1
💬В чем разница между длиной и емкостью среза в Go?

📌Внутри срез содержит указатель на резервный массив, а также длину и емкость👇

Длина среза указывает на количество элементов, которые в данный момент содержатся в срезе. Это число элементов, доступных для непосредственного использования и итерации.

Емкость среза отражает общее количество элементов, которые срез может содержать, не требуя дополнительного выделения памяти.

◽️Если при добавлении новых элементов в срез его длина превышает емкость, система выделит новый, больший участок памяти для этого среза, и старый участок памяти будет освобожден.

◽️Важно отметить, что длина среза не может превышать его емкость. В Go есть встроенные функции len и cap, которые позволяют получить длину и емкость среза.

◽️Когда мы создаем срез, мы можем указать как его длину, так и емкость. Если указать только длину, емкость будет установлена равной длине. Мы можем создавать срезы с начальной емкостью больше длины, чтобы оптимизировать производительность при последующем добавлении элементов, минимизируя количество операций выделения памяти.

📌Простой пример:
s := make([]int, 3, 6)
fmt.Println(s) // [0 0 0]


◽️В данном случае создается массив из шести элементов, но поскольку длина была установлена ​​равной 3, Go инициализирует только первые три элемента. Кроме того, поскольку срез является типом []int, первые три элемента инициализируются нулевым значением int: 0.

👉О распространенных ошибках при работе со срезами читайте здесь.
👍7🔥2🥱2
💬Что из себя представляет Принцип инверсии зависимостей (Dependency Inversion Principle, DIP) в Go?

DIP — это один из пяти принципов SOLID, который направлен на уменьшение зависимостей в коде. Суть в том, что:

1️⃣Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба типа модулей должны зависеть от абстракций.
2️⃣Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.
3️⃣Другими словами, зависимости должны быть инвертированы вниз, в сторону абстракций.

📌В контексте Go, это может быть реализовано с использованием интерфейсов. Например:

// Зависимость (абстракция)
type Database interface {
Connect() string
}

// Конкретная реализация
type MySQL struct {}

func (db MySQL) Connect() string {
return "Connected to MySQL"
}

// Модуль высокого уровня
type Application struct {
db Database
}

func NewApplication(database Database) Application {
return Application{db: database}
}

func (app Application) Start() {
fmt.Println(app.db.Connect())
}

func main() {
mysql := MySQL{}
app := NewApplication(mysql)
app.Start()
}


В этом примере Application является модулем высокого уровня, и он зависит от абстракции Database, а не от конкретной реализации MySQL. Такой подход позволяет легко заменить MySQL на другую реализацию Database, не изменяя при этом код Application.

Это улучшает гибкость и расширяемость кода, а также облегчает тестирование, т. к. можно использовать моки или фейки для интерфейса Database при тестировании Application.
👍20
💬Что из себя представляет graceful shutdown (правильное завершение работы) в контексте Go?

🔹Graceful shutdown — это процедура безопасного завершения работы приложения, где важно корректно обработать все текущие операции перед остановкой.

🔹Это особенно важно в высоконагруженных и распределенных системах, где внезапное прерывание может привести к потере данных или другим проблемам.

📌В Go, graceful shutdown обычно достигается следующим образом:

1️⃣Перехват сигналов ОС: настраиваем обработчики для сигналов ОС, таких как SIGINT (сигнал прерывания, обычно инициируемый нажатием Ctrl+C) и SIGTERM (сигнал запроса на завершение, отправляемый, например, системными утилитами для остановки процесса). Это позволяет приложению реагировать на попытки его завершения.

2️⃣Завершение текущих задач: после получения сигнала о завершении приложение пытается корректно завершить текущие операции. Это может включать в себя завершение обработки текущих запросов, закрытие открытых соединений и освобождение ресурсов.

3️⃣Отказ от новых запросов: во время graceful shutdown приложение перестает принимать новые запросы, чтобы сосредоточиться на корректном завершении текущих операций.

4️⃣Синхронизация горутин: если приложение использует горутины, необходимо убедиться, что они все завершили свою работу перед остановкой программы. Это часто достигается с помощью каналов и синхронизации.

5️⃣Освобождение ресурсов: перед полным завершением работы приложение должно корректно освободить все используемые ресурсы, такие как файловые дескрипторы, соединения с базами данных и т. д.

🔹Примером реализации graceful shutdown в Go может быть использование пакета context для контроля времени жизни горутин и использование пакета net/http для управления HTTP-сервером таким образом, чтобы он корректно завершал обработку текущих запросов перед остановкой.
👍16
💬Представьте, что вам необходимо внедрить информацию о версии и другие метаданные в ваше Go-приложение во время сборки, не изменяя исходный код. Как это реализовать?

📌Мы можем использовать флаг -ldflags, который позволяет управлять поведением компоновщика при сборке Go-программ. Он позволяет определять опции сборки на этапе компиляции.

📌Простые юзкейсы:

Установка значения переменной: мы можем установить значение переменной во время компиляции. Например, go build -ldflags "-X main.version=1.0.0" устанавливает переменную version в пакете main в значение 1.0.0.

Уменьшение размера бинарного файла: использование go build -ldflags "-w -s" позволяет уменьшить размер исполняемого файла, отключая отладочную информацию и символы таблицы.

👉 Подробнее
👍10
💬В чем разница между nil и пустым срезом в Go?

◾️Чтобы избежать распространенных ошибок, важно понимать разницу между nil и пустым срезом. Оба представляют собой срезы нулевой длины и нулевой емкости, но только nil срез не требует выделения памяти.

◾️Nil срез равен nil, в то время как пустой срез имеет нулевую длину. Nil срез является пустым, но пустой срез не обязательно является nil.

📌Мы можем инициализировать срез в зависимости от контекста, используя:

☑️ var s []string, если мы не уверены в окончательной длине и срез может быть пустым
☑️ []string(nil) как синтаксический сахар для создания nil и пустого среза
☑️ make([]string, length), если будущая длина известна

◾️[]string{} следует избегать, если мы инициализируем срез без элементов.
👍5
💬 Если ключ или значение типа map имеют размер более 128 байт, каким образом Go их будет хранить?

📌Если ключ или значение мапы превышает 128 байт, Go не сохранит его непосредственно в бакете мапы. Вместо этого Go сохраняет указатель на ключ или значение.

📌Хоть все происходит под капотом, это может значительно повлиять на производительность и управление памятью.

👉 Читайте подробнее об утечках памяти при работе с мапами
👍9🔥1
💬Как использовать операторы == и != для эффективного сравнения значений в Go?

📌Мы можем использовать эти операторы с операндами, которые сравнимы:

Логические: равны ли два логических значения.
Числовые (int, float, complex): равны ли два числовых значения.
Строки: равны ли две строки.
Каналы: созданы ли два канала одним вызовом make или оба равны nil.
Интерфейсы: имеют ли два интерфейса идентичные динамические типы и равные динамические значения или оба равны nil.
Указатели: указывают ли два указателя на одно и то же значение в памяти или оба равны nil.
Структуры и массивы: состоят ли они из аналогичных типов.

📌Также мы можем использовать операторы <=, >=, < и > с числовыми типами для сравнения значений и со строками для сравнения их лексического порядка. Если операнды несравнимы, мы должны использовать другие варианты, такие как рефлексия.

📌Например, в Go мы можем использовать reflect.DeepEqual. Эта функция сообщает, равны ли два элемента, рекурсивно обходя два значения. Элементы, которые она принимает, это базовые типы, массивы, структуры, срезы, мапы, указатели, интерфейсы и функции. Однако основной недостаток — это производительность.

📌Важно помнить, что в стандартной библиотеке есть некоторые существующие методы сравнения, такие как bytes.Compare, slices.Compare и другие.
5👍2
💬Как эффективно инициализировать тип map в Go?

🔸Мапа представляет собой неупорядоченную коллекцию пар ключ-значение, в которой все ключи различны. Под капотом мапа основана на структуре данных хеш-таблицы, которая в свою очередь представляет собой массив бакетов, где каждый бакет — это указатель на массив пар ключ-значение.

🔸Если мы заранее знаем количество элементов, которые будет содержать мапа, эффективнее будет создать ее, указав начальный размер. Это позволяет избежать потенциального расширения мапы, что довольно сложно с точки зрения вычислений, поскольку требует перераспределения достаточного пространства памяти и перебалансировки всех элементов.
7👍3🥱1