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

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

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

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

Наши каналы: https://t.me/proglibrary/9197
Download Telegram
💬 Какие особенности оператора break следует учитывать при использовании в сочетании со switch или select?

Рассмотрим пример. Мы используем switch внутри for. Когда индекс цикла получает значение 2, требуется прервать цикл:

for i := 0; i < 5; i++ {
fmt.Printf("%d ", i)
switch i {
default:
case 2: // Если i равен 2, то вызывается оператор break
break }
}


На первый взгляд код может казаться правильным, но он не приводит к выполнению ожидаемых действий. Оператор break не завершает цикл for, он завершает действие оператора switch. Следовательно, вместо итерации от 0 до 2 код выполняет итерацию от 0 до 4: 0 1 2 3 4.

💡break завершает выполнение самого последнего оператора for, switch или select. И здесь он прерывает действие switch.

Как написать код, который будет прерывать цикл, а не действие оператора switch? Самый идиоматический способ — использовать loop:

loop:  
for i := 0; i < 5; i++ {
fmt.Printf("%d ", i)
switch i {
default:
case 2:
break loop // Прерывается цикл, привязанный к loop, а не к switch }
}


☑️ Здесь мы связываем loop с циклом for. Поскольку мы указываем эту метку в операторе break, то он прерывает цикл, а не действие оператора switch. Новый код выведет 0 1 2, как и требовалось.

Прерывание не того оператора также может произойти и в случае с select, находящимся внутри цикла. В примере ниже мы хотим использовать select с двумя case и выйти из цикла, если контекст отменяется:

for {
select {
case <-ch:
// Какие-то действия
case <-ctx.Done():
break // Цикл прерывается, если контекст отменяется
}
}


Здесь самым внутренним оператором из списка for, switch, select является select, а не for. Поэтому цикл продолжается. Чтобы прервать сам цикл, используем loop:

loop:  
for {
select {
case <-ch:
// Какие-то действия
case <-ctx.Done():
break loop // Прерывается выполнение привязанного к loop цикла, а не select
}
}


Как и ожидалось, break приводит к выходу из цикла, а не к прерыванию выполнения select.
👍173🔥2👾1
💬 Что из себя представляет паттерн Throttle, применяемый в облачной разработке?

Паттерн Throttle (Дроссельная заслонка) ограничивает частоту вызовов функции некоторым предельным числом вызовов в единицу времени.

📌 Например:
◆ пользователю может быть разрешено обращаться к сервису не чаще, чем 10 раз в секунду;
◆ клиенту может быть позволено вызывать определенную функцию только один раз в каждые 500 миллисекунд;
◆ учетной записи может быть разрешено только три неудачные попытки входа в систему в течение 24 часов.

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

📌 Компоненты:

Effector — функция, частоту вызовов которой нужно ограничить.
Throttle — функция, принимающая Effector и возвращающая замыкание с той же сигнатурой, что и Effector.

📌 Пример кода:

type Effector func(context.Context) (string, error)

func Throttle(e Effector, max uint, refill uint, d time.Duration) Effector {
var tokens = max
var once sync.Once

return func(ctx context.Context) (string, error) {
if ctx.Err() != nil {
return "", ctx.Err()
}

once.Do(func() {
ticker := time.NewTicker(d)

go func() {
defer ticker.Stop()

for {
select {
case <-ctx.Done():
return

case <-ticker.C:
t := tokens + refill
if t > max {
t = max
}
tokens = t
}
}
}()
})
if tokens <= 0 {
return "", fmt.Errorf("too many calls")
}
tokens--

return e(ctx)
}
}
👍6
⚡️Свершилось: канал с книгами только по Go

Мы создали для вас канал, куда будем публиковать самые полезные книги только для Go-разработчиков. Подписывайтесь!

👉Книги для Go разработчиков
Please open Telegram to view this post
VIEW IN TELEGRAM
2🔥2👍1
💬 Что такое lvalue и rvalue в общем и в контексте Go?

🔹 lvalue (left value): в традиционном понимании, lvalue относится к выражению, которое ссылается на место в памяти, где хранится значение. В Go это может быть переменная, элемент массива, поле структуры или разыменованный указатель. Главное свойство lvalue заключается в том, что оно может находиться слева от оператора присваивания, то есть его можно использовать для присваивания или изменения значения.

📌 Пример:
var x int
x = 5 // 'x' здесь является lvalue, так как мы можем присвоить ему значение


🔹 rvalue (right value): rvalue, в свою очередь, обычно относится к данным, которые находятся справа от оператора присваивания. Это может быть литерал (например, число или строка), выражение, результат функции или любое значение, которое не имеет фиксированного места в памяти. В Go rvalue представляет значение, которое можно присвоить lvalue, но которое само по себе не может быть присвоено.

📌 Пример:
var y = x + 2 // 'x + 2' является rvalue, так как это выражение возвращает значение
👍91
💬 Что из себя представляет Interface Pollution в Go и как его определить?

Рассмотрим пример, который демонстрирует Interface Pollution, неправильно используя интерфейс, когда он не нужен.

type Server interface {
Start() error
Stop() error
Wait() error
}


Интерфейс Server определяет контракт для TCP-серверов. Проблема в том, что нам не нужен контракт, нам нужна реализация. К тому же этот интерфейс основан на существительном, а не на глаголе. Конкретные типы — это существительные, так как они представляют конкретную проблему. Интерфейсы описывают поведение, и Server — это не поведение.

📌 Некоторые способы определения Interface Pollution:

🔹 Пакет объявляет интерфейс, который соответствует полному API своего собственного конкретного типа.
🔹 Интерфейсы экспортируются, но конкретные типы, реализующие интерфейс, не экспортируются.
🔹 Функция фабрика для конкретного типа возвращает значение интерфейса с неэкспортируемым конкретным типом внутри.
🔹 Интерфейс можно удалить, и для пользователя API ничего не изменится.
🔹 Интерфейс не обеспечивает гибкость или адаптивность API к изменениям в будущем.
🥱8👍5
💬 Назовите общие случаи использования и злоупотребления дженериками в Go.

1️⃣ Структуры данных. Мы можем использовать дженерики, чтобы выделить тип элемента, например, если мы реализуем бинарное дерево, связанный список или кучу.
2️⃣ Функции, работающие со срезами, мапами и каналами любого типа. Например, функция объединения двух каналов будет работать с любым типом канала. Следовательно, можно использовать параметры типа, чтобы определить тип канала:

func merge[T any](ch1, ch2 <-chan T) <-chan T {
// ...
}


3️⃣ Факторизация поведения вместо типов. Пакет sort, например, содержит интерфейс sort.Interface, включающий в себя три метода:

type Interface interface {
Len() int
Less(i, j int) bool
Swap(i, j int
}


Этот интерфейс используется различными функциями: sort.Ints или sort.Float64s. Используя параметры типа, можно выделить действие по сортировке (например, определив структуру, содержащую срез, и функцию сравнения):

type SliceFn[T any] struct {
S []T // Используется параметр типа
Compare func(T, T) bool // Сравниваются два элемента T
}

func (s SliceFn[T]) Len() int { return len(s.S) }
func (s SliceFn[T]) Less(i, j int) bool { return s.Compare(s.S[i], s.S[j]) }
func (s SliceFn[T]) Swap(i, j int) { s.S[i], s.S[j] = s.S[j], s.S[i] }


Поскольку структура SliceFn реализует sort.Interface, можно отсортировать предоставленный срез с помощью функции sort.Sort(sort.Interface):

s := SliceFn[int]{
S: []int{3, 2, 1},
Compare: func(a, b int) bool {
return a < b
},
}
sort.Sort(s)
fmt.Println(s.S)

[1 2 3]


📌 Когда использовать дженерики не рекомендуется?

🔸При вызове метода с аргументом типа. Рассмотрим функцию, которая получает на входе io.Writer и вызывает метод Write:

func foo[T io.Writer](w T) {
b := getBytes()
_, _ = w.Write(b)
}


В этом случае использование дженериков не принесет коду никакой пользы. Нужно напрямую сделать значение аргумента w равным io.Writer.

🔸Когда это делает код более сложным. Дженерики никогда не бывают обязательными, и разработчики Go прекрасно жили без них более десяти лет.
👍71
💬 Опишите концепцию "package aliasing" и кейсы ее использования.

Она относится к возможности присваивания псевдонима (алиаса) импортируемому пакету.

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

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

📌 Как это работает:

При импорте пакета можно указать алиас перед путем к пакету. Например:

import fm "fmt"


Здесь fm является псевдонимом для пакета fmt. После этого, вместо использования fmt, мы можем использовать fm для доступа к функциям и типам из пакета fmt.

📌 Основные юзкейсы:

1. Избежание конфликтов имен: если мы импортируем два пакета с одинаковыми именами (например, две разные библиотеки db для работы с БД), мы можем использовать алиасы, чтобы различать их в коде.
import (
sqlDB "project/sql/db"
nosqlDB "project/nosql/db"
)


2. Упрощение длинных имен пакетов: некоторые пакеты имеют длинные пути импорта, и использование алиасов может сделать код более читаемым.

import (
mh "myproject/subproject/module/helpers"
)


3. Изменение стандартного имени пакета: в некоторых случаях, особенно при работе с генерируемым кодом или когда стандартное имя пакета не является описательным, алиасы могут помочь сделать код более понятным.
👍9🔥41
💬 Что такое указатель на указатель в Go?

В Go, указатель — это переменная, которая хранит адрес памяти другой переменной. Указатель на указатель — это переменная, которая хранит адрес памяти другого указателя, указывающий на некоторое значение или объект.

📌 Пример:

package main

import "fmt"

func main() {
a := 100
var b *int = &a // b — указатель на переменную a
var c **int = &b // c — указатель на указатель b

fmt.Println("Значение a:", a) // Исходное значение
fmt.Println("Адрес a:", &a) // Адрес переменной a
fmt.Println("Значение b:", b) // Адрес, хранящийся в b (адрес a)
fmt.Println("Разыменование b:", *b) // Разыменование b (значение a)
fmt.Println("Значение c:", c) // Адрес, хранящийся в c (адрес b)
fmt.Println("Разыменование c:", *c) // Разыменование c (значение b, т.е. адрес a)
fmt.Println("Двойное разыменование c:", **c) // Двойное разыменование c (значение a)
}


* a — обычная переменная типа int.
* b — указатель на int, который хранит адрес переменной a.
* c — указатель на указатель на int, который хранит адрес переменной b.

Таким образом, c является указателем на указатель. Он не только позволяет нам получить доступ к значению a через двойное разыменование (**c), но и изменять адрес, на который указывает b, что может быть полезно в некоторых сценариях, например, при передаче указателя в функцию для его модификации.
🔥14👍51
Самые полезные каналы для программистов в одной подборке!

Сохраняйте себе, чтобы не потерять 💾

🔥Для всех

Библиотека программиста — новости, статьи, досуг, фундаментальные темы
Книги для программистов
IT-мемы
Proglib Academy — тут мы рассказываем про обучение и курсы

#️⃣C#

Библиотека шарписта
Библиотека задач по C# — код, квизы и тесты
Библиотека собеса по C# — тренируемся отвечать на каверзные вопросы во время интервью и технического собеседования
Вакансии по C#, .NET, Unity Вакансии по PHP, Symfony, Laravel

☁️DevOps

Библиотека devops’а
Вакансии по DevOps & SRE
Библиотека задач по DevOps — код, квизы и тесты
Библиотека собеса по DevOps — тренируемся отвечать на каверзные вопросы во время интервью и технического собеседования

🐘PHP

Библиотека пхпшника
Вакансии по PHP, Symfony, Laravel
Библиотека PHP для собеса — тренируемся отвечать на каверзные вопросы во время интервью и технического собеседования
Библиотека задач по PHP — код, квизы и тесты

🐍Python

Библиотека питониста
Вакансии по питону, Django, Flask
Библиотека Python для собеса — тренируемся отвечать на каверзные вопросы во время интервью и технического собеседования
Библиотека задач по Python — код, квизы и тесты

Java

Библиотека джависта — полезные статьи по Java, новости и обучающие материалы
Библиотека Java для собеса — тренируемся отвечать на каверзные вопросы во время интервью и технического собеседования
Библиотека задач по Java — код, квизы и тесты
Вакансии для java-разработчиков

👾Data Science

Библиотека Data Science — полезные статьи, новости и обучающие материалы
Библиотека Data Science для собеса — тренируемся отвечать на каверзные вопросы во время интервью и технического собеседования
Библиотека задач по Data Science — код, квизы и тесты
Вакансии по Data Science, анализу данных, аналитике, искусственному интеллекту

🦫Go

Библиотека Go разработчика — полезные статьи, новости и обучающие материалы по Go
Библиотека Go для собеса — тренируемся отвечать на каверзные вопросы во время интервью и технического собеседования
Библиотека задач по Go — код, квизы и тесты
Вакансии по Go

🧠C++

Библиотека C/C++ разработчика — полезные статьи, новости и обучающие материалы по C++
Библиотека C++ для собеса — тренируемся отвечать на каверзные вопросы во время интервью и технического собеседования
Библиотека задач по C++ — код, квизы и тесты
Вакансии по C++

💻Другие профильные каналы

Библиотека фронтендера
Библиотека мобильного разработчика
Библиотека хакера
Библиотека тестировщика

💼Каналы с вакансиями

Вакансии по фронтенду, джаваскрипт, React, Angular, Vue
Вакансии для мобильных разработчиков
Вакансии по QA тестированию
InfoSec Jobs — вакансии по информационной безопасности

📁Чтобы добавить папку с нашими каналами, нажмите 👉сюда👈

🤖Также у нас есть боты:
Бот с IT-вакансиями
Бот с мероприятиями в сфере IT

Мы в других соцсетях:
🔸VK
🔸YouTube
🔸Дзен
🔸Facebook *
🔸Instagram *

* Организация Meta запрещена на территории РФ
2
💬 Что такое рефлексия в go и чем она полезна?

Рефлексия в Go реализована в пакете reflect и представляет собой механизм, позволяющий коду исследовать значения, типы и структуры во время выполнения, без заранее известной информации о них.

Рефлексия полезна в ситуациях, когда нам нужно работать с данными неизвестного типа, например, при сериализации/десериализации данных, реализации ORM систем и так далее.

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

📌 Простые примеры

🔸Определение типа переменной:

package main

import (
"fmt"
"reflect"
)

func main() {
x := 42
fmt.Println("Тип переменной x:", reflect.TypeOf(x))
}


В примере мы используем функцию reflect.TypeOf(), чтобы определить тип переменной x. Программа выведет int, так как x — целое число.

🔸Чтение и изменение значений:

package main

import (
"fmt"
"reflect"
)

func main() {
x := 42
v := reflect.ValueOf(&x).Elem() // Получаем reflect.Value

fmt.Println("Исходное значение x:", x)
v.SetInt(43) // Изменяем значение x
fmt.Println("Новое значение x:", x)
}


Здесь мы используем reflect.ValueOf() для получения reflect.Value переменной x, а затем изменяем её значение с помощью SetInt().

🔸Динамический вызов методов:

package main

import (
"fmt"
"reflect"
)

type MyStruct struct {
Field int
}

func (m *MyStruct) UpdateField(val int) {
m.Field = val
}

func main() {
x := MyStruct{Field: 10}

// Получаем reflect.Value структуры
v := reflect.ValueOf(&x)

// Получаем метод по имени
method := v.MethodByName("UpdateField")

// Вызываем метод с аргументами
method.Call([]reflect.Value{reflect.ValueOf(20)})

fmt.Println("Обновленное значение поля:", x.Field)
}


В этом примере мы создаем экземпляр структуры MyStruct, получаем метод UpdateField с помощью MethodByName и вызываем его динамически с помощью Call. Метод обновляет значение поля структуры.
👍13🔥4
💬 Что из себя представляют сигналы отмены в контексте пакета context?

Передача сигнала отмены — один из юзкейсов контекстов в Go. Допустим, нужно создать приложение, которое вызывает CreateFileWatcher(ctx context.Context, filename string) внутри другой горутины. Эта функция создает file watcher, который постоянно читает файл и отслеживает его обновления. Когда предоставленный контекст становится неактуальным или отменяется, эта функция обрабатывает его, чтобы закрыть дескриптор файла.

Наконец, когда происходит возврат из main, мы хотим, чтобы все обрабатывалось корректно путем закрытия этого файлового дескриптора. Поэтому нам нужно передать сигнал. Возможный подход — использовать context.WithCancel, возвращающий контекст (возвращается первая переменная), который отменяется после вызова функции cancel (возвращается вторая переменная):

func main() {
ctx, cancel := context.WithCancel(context.Background()) // создание контекста, который может быть отменен
defer cancel() // откладываем вызов cancel
go func() {
CreateFileWatcher(ctx, "foo.txt") // вызов функции с использованием созданного контекста
}()
// ...
}


Когда происходит возврат из main, происходит и вызов функции cancel для отмены контекста, переданного в CreateFileWatcher, — чтобы дескриптор файла корректно закрылся.
👍10
🏃 Самоучитель по Go для начинающих. Часть 2. Ресурсы для изучения Go с нуля

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

🔗 Читать статью
🔗 Ссылка на первую часть
👍10
💬 Что из себя представляет паттерн Fan-In (Мультиплексор), применяемый в облачной разработке?

Паттерн Fan-In мультиплексирует несколько входных каналов в один выходной канал.

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

📌 Компоненты:
Sources — набор из одного или нескольких входных каналов одного типа, которые может принять Funnel.
Destination — выходной канал того же типа, что и каналы в наборе Sources. Создается функцией Funnel.
Funnel — принимает набор Sources и сразу же возвращает Destination. Любые данные, поступающие из каналов в Sources, передаются в Destination.

📌 Реализация:
Funnel — функция, которая принимает до N входных каналов (Sources). Для каждого входного канала в Sources функция Funnel запускает отдельную горутину, читающую значения из назначенного ей канала и передающую их в выходной канал, общий для всех горутин (Destination).

Функция Funnel принимает переменное число аргументов — ноль или более каналов некоторого типа (int в нашем примере):

func Funnel(sources ...<-chan int) <-chan int {
dest := make(chan int) // Общий выходной канал

var wg sync.WaitGroup // Для автоматического закрытия dest, когда закроются все входящие каналы sources

wg.Add(len(sources)) // Установить размер WaitGroup

for _, ch := range sources { // Запуск горутины для каждого входного канала
go func(c <-chan int) {
defer wg.Done() // Уведомить WaitGroup, когда c закроется

for n := range c {
dest <- n
}
}(ch)
}

go func() { // Запустить горутину, которая закроет dest после закрытия всех входных каналов
wg.Wait()
close(dest)
}()

return dest
}


Для каждого входного канала Funnel запускает специальную горутину, которая читает значения из назначенного ей канала и пересылает их в dest, общий выходной канал для всех горутин.

📌 Использование функции Funnel:

func main() {
sources := make([]<-chan int, 0) // Создать пустой срез с каналами

for i := 0; i < 3; i++ {
ch := make(chan int)
sources = append(sources, ch) // Создать канал; добавить в срез sources
go func() { // Запустить горутину для каждого
defer close(ch) // Закрыть канал по завершении горутины
for i := 1; i <= 5; i++ {
ch <- i
time.Sleep(time.Second) }
}()
}

dest := Funnel(sources...)
for d := range dest {
fmt.Println(d)
}
}
👍12
💬 Как реализовать аргументы командной строки в Go?

Go предоставляет пакет стандартной библиотеки flag, который является мощным инструментом для обработки аргументов командной строки. Он позволяет легко определять и парсить аргументы.

🔸 Основные функции: flag.String, flag.Int, flag.Bool и т. д., используются для определения ожидаемых аргументов командной строки с указанием их типа.

🔸 Парсинг: функция flag.Parse() используется для анализа аргументов командной строки и присвоения их соответствующим переменным.

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

package main

import (
"flag"
"fmt"
)

func main() {
// Определение аргументов
host := flag.String("host", "localhost", "a string var")
port := flag.Int("port", 8080, "an int var")
verbose := flag.Bool("verbose", false, "a bool var")

// Парсинг аргументов
flag.Parse()

// Использование аргументов
fmt.Printf("Host: %s, Port: %d, Verbose: %t\n", *host, *port, *verbose)
}


📌 Основные преимущества:

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

🔸 Использование указателей для аргументов (flag.String, flag.Int и т. д.) обеспечивает прямой доступ к значениям после парсинга.

🔸 Библиотека позволяет задавать значения по умолчанию и описания для каждого аргумента, что улучшает понимание и использование командной строки.

🔸 Для более сложных сценариев использования аргументов командной строки можно использовать сторонние библиотеки, такие как cobra или urfave/cli, которые предоставляют дополнительные возможности и улучшенный синтаксис.
🔥71
🧑‍💻 Статьи для IT: как объяснять и распространять значимые идеи

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

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

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

👉Материалы регулярно дополняются, обновляются и корректируются. А еще мы отвечаем на все учебные вопросы в комментариях курса.
👍2