В этом посте мы обсудим несколько увлекательных моментов, которые я узнал в процессе своей работы.Мой канал с инструментами Go разработчика, с разбором каверзных вопросов с собеседований, примерами с кодом, обучающими уроками и кучей всего полезного, здесь целая папка для всех, кто любит Golang.
Поехали!
1. Как используется встраивание (embedding) в Go?
В Go директива //go:embed используется для встраивания файлов и папок в бинарный файл Go на этапе компиляции. Эта функция улучшает безопасность, производительность и простоту кода за счёт возможности прямого импорта файлов без использования функций операционной системы.
Пример:
//go:embed all:frontend/dist
var assets embed.FS
//go:embed all:frontend/dist Эта директива указывает компилятору Go встроить содержимое каталога frontend/dist в бинарный файл. Префикс all: используется для включения не только файлов из каталога, но и всех вложенных каталогов и их файлов рекурсивно.
var assets embed.FS Это объявляет переменную с именем assets типа embed.FS, который является интерфейсом файловой системы, предоставляемым пакетом embed. Эта переменная будет предоставлять доступ к встроенным файлам во время выполнения.
Преимущество заключается в том, что импортирование определённых файлов напрямую и использование embed вместо функций операционной системы повышает безопасность, производительность и простоту использования.
Можно попробовать использовать это для создания настольного приложения и загрузить все данные, связанные с интерфейсом, в сервер Go.
2. Какая польза от оператора Select в Go?
Оператор select позволяет горутине ожидать выполнения нескольких операций взаимодействия.
Ну, это то, что вы могли бы увидеть на https://go.dev/tour/concurrency/5, но в чём здесь польза?
go func(){
_, err := destConn.Write(requestBuffer1)
if err != nil {
errChan1 <- err
close(requestBuffer1Chan)
return
}
successfullyWrittenBuffer1Chan <- true
}()
//write the reply to mongo client
select {
case <-ctx.Done():
destConn.Close()
return
case err = <-errChan1:
logger.Error("failed to write the reply message to mongo client", zap.Error(err))
return
case <-successfullyWrittenBuffer1Chan:
}
Давайте рассмотрим пример, где мы записываем данные в конечное соединение. В случае закрытия приложения вы не хотите писать в закрытое конечное соединение.
Код не обязательно “останавливается” на ctx.Done(). Оператор select ожидает, когда один из случаев станет готовым. Если контекст отменен (или его срок действия истекает), до того как будет получена ошибка или до того как операция записи будет подтверждена как успешная, тогда случай ctx.Done() будет выполняться первым, что приведет к закрытию destConn и раннему завершению функции и её возврату.
Если контекст не завершён и произошла ошибка, то выполнится случай err = <-errChan1, который залогирует ошибку и вернёт её.
Если контекст не завершён и ошибка не произошла, а операция записи выполнилась успешно первой, то выполнится последний случай, позволяя функции продолжить выполнение за пределами оператора select.
3. Как реализовать интерфейсы в Go?
В Go интерфейс представляет собой тип, который определяет набор сигнатур методов (названия методов, параметры и типы возвращаемых значений), но не реализует сами методы. Это способ определения поведения как набора действий. Тип реализует интерфейс, реализуя все методы, объявленные в интерфейсе.
Реализуйте следующий интерфейс:
package main
import (
"fmt"
)
// Определение интерфейса
type Speaker interface {
Speak() string
}
// Реализация интерфейса с помощью типа Dog
type Dog struct {
Name string
}
func (d Dog) Speak() string {
return fmt.Sprintf("%s говорит гав!", d.Name)
}
// Реализация интерфейса с помощью типа Cat
type Cat struct {
Name string
}
func (c Cat) Speak() string {
return fmt.Sprintf("%s говорит мяу!", c.Name)
}
func main() {
var dog Speaker = Dog{Name: "Барсик"}
var cat Speaker = Cat{Name: "Мурка"}
fmt.Println(dog.Speak())
fmt.Println(cat.Speak())
}
Приведенный выше фрагмент кода демонстрирует контракт для получения данных о студенте. Любой тип, который реализует этот интерфейс, может предоставлять данные о студенте, что позволяет легко заменять различные источники данных или реализации без изменения остального кода. Это может быть особенно полезно при работе с различными системами баз данных и внешними службами или при создании заглушек данных для тестирования.
Какова практическая польза от этого? Абстракция, полиморфизм, гибкость и еще много чего.
4. Как init работает в Go?
В Go функция init является специальной функцией, которая автоматически выполняется при инициализации пакета. В каждом пакете может быть одна или несколько функций init, и они выполняются в порядке их появления в файле. Функция init используется для выполнения задач настройки, которые необходимо выполнить перед использованием пакета, таких как инициализация переменных или выполнение другой инициализационной логики.
package config
import (
"fmt"
"os"
"strconv"
"github.com/joho/godotenv"
)
type Config struct {
AppName string
SuperAdmin string
}
var config Config
// Должен запускаться в самом начале перед любым другим пакетом или кодом.
func init() {
appEnv := os.Getenv("APP_ENV")
if len(appEnv) == 0 {
appEnv = "dev"
}
configFilePath : "./config/.env"
if os.Getenv("APP_ENV") == "test" {
configFilePath = "../config/.env"
}
e := godotenv.Load(configFilePath)
if e != nil {
fmt.Println("error loading env: ", e)
panic(e.Error())
}
config.AppName = os.Getenv("SERVICE_NAME")
config.SuperAdmin = os.Getenv("SuperAdmin")
}
func Get() Config {
return config
}
Какова выгода от этого? Вам не нужно инициализировать функцию init вручную; вам просто нужно импортировать функцию, и значения будут извлечены и назначены.
appname := config.Get().AppName
5. Как работают несколько отложенных вызовов (deferrals)?
Отложенные вызовы работают как стек, что означает “первым вошел, последним вышел” (LIFO). Так что, если кто-то напишет что-то вроде этого:
defer closeCon()
defer cleancon()
defer printCon()
Таким образом, когда эта функция завершится, сначала будет выполнена функция printCon, затем cleanCon, и, наконец, closeCon.
6. Как использовать слушатель событий в Go?
В приведенном ниже фрагменте кода на Go мы имеем мощный шаблон обработки событий, который облегчает создание отзывчивых приложений.
runtime.EventsOn(ctx, "event:check:health", func(args ...interface{}) {
responseEventName := args[0].(string)
runtime.EventsEmit(ctx, responseEventName, map[string]interface{}{
"success": true,
"version": config.GetConfig().Version,
})
})
Регистрация события: runtime.EventsOn – это вызов функции, который регистрирует слушатель событий для события с именем “event:check:health”. Когда это событие срабатывает, вызывается функция (замыкание), предоставленная вторым аргументом.
Излучение события: затем используется runtime.EventsEmit для отправки события с именем responseEventName. Вместе с именем в качестве данных события передается карта, содержащая статус успешности и текущую версию приложения (полученную через config.GetConfig().Version).
В заключение, данный фрагмент кода устанавливает слушатель событий, который при срабатывании события “event:check:health” отвечает другим событием, указывающим на успешность и версию приложения. Это элегантный пример того, как Go может кратко обрабатывать сложные событийно-ориентированные рабочие процессы.
Взято из репозитория Slashbase.
7. Как обрабатывать панику в Go?
В Go паника (panic) – это встроенная функция, которая прекращает обычный поток управления и начинает паниковать. Когда вызывается функция panic, выполнение текущей функции немедленно останавливается, и управление начинает разворачивать стек, выполняя при этом все отложенные функции.
Если паника достигает вершины стека вызовов горутины без восстановления, программа завершается с ненулевым кодом статуса и, как правило, выводит сообщение о панике и стек вызовов в стандартный поток ошибок.
Для обработки паники в Go предоставляется функция recover, которая может остановить последовательность паники и вернуть значение, переданное функции panic. Она полезна только внутри операторов defer, потому что после начала паники это единственный способ вернуть контроль над программой.
func mayPanic() {
panic("a problem")
}
func main() {
defer func() {
if r := recover(); r != nil {
fmt.Println("Восстановлено после паники:", r)
}
}()
mayPanic()
fmt.Println("После вызова mayPanic()")
}
Комментарии (10)
FanatPHP
31.03.2024 07:18+21Очередной продавец канала в телеграм со сгенерированной чат жипити подборкой тривиальностей (и нелепой попыткой замаскировать её под "собственный опыт").
PaperBread
31.03.2024 07:18+1И даже не проверенная на банальные ошибки, пример с интерфейсами описывает в коде одно, а заметки к нему про другое.
Fardeadok
31.03.2024 07:18"что позволяет легко заменять различные источники данных или реализации без изменения остального кода" - это как?
Tony-Sol
31.03.2024 07:18Таким образом, когда эта функция завершится, сначала будет выполнена функция printCon, затем cleanCon, и, наконец, closeCon.
Надо или это описание поправить, или код выше изменить
“первым вошел, последним вышел” (LIFO).
FILO тогда уж - First In Last Out
NeoCode
31.03.2024 07:18+1//go:embed all:frontend/dist
var assets embed.FSФича гениальная, реализация странная. Комментарии, они разве для этого? Ну да, в Go же нет такого понятия как "атрибуты" или "аннотации", вот и пришлось применять такой странный способ включения метаинформации времени компиляции в файл.
RikkiMongoose
31.03.2024 07:18Приведенный выше фрагмент кода демонстрирует контракт для получения данных о студенте.
Студенты Cat и Dog. Оба мяукают.
Xantorohara
Префикс `all:` в embed всего лишь позволяет включать файлы, у которых имя начинается с точки или подчёркивания. К рекурсивному включению вложенных каталогов и файлов он отношения не имеет.