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

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

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

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

Наши каналы: https://t.me/proglibrary/9197
Download Telegram
💬Как в Go реализована композиция (агрегация)? Чем она отличается от наследования?

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

📌В нашем примере:

Определен базовый тип Engine с двумя полями (Power и Type) и методом Start.
Определен тип Car, в который встраивается Engine.
В функции main создается экземпляр типа Car, и через этот экземпляр вызывается метод Start встроенного типа Engine, а также обращаются к полям встроенного типа Engine.

📌Таким образом, композиция в Go позволяет объединять простые типы в более сложные структуры, сохраняя при этом простоту и ясность кода.
👍14🔥3🤔3
🧑‍💻 Статьи для IT: как объяснять и распространять значимые идеи

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

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

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

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

📌Чтение из закрытого канала: когда мы пытаемся читать из закрытого канала, происходит следующее:

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

ch := make(chan int)
close(ch)
value, ok := <-ch
fmt.Println(value, ok)
// 0 false

📌Запись в закрытый канал: попытка записать в закрытый канал вызовет панику. Это означает, что запись в закрытый канал является недопустимой операцией.

ch := make(chan int)
close(ch)
ch <- 42
// panic: send on closed channel

📌Чтение из неинициализированного (nil) канала будет заблокировано, как и запись в неициализированный (nil) канал.

☝️Важно отслеживать состояния каналов, инициализировать и закрывать их по мере необходимости, чтобы избежать паник и непредсказуемого поведения.
👍8😁21
💬Что такое паника (panic) в Go? Какие операции автоматически возвращают панику и останавливают программу?

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

🔸Доступ к индексу за пределами массива/среза: попытка доступа к элементу массива или среза по индексу за пределами его размера приведет к панике.

arr := []int{1, 2, 3}
fmt.Println(arr[5])
// panic: runtime error: index out of range

🔸Type assertion:
неправильное приведение типа с помощью type assertion может вызвать панику.

var i interface{} = "hello"
fmt.Println(i.(int))
// panic: interface conversion: interface {} is string, not int

🔸Закрытие закрытого канала:
попытка закрыть уже закрытый канал вызовет панику.

ch := make(chan int)
close(ch)
close(ch)
// panic: close of closed channel

📌Обработка паники

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

func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Recovered from panic:", r)
}
}()
causePanic()
fmt.Println("This line will not be reached")
}

func causePanic() {
panic("This is a panic")
}


Если функция causePanic вызывает панику, функция defer будет вызвана перед завершением программы. Функция recover затем используется для захвата значения паники и предотвращения завершения программы.

👉 Подробнее
👍102
💬В чем разница между context.Background() и context.TODO()?

📌Разница между context.Background() и context.TODO() в основном заключается в их семантическом использовании.

👉context.Background() используется как стартовый контекст, а context.TODO() указывает на то, что контекст будет предоставлен позже, другими словами, когда неясно, какой контекст использовать, или он еще недоступен. С точки зрения функциональности они идентичны.
🔥13👍2
💬Что из себя представляет стабы (stubs) и моки (mock) в контексте тестирования в Go?

📌Стабы (stubs) и моки (mocks) являются техниками, используемыми для изоляции тестируемого кода от внешних зависимостей во время тестирования в Go.

🔸Стабы (stubs) — это фейковые объекты, которые предоставляют предопределенные ответы на вызовы методов во время тестирования.

package main

import "fmt"


type DatabaseStub struct{}

func (db *DatabaseStub) GetUserName(id int) string {
return "Alice"
}

type Database interface {
GetUserName(id int) string
}

func PrintUserName(db Database, id int) {
name := db.GetUserName(id)
fmt.Println(name)
}

func main() {
dbStub := &DatabaseStub{}
PrintUserName(dbStub, 1)
}


🔸Моки (mocks) — это более продвинутые фейковые объекты, которые, кроме предоставления предопределенных ответов, также проверяют, как и когда методы были вызваны в тестах, что помогает в проверке взаимодействия между объектами.

package main

import (
"github.com/stretchr/testify/mock"
"testing"
)

type DatabaseMock struct {
mock.Mock
}

func (db *DatabaseMock) GetUserName(id int) string {
args := db.Called(id)
return args.String(0)
}

func TestPrintUserName(t *testing.T) {
dbMock := new(DatabaseMock)
dbMock.On("GetUserName", 1).Return("Alice")

name := dbMock.GetUserName(1)

dbMock.AssertExpectations(t)
}


📌В первом примере создается стаб DatabaseStub, который имеет метод GetUserName. Во втором примере создается мок DatabaseMock с использованием библиотеки testify, который проверяет, был ли метод GetUserName вызван с правильным аргументом.
👍51
💬Что из себя представляют структурные теги в Go?

🔸Теги структур в Go — это метаданные, прикрепленные к полям структуры, которые могут быть использованы для предоставления дополнительной информации или инструкций внешним пакетам или библиотекам.

🔸Они представляют собой строковые литералы, расположенные в бэктиках (`` ` ``) в объявлении поля структуры.

📌Пример тега структуры в Go:

type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}


🔸В этом примере `json:"name"` и `json:"age"` являются тегами структуры для полей `Name` и `Age` соответственно. Эти теги могут быть использованы пакетом `encoding/json` для управления тем, как объекты `Person` сериализуются/десериализуются в/из JSON.

📌Теги структур могут быть использованы для различных целей, включая:

1. Контроль сериализации и десериализации: теги могут указывать, как поля должны быть сериализованы или десериализованы в форматы, такие как JSON или XML. Например, тег `json:"name,omitempty"` указывает, что поле `Name` должно быть сериализовано как `name` в JSON, и если поле пустое, его следует опустить.

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

3. Описания и документация: теги могут содержать документацию или описания полей.

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

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

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

👉 Подробнее
Please open Telegram to view this post
VIEW IN TELEGRAM
👍122
💬Что такое `json:,omitempty` в контексте структур Go?

🔹Если вы читали предыдущий пост, наверняка заметили упоминание аннотации `json:,omitempty`. Например:

type fruit struct {
Name string
Length int `json:,omitempty`

}

🔹`json:,omitempty` — это тег JSON для поля в структуре. Если при демаршаллинге данных JSON в структуру это конкретное поле пусто, оно будет игнорироваться. Без тега omitempty будет использоваться значение по умолчанию.

🔹В приведенном выше примере без тега omitempty поле длины будет заполнено значением int 0. Если тип пустого поля — строка, "" (пустая строка) будет значением по умолчанию. Аналогично, значение по умолчанию для логического типа — false, nil для указателя, интерфейса, среза и мапы.

👉 Подробнее
Please open Telegram to view this post
VIEW IN TELEGRAM
👍8🤔1
💬Какова цель функции 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