💬 Какие подводные камни могут возникнуть при работе с циклом range в Go?
📌 Синтаксис цикла
◆ Рассмотрим пример, где к срезу добавляется элемент, по которому мы выполняем итерацию.
🤔 Завершится ли этот цикл?
◆ Чтобы понять суть, следует помнить, что при использовании цикла
◆ В этом контексте слово «вычисляется» означает, что предоставленное выражение копируется во временную переменную, а затем цикл
◆ Каждый шаг приводит к добавлению нового элемента. Но за три шага мы прошлись по всем его элементам. Длина временного среза, используемого в
◆ Такое поведение отличается от классического цикла
◆ В этом примере цикл никогда не закончится. Значение выражения
💡 Чтобы правильно использовать циклы в Go, важно помнить об этой разнице. При использовании
📌 Синтаксис цикла
range
требует наличия выражения. Например, в цикле for i, v := range exp
, exp
— это выражение. Это может быть строка, массив, указатель на массив, срез, map или канал. ◆ Рассмотрим пример, где к срезу добавляется элемент, по которому мы выполняем итерацию.
🤔 Завершится ли этот цикл?
s := []int{0, 1, 2}
for range s {
s = append(s, 10)
}
◆ Чтобы понять суть, следует помнить, что при использовании цикла
range
указываемое выражение вычисляется только один раз — перед началом цикла. ◆ В этом контексте слово «вычисляется» означает, что предоставленное выражение копируется во временную переменную, а затем цикл
range
выполняет итерации. В этом примере при вычислении выражения s
результатом будет копия среза. Цикл range
использует эту временную переменную. Исходный срез s
также обновляется во время каждой итерации. ◆ Каждый шаг приводит к добавлению нового элемента. Но за три шага мы прошлись по всем его элементам. Длина временного среза, используемого в
range
, остается равна 3
, поэтому цикл завершается после трех итераций.◆ Такое поведение отличается от классического цикла
for
:s := []int{0, 1, 2}
for i := 0; i < len(s); i++ {
s = append(s, 10)
}
◆ В этом примере цикл никогда не закончится. Значение выражения
len(s)
вычисляется во время каждой итерации, и раз мы продолжаем добавлять элементы, то никогда не достигнем состояния завершения цикла. 💡 Чтобы правильно использовать циклы в Go, важно помнить об этой разнице. При использовании
range
помните, что вышеописанное поведение (выражение вычисляется только один раз) также применимо ко всем типам данных.🔥26👍5❤4
💬 Что такое deadline в контексте Go context?
🔸
•
•
🔸Семантика
🔸Рассмотрим пример, который каждые 4 секунды получает от радара данные о позиции самолета. Получив позицию, мы хотим поделиться ею с другими приложениями, для которых интерес представляет только последняя позиция.
🔸В нашем распоряжении есть интерфейс
🔸Этот метод принимает контекст и позицию. Предполагается, что конкретная реализация вызывает функцию для публикации сообщения брокеру (например, использование Sarama для публикации сообщения Kafka).
🔸Эта функция контекстно зависимая, это означает, что она может отменить запрос после отмены контекста. Предполагая, что мы не получаем существующий контекст, что нужно предоставить методу
🔸Создаваемый контекст должен сообщать о ней через 4 секунды, а если мы не смогли опубликовать позицию, то следует остановить вызов
🔸Код создает контекст с помощью функции
🔸В чем смысл вызова функции
🔸
deadline
указывает на момент времени, определяемый одним из следующих способов:•
time.Duration
с настоящего момента (например, через 250 мс); •
time.Time
(например, 2023-02-07 00:00:00 UTC).🔸Семантика
deadline
означает, что текущая деятельность должна быть остановлена, если этот крайний срок наступил. «Деятельность» — это, например, запрос типа ввод/вывод или горутина в состоянии ожидания получения сообщения из канала.🔸Рассмотрим пример, который каждые 4 секунды получает от радара данные о позиции самолета. Получив позицию, мы хотим поделиться ею с другими приложениями, для которых интерес представляет только последняя позиция.
🔸В нашем распоряжении есть интерфейс
publisher
, содержащий в себе одинединственный метод:type publisher interface {
Publish(ctx context.Context, position flight.Position) error
}
🔸Этот метод принимает контекст и позицию. Предполагается, что конкретная реализация вызывает функцию для публикации сообщения брокеру (например, использование Sarama для публикации сообщения Kafka).
🔸Эта функция контекстно зависимая, это означает, что она может отменить запрос после отмены контекста. Предполагая, что мы не получаем существующий контекст, что нужно предоставить методу
Publish
в качестве аргумента контекста? 🔸Создаваемый контекст должен сообщать о ней через 4 секунды, а если мы не смогли опубликовать позицию, то следует остановить вызов
Publish
:type publishHandler struct {
pub publisher
}
func (h publishHandler) publishPosition(position flight.Position) error {
ctx, cancel := context.WithTimeout(context.Background(), 4*time.Second)
defer cancel()
return h.pub.Publish(ctx, position)
}
🔸Код создает контекст с помощью функции
context.WithTimeout
, которая принимает тайм-аут и контекст. Поскольку publishPosition
не получает существующий контекст, мы создаем его из пустого контекста с помощью context.Background
. Между тем context.WithTimeout
возвращает две переменные: созданный контекст и функцию отмены func()
, которая отменит контекст после вызова. Передача созданного контекста в метод Publish
должна произойти не позднее чем через 4 секунды.🔸В чем смысл вызова функции
cancel
как функции defer
? WithTimeout
создает горутину, которая будет храниться в памяти в течение 4 секунд или до тех пор, пока не будет вызвана cancel
. Следовательно, вызов cancel
в качестве функции defer
означает, что при выходе из родительской функции контекст будет отменен, а созданная горутина остановлена. Это мера предосторожности, чтобы при возвращении мы не оставили в памяти сохраненные объекты.👍12
💬 В Go существует несколько способов возврата структур или их частей. Назовите основные.
1. Возврат копии структуры:
Здесь функция
2. Возврат указателя на структуру:
В этом случае функция
3. Изменение структуры, переданной по указателю:
Функция
4. Возврат части структуры:
Здесь функция
5. Возврат через интерфейс:
Предположим, у нас есть интерфейс
Функция
6. Использование срезов и мап структур:
7. Возврат структуры через канал:
Здесь функция отправляет структуру в канал, что позволяет использовать её в конкурентных операциях или между горутинами.
💡Эти подходы могут комбинироваться и адаптироваться под различные сценарии использования, учитывая требования к производительности и управлению памятью.
👉 Вдохновлено вопросом на StackOverflow
1. Возврат копии структуры:
type MyStruct struct { Value int }
func returnCopy() MyStruct {
return MyStruct{Value: 1}
}
Здесь функция
returnCopy
возвращает копию структуры MyStruct
. Изменения возвращаемой копии не затронут оригинал.2. Возврат указателя на структуру:
func returnPointer() *MyStruct {
return &MyStruct{Value: 2}
}
В этом случае функция
returnPointer
возвращает указатель на структуру MyStruct
. Это позволяет избежать копирования и работать непосредственно с объектом.3. Изменение структуры, переданной по указателю:
func modifyStruct(s *MyStruct) {
s.Value = 3
}
Функция
modifyStruct
ожидает указатель на структуру и изменяет её напрямую. Это позволяет функции влиять на исходный объект.4. Возврат части структуры:
func returnValue(s MyStruct) int {
return s.Value
}
Здесь функция
returnValue
возвращает только значение поля Value
из структуры.5. Возврат через интерфейс:
Предположим, у нас есть интерфейс
MyInterface
, и структура MyStruct
его реализует.type MyInterface interface { DoSomething() }
type MyStruct struct { /* ... */ }
func (m MyStruct) DoSomething() { /* ... */ }
func returnInterface() MyInterface {
return MyStruct{}
}
Функция
returnInterface
возвращает экземпляр MyStruct
, но тип возврата — интерфейс MyInterface
.6. Использование срезов и мап структур:
func returnSlice() []MyStruct {
return []MyStruct{{Value: 4}, {Value: 5}}
}
func returnMap() map[string]MyStruct {
return map[string]MyStruct{"first": {Value: 6}, "second": {Value: 7}}
}
7. Возврат структуры через канал:
func returnThroughChannel(ch chan MyStruct) {
ch <- MyStruct{Value: 8}
}
Здесь функция отправляет структуру в канал, что позволяет использовать её в конкурентных операциях или между горутинами.
💡Эти подходы могут комбинироваться и адаптироваться под различные сценарии использования, учитывая требования к производительности и управлению памятью.
👉 Вдохновлено вопросом на StackOverflow
Stack Overflow
Pointers vs. values in parameters and return values
In Go there are various ways to return a struct value or slice thereof. For individual ones I've seen:
type MyStruct struct {
Val int
}
func myfunc() MyStruct {
return MyStruct{Val: 1}
}
...
type MyStruct struct {
Val int
}
func myfunc() MyStruct {
return MyStruct{Val: 1}
}
...
👍12
💬 Что из себя представляет паттерн Retry в Go?
Паттерн
Любое взаимодействие программных компонентов ненадежно. Вызываемый компонент может быть временно недоступен или возвращать различные ошибки. Особенно если взаимодействие происходит по сети.
Некоторые типы компонентов обязаны быть устойчивы к временным сбоям, которые могут случаться в их окружении. Они должны иметь возможность повторять запросы или восстанавливать соединения.
📌 Компоненты
🔸
🔸
📌Пример кода
Сигнатура функции
Функция
Возможно, вы сами заметили, почему функция
Чтобы использовать
Функция
Паттерн
Retry
(Повтор) учитывает возможный временный характер ошибки в распределенной системе и осуществляет повторные попытки выполнить неудачную операцию.Любое взаимодействие программных компонентов ненадежно. Вызываемый компонент может быть временно недоступен или возвращать различные ошибки. Особенно если взаимодействие происходит по сети.
Некоторые типы компонентов обязаны быть устойчивы к временным сбоям, которые могут случаться в их окружении. Они должны иметь возможность повторять запросы или восстанавливать соединения.
📌 Компоненты
🔸
Effector
— функция, взаимодействующая с сервисом.🔸
Retry
— функция, принимающая Effector
.📌Пример кода
Сигнатура функции
Effector
:type Effector func(context.Context) (string, error)
Функция
Retry
:func Retry(effector Effector, retries int, delay time.Duration) Effector {
return func(ctx context.Context) (string, error) {
for r := 0; ; r++ {
response, err := effector(ctx)
if err == nil || r >= retries {
return response, err
}
log.Printf("Attempt %d failed; retrying in %v", r + 1, delay)
select {
case <-time.After(delay):
case <-ctx.Done():
return "", ctx.Err()
}
}
}
}
Возможно, вы сами заметили, почему функция
Retry
имеет такую стройную реализацию: она возвращает функцию, но у этой функции нет внешнего состояния.Чтобы использовать
Retry
, необходимо реализовать функцию, которая выполняет потенциально неудачную операцию, чья сигнатура соответствует типу Effector
. Выберем на эту роль функцию EmulateTransientError
:var count int
func EmulateTransientError(ctx context.Context) (string, error) {
count++
if count <= 3 {
return "intentional fail", errors.New("error")
} else {
return "success", nil
}
}
func main() {
r := Retry(EmulateTransientError, 5, 2*time.Second)
res, err := r(context.Background())
fmt.Println(res, err)
}
Функция
main
передает функцию EmulateTransientError
в Retry
и сохраняет полученную функцию в переменной r
. Когда будет вызвана функция r
, она вызовет EmulateTransientError
и повторит попытку после задержки, если EmulateTransientError
вернет ошибку, в соответствии с логикой повтора, показанной выше. Наконец, после четвертой попытки EmulateTransientError
возвращает nil
в качестве ошибки и завершает работу.👍18❤1
💬 Как выглядит сокращенная форма объявления переменных в Go?
🔸 Go поддерживает синтаксический сахар, позволяющий одновременно объявлять переменные и присваивать им значения внутри функций: оператор "
🔸 В общем случае сокращенная форма объявления имеет вид:
🔸 С его помощью можно объявить и одну, и сразу несколько переменных:
◆ С инициализацией:
◆ Сразу несколько переменных:
🔸 На практике сокращенная форма является наиболее распространенным способом объявления и инициализации переменных; ключевое слово
💡"
💡Если краткая форма объявления содержит слева смесь новых и существующих переменных, то она действует как форма присваивания новых значений существующим переменным.
🔸 Go поддерживает синтаксический сахар, позволяющий одновременно объявлять переменные и присваивать им значения внутри функций: оператор "
:=
" вместо объявления var
с неявным типом.🔸 В общем случае сокращенная форма объявления имеет вид:
имя := выражение
🔸 С его помощью можно объявить и одну, и сразу несколько переменных:
◆ С инициализацией:
percent := rand.Float64() * 100.0
◆ Сразу несколько переменных:
x, y := 0, 2
🔸 На практике сокращенная форма является наиболее распространенным способом объявления и инициализации переменных; ключевое слово
var
обычно используется либо для объявления локальных переменных, когда требуется явно указать тип, либо для объявления переменных, которым значения будут присвоены позже.💡"
:=
" — это объявление, а "=
" — присваивание. Попытка повторно использовать оператор ":=
" для присваивания нового значения существующей переменной завершится ошибкой во время компиляции.💡Если краткая форма объявления содержит слева смесь новых и существующих переменных, то она действует как форма присваивания новых значений существующим переменным.
👍5😁3
🧑💻 Статьи для IT: как объяснять и распространять значимые идеи
Напоминаем, что у нас есть бесплатный курс для всех, кто хочет научиться интересно писать — о программировании и в целом.
Что: семь модулей, посвященных написанию, редактированию, иллюстрированию и распространению публикаций.
Для кого: для авторов, копирайтеров и просто программистов, которые хотят научиться интересно рассказывать о своих проектах.
👉Материалы регулярно дополняются, обновляются и корректируются. А еще мы отвечаем на все учебные вопросы в комментариях курса.
Напоминаем, что у нас есть бесплатный курс для всех, кто хочет научиться интересно писать — о программировании и в целом.
Что: семь модулей, посвященных написанию, редактированию, иллюстрированию и распространению публикаций.
Для кого: для авторов, копирайтеров и просто программистов, которые хотят научиться интересно рассказывать о своих проектах.
👉Материалы регулярно дополняются, обновляются и корректируются. А еще мы отвечаем на все учебные вопросы в комментариях курса.
💬 Что из себя представляет паттерн Circuit Breaker, применяемый в облачной разработке?
Circuit Breaker автоматически отключает сервисные функции в ответ на вероятную неисправность, чтобы предотвратить более крупные или каскадные отказы, устранить повторяющиеся ошибки и обеспечить разумную реакцию на ошибки.
📌 Компоненты
◆
◆
По сути, Circuit Breaker — это просто специализированный паттерн Adapter, в котором функция
Подобно электрическому выключателю, от которого этот паттерн получил свое название,
📌 Пример кода
Начнем с определения типа
Однако она должна включать ошибку в список возвращаемых значений:
В примере функция
Circuit Breaker автоматически отключает сервисные функции в ответ на вероятную неисправность, чтобы предотвратить более крупные или каскадные отказы, устранить повторяющиеся ошибки и обеспечить разумную реакцию на ошибки.
📌 Компоненты
◆
Circuit
— функция, взаимодействующая с сервисом.◆
Breaker
— функция-замыкание с той же сигнатурой, что и Circuit
.По сути, Circuit Breaker — это просто специализированный паттерн Adapter, в котором функция
Breaker
обертывает Circuit
и добавляет дополнительную логику обработки ошибок.Подобно электрическому выключателю, от которого этот паттерн получил свое название,
Breaker
имеет два возможных состояния: замкнуто и разомкнуто. В замкнутом состоянии все работает как обычно. Все запросы, полученные от клиента с помощью Breaker
, передаются в Circuit
без изменений, а все ответы от Circuit
возвращаются обратно клиенту. В разомкнутом состоянии Breaker
не передает запросы в Circuit
, а просто «быстро терпит неудачу», возвращая информативное сообщение об ошибке.📌 Пример кода
Начнем с определения типа
Circuit
, определяющего сигнатуру функции, которая взаимодействует с БД или другой вышестоящей службой. На практике эта сигнатура может быть любой, соответствующей нашим требованиям. Однако она должна включать ошибку в список возвращаемых значений:
type Circuit func(context.Context) (string, error)
В примере функция
Circuit
принимает экземпляр Context
. Функция Breaker
принимает любую функцию, соответствующую определению типа Circuit
, и целое число без знака, представляющее количество отказов, следующих друг за другом, после которых должно произойти автоматическое размыкание цепи. В ответ она возвращает другую функцию, которая также соответствует определению типа Circuit
:func Breaker(circuit Circuit, failureThreshold uint) Circuit {
var consecutiveFailures int = 0
var lastAttempt = time.Now()
var m sync.RWMutex
return func(ctx context.Context) (string, error) {
m.RLock() // Установить "блокировку чтения"
d := consecutiveFailures - int(failureThreshold)
if d >= 0 {
shouldRetryAt := lastAttempt.Add(time.Second * 2 << d)
if !time.Now().After(shouldRetryAt) {
m.RUnlock()
return "", errors.New("service unreachable")
}
}
m.RUnlock() // Освободить блокировку чтения
response, err := circuit(ctx) // Послать запрос, как обычно
m.Lock() // Заблокировать общие ресурсы
defer m.Unlock()
lastAttempt = time.Now() // Зафиксировать время попытки
if err != nil { // Если Circuit вернула ошибку,
consecutiveFailures++ // увеличить счетчик ошибок
return response, err // и вернуть ошибку
}
consecutiveFailures = 0 // Сбросить счетчик ошибок
return response, nil
}
}
🔥14👍6❤2
💬 Что следует учитывать при добавлении элемента в мапу во время итерации, чтобы избежать недетерминированных результатов?
В примере ниже проводятся итерации по
Результат непредсказуем:
Вот что говорится в спецификации Go по поводу создания нового элемента мапы во время итераций:
Когда элемент добавляется к мапе во время итерации, он может быть либо создан, либо нет при последующей итерации. В Go нет возможности как-то «навязать» поведение кода. Оно может варьироваться от одной итерации к другой, и поэтому мы трижды получали разные результаты.
Важно помнить о таком поведении, чтобы код не выдавал непредсказуемых результатов. Если нужно обновить мапу во время итерации по ней, то одним из решений будет работа с копией мапы:
В этом примере мы отделяем читаемую мапу от обновляемой. Мы продолжаем итерировать по
💡При работе с мапой не следует полагаться:
◆ на то, что данные упорядочиваются по ключам;
◆ на то, что порядок вставки сохранится;
◆ на детерминированность порядка итераций;
◆ на то, что элемент будет создан во время той же итерации, во время которой он был добавлен.
В примере ниже проводятся итерации по
map[int]bool
. Если значение пары равно true
, мы добавляем еще один элемент. m := map[int]bool {
0: true,
1: false,
2: true, }
for k, v := range m {
if v {
m[10+k] = true
}
}
fmt.Println(m)
Результат непредсказуем:
map[0:true 1:false 2:true 10:true 12:true 20:true 22:true 30:true]
map[0:true 1:false 2:true 10:true 12:true 20:true 22:true 30:true 32:true]
map[0:true 1:false 2:true 10:true 12:true 20:true]
Вот что говорится в спецификации Go по поводу создания нового элемента мапы во время итераций:
Если во время итерации создается элемент мапы, он может быть обработан во время итерации или пропущен. Выбор может варьироваться для каждого созданного элемента и от одной итерации к другой.
Когда элемент добавляется к мапе во время итерации, он может быть либо создан, либо нет при последующей итерации. В Go нет возможности как-то «навязать» поведение кода. Оно может варьироваться от одной итерации к другой, и поэтому мы трижды получали разные результаты.
Важно помнить о таком поведении, чтобы код не выдавал непредсказуемых результатов. Если нужно обновить мапу во время итерации по ней, то одним из решений будет работа с копией мапы:
m := map[int]bool{
0: true,
1: false,
2: true,
}
m2 := copyMap(m) // Создается копия первоначальной мапы
for k, v := range m {
m2[k] = v
if v {
m2[10+k] = true // Обновляется m2 вместо m
}
}
fmt.Println(m2)
В этом примере мы отделяем читаемую мапу от обновляемой. Мы продолжаем итерировать по
m
, но все обновления делаются на m2
. Новая версия кода ведет к предсказуемому и повторяемому результату:map[0:true 1:false 2:true 10:true 12:true]
💡При работе с мапой не следует полагаться:
◆ на то, что данные упорядочиваются по ключам;
◆ на то, что порядок вставки сохранится;
◆ на детерминированность порядка итераций;
◆ на то, что элемент будет создан во время той же итерации, во время которой он был добавлен.
👍21
💬 Какие особенности оператора 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
.👍17⚡3🔥2👾1
💬 Что из себя представляет паттерн Throttle, применяемый в облачной разработке?
Паттерн
📌 Например:
◆ пользователю может быть разрешено обращаться к сервису не чаще, чем 10 раз в секунду;
◆ клиенту может быть позволено вызывать определенную функцию только один раз в каждые 500 миллисекунд;
◆ учетной записи может быть разрешено только три неудачные попытки входа в систему в течение 24 часов.
Наиболее распространенной причиной применения
📌 Компоненты:
◆
◆
📌 Пример кода:
Паттерн
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
Forwarded from Библиотека Go-разработчика | Golang
⚡️Свершилось: канал с книгами только по Go
Мы создали для вас канал, куда будем публиковать самые полезные книги только для Go-разработчиков. Подписывайтесь!
👉 Книги для Go разработчиков
Мы создали для вас канал, куда будем публиковать самые полезные книги только для Go-разработчиков. Подписывайтесь!
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2🔥2👍1
💬 Что такое lvalue и rvalue в общем и в контексте Go?
🔹 lvalue (left value): в традиционном понимании,
📌 Пример:
🔹 rvalue (right value):
📌 Пример:
🔹 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, так как это выражение возвращает значение
👍9❤1
💬 Что из себя представляет Interface Pollution в Go и как его определить?
Рассмотрим пример, который демонстрирует Interface Pollution, неправильно используя интерфейс, когда он не нужен.
Интерфейс
📌 Некоторые способы определения Interface Pollution:
🔹 Пакет объявляет интерфейс, который соответствует полному API своего собственного конкретного типа.
🔹 Интерфейсы экспортируются, но конкретные типы, реализующие интерфейс, не экспортируются.
🔹 Функция фабрика для конкретного типа возвращает значение интерфейса с неэкспортируемым конкретным типом внутри.
🔹 Интерфейс можно удалить, и для пользователя API ничего не изменится.
🔹 Интерфейс не обеспечивает гибкость или адаптивность API к изменениям в будущем.
Рассмотрим пример, который демонстрирует Interface Pollution, неправильно используя интерфейс, когда он не нужен.
type Server interface {
Start() error
Stop() error
Wait() error
}
Интерфейс
Server
определяет контракт для TCP-серверов. Проблема в том, что нам не нужен контракт, нам нужна реализация. К тому же этот интерфейс основан на существительном, а не на глаголе. Конкретные типы — это существительные, так как они представляют конкретную проблему. Интерфейсы описывают поведение, и Server
— это не поведение.📌 Некоторые способы определения Interface Pollution:
🔹 Пакет объявляет интерфейс, который соответствует полному API своего собственного конкретного типа.
🔹 Интерфейсы экспортируются, но конкретные типы, реализующие интерфейс, не экспортируются.
🔹 Функция фабрика для конкретного типа возвращает значение интерфейса с неэкспортируемым конкретным типом внутри.
🔹 Интерфейс можно удалить, и для пользователя API ничего не изменится.
🔹 Интерфейс не обеспечивает гибкость или адаптивность API к изменениям в будущем.
🥱8👍5
💬 Назовите общие случаи использования и злоупотребления дженериками в Go.
1️⃣ Структуры данных. Мы можем использовать дженерики, чтобы выделить тип элемента, например, если мы реализуем бинарное дерево, связанный список или кучу.
2️⃣ Функции, работающие со срезами, мапами и каналами любого типа. Например, функция объединения двух каналов будет работать с любым типом канала. Следовательно, можно использовать параметры типа, чтобы определить тип канала:
3️⃣ Факторизация поведения вместо типов. Пакет
Этот интерфейс используется различными функциями:
Поскольку структура
📌 Когда использовать дженерики не рекомендуется?
🔸При вызове метода с аргументом типа. Рассмотрим функцию, которая получает на входе io.Writer и вызывает метод Write:
В этом случае использование дженериков не принесет коду никакой пользы. Нужно напрямую сделать значение аргумента
🔸Когда это делает код более сложным. Дженерики никогда не бывают обязательными, и разработчики 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 прекрасно жили без них более десяти лет.
👍7❤1
💬 Опишите концепцию "package aliasing" и кейсы ее использования.
Она относится к возможности присваивания псевдонима (алиаса) импортируемому пакету.
Это делается для удобства, чтобы избежать конфликтов имен или упростить обращение к пакетам с длинными или неудобными для использования именами.
И хотя алиасы могут быть полезны в определенных ситуациях, их следует использовать умеренно, чтобы не усложнять читаемость кода.
📌 Как это работает:
При импорте пакета можно указать алиас перед путем к пакету. Например:
Здесь
📌 Основные юзкейсы:
1. Избежание конфликтов имен: если мы импортируем два пакета с одинаковыми именами (например, две разные библиотеки
2. Упрощение длинных имен пакетов: некоторые пакеты имеют длинные пути импорта, и использование алиасов может сделать код более читаемым.
3. Изменение стандартного имени пакета: в некоторых случаях, особенно при работе с генерируемым кодом или когда стандартное имя пакета не является описательным, алиасы могут помочь сделать код более понятным.
Она относится к возможности присваивания псевдонима (алиаса) импортируемому пакету.
Это делается для удобства, чтобы избежать конфликтов имен или упростить обращение к пакетам с длинными или неудобными для использования именами.
И хотя алиасы могут быть полезны в определенных ситуациях, их следует использовать умеренно, чтобы не усложнять читаемость кода.
📌 Как это работает:
При импорте пакета можно указать алиас перед путем к пакету. Например:
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🔥4❤1
💬 Что такое указатель на указатель в Go?
В 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👍5❤1
Самые полезные каналы для программистов в одной подборке!
Сохраняйте себе, чтобы не потерять 💾
🔥Для всех
Библиотека программиста — новости, статьи, досуг, фундаментальные темы
Книги для программистов
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 запрещена на территории РФ
Сохраняйте себе, чтобы не потерять 💾
🔥Для всех
Библиотека программиста — новости, статьи, досуг, фундаментальные темы
Книги для программистов
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 реализована в пакете
Рефлексия полезна в ситуациях, когда нам нужно работать с данными неизвестного типа, например, при сериализации/десериализации данных, реализации ORM систем и так далее.
С помощью рефлексии мы можем, например, определить тип переменной, прочитать и изменить её значения, вызвать методы динамически. Это делает код более гибким, но следует использовать рефлексию осторожно, так как она может привести к сложному и трудночитаемому коду, а также снизить производительность.
📌 Простые примеры
🔸Определение типа переменной:
В примере мы используем функцию
🔸Чтение и изменение значений:
Здесь мы используем
🔸Динамический вызов методов:
В этом примере мы создаем экземпляр структуры
Рефлексия в 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. Допустим, нужно создать приложение, которое вызывает
Наконец, когда происходит возврат из
Когда происходит возврат из
Передача сигнала отмены — один из юзкейсов контекстов в 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
Forwarded from Библиотека Go-разработчика | Golang
🏃 Самоучитель по Go для начинающих. Часть 2. Ресурсы для изучения Go с нуля
Расскажем про актуальные и полезные источники информации по языку Go, которые подойдут для самостоятельного изучения и помогут погрузиться в захватывающий мир программирования.
🔗 Читать статью
🔗 Ссылка на первую часть
Расскажем про актуальные и полезные источники информации по языку Go, которые подойдут для самостоятельного изучения и помогут погрузиться в захватывающий мир программирования.
🔗 Читать статью
🔗 Ссылка на первую часть
👍10