Возьмем типичный план "прокачки" Go-разработчика
- Базовые типы данных. Указатели. Строки, Slice, Map. Внутреннее устройство типов данных. Структуры. Интерфейсы.
- Процессы и потоки ОС. Модель памяти.
- Горутины. Планировщик Go. Go Runtime. GC.
- Работа с многопоточностью (каналы, мультиплексирование, контексты).
- Паттерны многопоточной обработки данных (Worker Pool, Pipeline)
- Обработка ошибок и паник в Go. Метрики и логирование.
- Работа с HTTP / gRPC в Go. Middleware.
- Алгоритмы и структуры данных.
- ACID. Типы индексов PostgreSQL. Репликация и партицирование. Работа с БД в Go.
- Брокеры сообщений. Kafka. Работа с Kafka в Go
- Тестирование приложений в Go. Профилирование. Race Detection.
- System Design. Паттерны микросервисной архитектуры.
Я считаю, что хорошее понимание данного набора тем — это необходимый минимум для того, чтобы считать себя backend-разработчиком на Go и в дальнейшем успешно проходить собеседования.
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий
- Базовые типы данных. Указатели. Строки, Slice, Map. Внутреннее устройство типов данных. Структуры. Интерфейсы.
- Процессы и потоки ОС. Модель памяти.
- Горутины. Планировщик Go. Go Runtime. GC.
- Работа с многопоточностью (каналы, мультиплексирование, контексты).
- Паттерны многопоточной обработки данных (Worker Pool, Pipeline)
- Обработка ошибок и паник в Go. Метрики и логирование.
- Работа с HTTP / gRPC в Go. Middleware.
- Алгоритмы и структуры данных.
- ACID. Типы индексов PostgreSQL. Репликация и партицирование. Работа с БД в Go.
- Брокеры сообщений. Kafka. Работа с Kafka в Go
- Тестирование приложений в Go. Профилирование. Race Detection.
- System Design. Паттерны микросервисной архитектуры.
Я считаю, что хорошее понимание данного набора тем — это необходимый минимум для того, чтобы считать себя backend-разработчиком на Go и в дальнейшем успешно проходить собеседования.
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий
Далее посмотрим уже на детальные планы. Точный состав тем может отличаться в ту или иную сторону на занятиях, здесь публикую что-то усредненное.
На первом занятии подробно проходимся по примитивам Go, разбираемся как устроены структуры и интерфейсы, как разботает наследование, композиция и агреггация, какие есть отличия между разными версиями языка, разбираем некоторые тонкости данных тем.
Занятие 1
Пакет builtin. Базовые типы данных. Указатели. Zero-Value. Пустой интерфейс. Пустая структура. Пользовательские структуры. Pointer / Value Receiver.
Операции new / make
Выравнивание структур. Композиция и агрегация.
Упражнение
Данное упражнение не несет особого практического смысла с точки зрения бизнес логики, а скорее направлено просто на отработку базового синтаксиса языка, его основных конструкций и знакомит нас с Git, GoLand (или VS Code).
Реализовать структуру Point (координаты X и Y на плоскости)
Реализовать методы для вычисления расстояния между двумя точками.
Реализовать метод для проверки "попадает ли точка в радиус N относительно другой точки"
Реализовать структуру "Полигон" (набор точек, образующих замкнутый многоугольник)
Реализовать метод для проверки "Находится ли точка внутри полигона"
Написать тесты для этого.
При помощи пакета flag или pflag реализовать работу с точками через консольное приложение, пример
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий #Занятие1 #Упражнение
На первом занятии подробно проходимся по примитивам Go, разбираемся как устроены структуры и интерфейсы, как разботает наследование, композиция и агреггация, какие есть отличия между разными версиями языка, разбираем некоторые тонкости данных тем.
Занятие 1
Пакет builtin. Базовые типы данных. Указатели. Zero-Value. Пустой интерфейс. Пустая структура. Пользовательские структуры. Pointer / Value Receiver.
Операции new / make
Выравнивание структур. Композиция и агрегация.
Упражнение
Данное упражнение не несет особого практического смысла с точки зрения бизнес логики, а скорее направлено просто на отработку базового синтаксиса языка, его основных конструкций и знакомит нас с Git, GoLand (или VS Code).
Реализовать структуру Point (координаты X и Y на плоскости)
Реализовать методы для вычисления расстояния между двумя точками.
Реализовать метод для проверки "попадает ли точка в радиус N относительно другой точки"
Реализовать структуру "Полигон" (набор точек, образующих замкнутый многоугольник)
Реализовать метод для проверки "Находится ли точка внутри полигона"
Написать тесты для этого.
При помощи пакета flag или pflag реализовать работу с точками через консольное приложение, пример
# ./point --point=2,0 --point=0,0 --distance
> 2
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий #Занятие1 #Упражнение
На первом занятии мы подробно разобрали примитивные типы данных, на втором - начинаем разбирать составные типы, массивы и слайсы. Разбираемся, в чем их отличия и особенности использования, разбираемся как работает append.
Также, рассматриваем простейшие структуры данных, реализуемые при помощи слайсов, разбираемся, в каких случаях вместо массивов стоит использовать односвязные или двусвязные списки
Занятие 2
Массивы. Устройство массивов. Слайсы. Внутреннее устройство слайсов. Отличия массивов и слайсов. Адресная арифметика.
Команды make / append / copy.
Структуры данных. Стек. Очередь. Односвязный и двусвязный список.
Упражнение
Реализовать структуру данных "Стек"
Реализовать структуру данных "Очередь"
Написать тесты
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий #Занятие2 #Упражнение
Также, рассматриваем простейшие структуры данных, реализуемые при помощи слайсов, разбираемся, в каких случаях вместо массивов стоит использовать односвязные или двусвязные списки
Занятие 2
Массивы. Устройство массивов. Слайсы. Внутреннее устройство слайсов. Отличия массивов и слайсов. Адресная арифметика.
Команды make / append / copy.
Структуры данных. Стек. Очередь. Односвязный и двусвязный список.
Упражнение
Реализовать структуру данных "Стек"
Реализовать структуру данных "Очередь"
Написать тесты
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий #Занятие2 #Упражнение
Строки в Go — во многом похожи на слайсы, однако имеют некоторые важные особенности. Иммутабельность, кодирование в UTF-8 и представление в виде байт и рун.
Разбираемся, чем отличается Unicode от UTF-8, как максимально эффективно работать со строками.
Занятие 3
Строки. Внутреннее устройство строк в Go.
Байты. Руны. Кодировка. Unicode. UTF-8.
Пакет strings, unicode/utf8.
Форматирование строк. Преобразование типов данных. Пакеты fmt, strconv.
Упражнение
Необходимо написать Go функцию, осуществляющую примитивную распаковку строки, содержащую повторяющиеся символы/руны, например:
Как видно из примеров, разрешено использование цифр, но не чисел.
В случае, если была передана некорректная строка, функция должна возвращать ошибку. При необходимости можно выделять дополнительные функции / ошибки.
Написать тесты на весь функционал
(*) Дополнительное задание: поддержка экранирования через \:
(обратите внимание на косые кавычки)
Как видно из примера, заэкранировать можно только цифру или слэш.
(*) Дополнительное задание: Написать упаковку строк (обратное преобразование)
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий #Занятие3 #Упражнение
Разбираемся, чем отличается Unicode от UTF-8, как максимально эффективно работать со строками.
Занятие 3
Строки. Внутреннее устройство строк в Go.
Байты. Руны. Кодировка. Unicode. UTF-8.
Пакет strings, unicode/utf8.
Форматирование строк. Преобразование типов данных. Пакеты fmt, strconv.
Упражнение
Необходимо написать Go функцию, осуществляющую примитивную распаковку строки, содержащую повторяющиеся символы/руны, например:
"a4bc2d5e" => "aaaabccddddde"
"abcd" => "abcd"
"3abc" => "" (некорректная строка)
"45" => "" (некорректная строка)
"aaa10b" => "" (некорректная строка)
"aaa0b" => "aab"
"" => ""
"d\n5abc" => "d\n\n\n\n\nabc"
Как видно из примеров, разрешено использование цифр, но не чисел.
В случае, если была передана некорректная строка, функция должна возвращать ошибку. При необходимости можно выделять дополнительные функции / ошибки.
Написать тесты на весь функционал
(*) Дополнительное задание: поддержка экранирования через \:
(обратите внимание на косые кавычки)
`qwe\4\5` => "qwe45"
`qwe\45` => "qwe44444"
`qwe\\5` => `qwe\\\\\`
`qw\ne` => "" (некорректная строка)
Как видно из примера, заэкранировать можно только цифру или слэш.
(*) Дополнительное задание: Написать упаковку строк (обратное преобразование)
aaaabccddddde => a4bc2d5e
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий #Занятие3 #Упражнение
Следующий тип данных в Go - это map. Map - это реализация структуры HashMap, которая обеспечивает сложность доступа по ключу в О(1).
Занятие 4
Тип данных map. Внутреннее устройство map. Бакеты.
Hash-функция. Коллизии. Алгоритмы разрешения коллизий.
Устройство памяти. Процесс эвакуации. Взятие указателя на значение map по ключу.
Многопоточная работа с map. Особенности работы с sync.Map
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий #Занятие4
Занятие 4
Тип данных map. Внутреннее устройство map. Бакеты.
Hash-функция. Коллизии. Алгоритмы разрешения коллизий.
Устройство памяти. Процесс эвакуации. Взятие указателя на значение map по ключу.
Многопоточная работа с map. Особенности работы с sync.Map
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий #Занятие4
Ранее мы познакомились с основными типами и структурами данных, теперь мы постепенно начинаем применять их на практике.
Одна из наиболее часто используемых структур в production - это LRU-Cache.
Структура представляет из себя кеш ограниченного размера, при этом при добавлении новых элементов, в ситуации, когда кэш заполнен, старые элементы - удаляются. При этом удаляются те элементы, которые давно не были использованы на чтение.
Предполагается, что те элементы, которые мы часто читаем - удалять из кеша не надо, а те элементы, что на чтение не используются - нет смысла в этом кеше хранить.
Упражнение
Реализовать LRU-Cache. При создании кеша указываем его размер при помощи функциональных опций. Если размер не задан - используем размер по-умолчанию.
Кеш должен быть вынесен в отдельный пакет.
Пакет должен иметь тесты на весь функционал.
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий #Занятие4 #Упражнение
Одна из наиболее часто используемых структур в production - это LRU-Cache.
Структура представляет из себя кеш ограниченного размера, при этом при добавлении новых элементов, в ситуации, когда кэш заполнен, старые элементы - удаляются. При этом удаляются те элементы, которые давно не были использованы на чтение.
Предполагается, что те элементы, которые мы часто читаем - удалять из кеша не надо, а те элементы, что на чтение не используются - нет смысла в этом кеше хранить.
Упражнение
Реализовать LRU-Cache. При создании кеша указываем его размер при помощи функциональных опций. Если размер не задан - используем размер по-умолчанию.
Кеш должен быть вынесен в отдельный пакет.
Пакет должен иметь тесты на весь функционал.
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий #Занятие4 #Упражнение
Зачастую, приходится работать со структурой под название "Множество" или "Set".
Данная структура представляет из себя набор уникальных элементов.
Упражнение
Реализовать структуру данных Set
Реализовать методы "Пересеченме двух множеств", "Объединение двух множеств", "Вычитание множества"
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий #Занятие4 #Упражнение
Данная структура представляет из себя набор уникальных элементов.
Упражнение
Реализовать структуру данных Set
Реализовать методы "Пересеченме двух множеств", "Объединение двух множеств", "Вычитание множества"
#Менторство #Менторинг #Менти #GoLang #Go #Backend #ПланЗанятий #Занятие4 #Упражнение
Какие источники стоит изучить при изучении Go и прохождении курсов?
☑️Основы
Основы Go на официальном сайте https://go.dev/learn/
Прохождение Go Tour https://go.dev/tour/
☑️Официальная документация
https://go.dev/doc/
Более подробная документация по внутренностям языка https://go.dev/doc/effective_go
Литература
☑️По языку
📓Язык программирования Go, Алан А.А. Донован, Брайан У.Керниган
📓Golang для профи. Михалис Цукалос.
📓Go: идиомы и паттерны проектирования. Джон Боднер
☑️Алгоритмы и структуры данных
📓Грокаем алгоритмы. Адидтья Бхаргава.
📓Совершенный алгоритм. Тим Рафгарден.
📓Продвинутые алгоритмы и структуры данных. М. Ла. Рокка
☑️ОС и сети
📓Архитектура компьютера. Э. Таненбаум. Т. Остин
📓Современные операционные системы. Э. Таненбаум. Х. Бос.
📓Компьютерные сети. Э. Таненбаум. Д. Уэзеролл.
☑️Архитектура
📓Фундаментальный подход к программной архитектуре: паттерны, свойства, проверенные методы. Ричардс М. , Форд Н.
📓Современный подход к программной архитектуре: сложные компромиссы. Ричардс М. , Форд Н. , Садаладж П., Дехгани Ж.
📓Создание микросервисов. Сэм Ньюмен.
📓Микросервисы. Крис Ричардсон.
📓Высоконагруженные приложения. Программирование. Масштабирование. Поддержка. М. Клеппман.
📓System Design. Подготовка к сложному интервью. Алекс Сюй.
☑️Технологии
📓PostgreSQL 16 изнутри. Егор Рогов.
📓Apache Kafka. Потоковая обработка и анализ данных. Гвен Шапира, Тодд Палино, Раджини Сиварам, Крит Петти.
☑️Observability
📓Site Reliability Engineering. Надежность и безопасность как в Google. Бетси Бейер, Крис Джоунс, Дженнифер Петофф, Нейл Ричард Мёрфи.
📓Site Reliability Workbook: практическое применение. Бетси Бейер, Нейл Ричард Мёрфи, Дэвид Рензин, Кент Кавахара, Стивен Торн
#Go #Backend #Литература #ПолезныеМатериалы
☑️Основы
Основы Go на официальном сайте https://go.dev/learn/
Прохождение Go Tour https://go.dev/tour/
☑️Официальная документация
https://go.dev/doc/
Более подробная документация по внутренностям языка https://go.dev/doc/effective_go
Литература
☑️По языку
📓Язык программирования Go, Алан А.А. Донован, Брайан У.Керниган
📓Golang для профи. Михалис Цукалос.
📓Go: идиомы и паттерны проектирования. Джон Боднер
☑️Алгоритмы и структуры данных
📓Грокаем алгоритмы. Адидтья Бхаргава.
📓Совершенный алгоритм. Тим Рафгарден.
📓Продвинутые алгоритмы и структуры данных. М. Ла. Рокка
☑️ОС и сети
📓Архитектура компьютера. Э. Таненбаум. Т. Остин
📓Современные операционные системы. Э. Таненбаум. Х. Бос.
📓Компьютерные сети. Э. Таненбаум. Д. Уэзеролл.
☑️Архитектура
📓Фундаментальный подход к программной архитектуре: паттерны, свойства, проверенные методы. Ричардс М. , Форд Н.
📓Современный подход к программной архитектуре: сложные компромиссы. Ричардс М. , Форд Н. , Садаладж П., Дехгани Ж.
📓Создание микросервисов. Сэм Ньюмен.
📓Микросервисы. Крис Ричардсон.
📓Высоконагруженные приложения. Программирование. Масштабирование. Поддержка. М. Клеппман.
📓System Design. Подготовка к сложному интервью. Алекс Сюй.
☑️Технологии
📓PostgreSQL 16 изнутри. Егор Рогов.
📓Apache Kafka. Потоковая обработка и анализ данных. Гвен Шапира, Тодд Палино, Раджини Сиварам, Крит Петти.
☑️Observability
📓Site Reliability Engineering. Надежность и безопасность как в Google. Бетси Бейер, Крис Джоунс, Дженнифер Петофф, Нейл Ричард Мёрфи.
📓Site Reliability Workbook: практическое применение. Бетси Бейер, Нейл Ричард Мёрфи, Дэвид Рензин, Кент Кавахара, Стивен Торн
#Go #Backend #Литература #ПолезныеМатериалы
Для начала, поговорим о структурах данных.
Как правило, их изучение начинается с самых простых структур — массивов (в случае Go — ещё и слайсов), поэтому, первая тема —
Массивы и их внутреннее устройство.
Что такое массив?
Массив это набор элементов одного типа, расположенный в памяти последовательно. Как правило, массив — это указатель на начало нужного куска памяти, и информация о том, какого типа значения хранятся в массиве.
Таким образом, если мы знаем индекс нужного нам элемента, мы можем быстро его найти.
Как? За счет адресной арифметики. Адреса всех переменных в памяти — это просто числа, если к какому-то адресу прибавить (или вычесть) какое-то значение, мы получим адрес какого-то другого элемента в памяти. Как уже сказано выше, массив — это набор элементов одного типа, расположенных в памяти последовательно. А значит, если мы знаем адрес одного элемента массива, то для того, чтобы найти следующий нам всего лишь надо к адресу этого элемента прибавить число, равное размеру одного элемента в памяти.
Рассмотрим на примере:
Допустим, у нас есть массив из пяти элементов типа int64. Пусть будет массив arr = [1 2 3 4 5], начало массива расположено в ячейке памяти с номером 365 (конкретное число нам тут не важно). Каждая ячейка памяти занимает один байт. Каждая переменная занимает 8 байт, а значит и 8 ячеек памяти.
Таким образом, в ячейках 365-372 хранится число 4. В ячейках 373-380 хранится число 3, а заканчивается массив на ячейке 404. В ячейке 405 будут находиться уже какие-то другие данные (или она будет пустая, нам невжано, к нашему массиву она уже не имеет отношения).
Итого, всего у нас занято 8 * 5 = 40 ячеек памяти.
При этом, у нас хранится указатель на начало этого массива (то есть значение 365). Теперь, при обращении к arr[0] мы получаем значение из ячеек 365-372, при обращении к arr[4] получаем значение из ячеек 397-404.
И тут кроется ответ на вопрос, почему нумерация индексов в массивах идет с нуля.
Для получения значения по индексу в массиве, используется следующая формула:
Несложно заметить, что данная формула не зависит от количества элементов в массиве, мы одинаково легко можем найти адрес как первого элемента массива, так и 100500-го элемента в массиве из 1000000000 значений (не вдаваясь в подробности, что тут будет происходить с памятью, конечно)
Таким образом, сложность поиска элемента массива по его индексу — константная, или по-другому — О(1). С О-нотацией разберемся позже.
В следующей части — об особенностях массивов в Go
#Алгоритмы #Массивы #Go
Как правило, их изучение начинается с самых простых структур — массивов (в случае Go — ещё и слайсов), поэтому, первая тема —
Массивы и их внутреннее устройство.
Что такое массив?
Массив это набор элементов одного типа, расположенный в памяти последовательно. Как правило, массив — это указатель на начало нужного куска памяти, и информация о том, какого типа значения хранятся в массиве.
Таким образом, если мы знаем индекс нужного нам элемента, мы можем быстро его найти.
Как? За счет адресной арифметики. Адреса всех переменных в памяти — это просто числа, если к какому-то адресу прибавить (или вычесть) какое-то значение, мы получим адрес какого-то другого элемента в памяти. Как уже сказано выше, массив — это набор элементов одного типа, расположенных в памяти последовательно. А значит, если мы знаем адрес одного элемента массива, то для того, чтобы найти следующий нам всего лишь надо к адресу этого элемента прибавить число, равное размеру одного элемента в памяти.
Рассмотрим на примере:
package main
import (
"fmt"
)
func main() {
a := [5]int{1, 2, 3, 4, 5}
fmt.Println(&a)
fmt.Println(&a[0])
fmt.Println(&a[1])
fmt.Println(&a[2])
fmt.Println(&a[3])
fmt.Println(&a[4])
}
Допустим, у нас есть массив из пяти элементов типа int64. Пусть будет массив arr = [1 2 3 4 5], начало массива расположено в ячейке памяти с номером 365 (конкретное число нам тут не важно). Каждая ячейка памяти занимает один байт. Каждая переменная занимает 8 байт, а значит и 8 ячеек памяти.
Таким образом, в ячейках 365-372 хранится число 4. В ячейках 373-380 хранится число 3, а заканчивается массив на ячейке 404. В ячейке 405 будут находиться уже какие-то другие данные (или она будет пустая, нам невжано, к нашему массиву она уже не имеет отношения).
Итого, всего у нас занято 8 * 5 = 40 ячеек памяти.
При этом, у нас хранится указатель на начало этого массива (то есть значение 365). Теперь, при обращении к arr[0] мы получаем значение из ячеек 365-372, при обращении к arr[4] получаем значение из ячеек 397-404.
И тут кроется ответ на вопрос, почему нумерация индексов в массивах идет с нуля.
Для получения значения по индексу в массиве, используется следующая формула:
АдресЗначения = НачалоМассива + Индекс * РазмерОдногоЭлемента
То есть, в нашем случае, это АдресЗначения = 365 + index * 8,
где индекс принимает значения от нуля до четырех.Несложно заметить, что данная формула не зависит от количества элементов в массиве, мы одинаково легко можем найти адрес как первого элемента массива, так и 100500-го элемента в массиве из 1000000000 значений (не вдаваясь в подробности, что тут будет происходить с памятью, конечно)
Таким образом, сложность поиска элемента массива по его индексу — константная, или по-другому — О(1). С О-нотацией разберемся позже.
В следующей части — об особенностях массивов в Go
#Алгоритмы #Массивы #Go
Особенности массивов в Go.
Фактически, массивы в Go не используются. Лично по своему опыту могу сказать, что за 10 лет опыта разработки на Go не использовал массивы ни разу, всегда лучше работать со слайсами.
Основная причина — длина массива не может изменяться. Мы не можем добавить туда новые элементы, количество элементов в слайсе должно быть известно на этапе компиляции, а не в runtime, а работаем мы как правило с неизвестным количеством элементов, поэтому слайсы более удобны в большинстве сценариев.
Исчерпывающая информация по поводу массивов дана в официальной документации (https://go.dev/doc/effective_go#arrays)
- Массивы передаются по значению.
- Присвоение переменной другого массива приведет к полному копированию всех данных. В частности, если вы передаете массив как параметр функции, она получит копию массива, а не указатель на неё
- Размер массива является частью его типа. Тип [10]int и [20]int — это разные типы.
При этом, в некоторых случаях (при достаточно большом размере массива), компилятор может решить, что аллоцировать массив в стеке — невыгодно (придется его постоянно копировать по стеку), поэтому в таких ситуациях массив аллоцируется в куче.
#Алгоритмы #Массивы #Go
Фактически, массивы в Go не используются. Лично по своему опыту могу сказать, что за 10 лет опыта разработки на Go не использовал массивы ни разу, всегда лучше работать со слайсами.
Основная причина — длина массива не может изменяться. Мы не можем добавить туда новые элементы, количество элементов в слайсе должно быть известно на этапе компиляции, а не в runtime, а работаем мы как правило с неизвестным количеством элементов, поэтому слайсы более удобны в большинстве сценариев.
Исчерпывающая информация по поводу массивов дана в официальной документации (https://go.dev/doc/effective_go#arrays)
- Массивы передаются по значению.
- Присвоение переменной другого массива приведет к полному копированию всех данных. В частности, если вы передаете массив как параметр функции, она получит копию массива, а не указатель на неё
- Размер массива является частью его типа. Тип [10]int и [20]int — это разные типы.
При этом, в некоторых случаях (при достаточно большом размере массива), компилятор может решить, что аллоцировать массив в стеке — невыгодно (придется его постоянно копировать по стеку), поэтому в таких ситуациях массив аллоцируется в куче.
#Алгоритмы #Массивы #Go