Если раньше большую часть кода я писал в neovim, то сейчас все чаще и чаще у меня параллельно открыт Cursor. Если я понимаю, что время генерации кода курсором будет быстрее, чем время, потраченное на написание промпта для такой генерации - я рационально иду и генерирую его. Это очень круто работает для тестов и для задач в которым мало контекста и зависимостей.
И я давно думал, что было бы очень круто, если бы была возможность объединить простоту и удобство генерации кода через LLM, доступное в Cursor и все фишки neovim, большинство из которых невозможно настроить в Cursor через сторонние плагины.
И как оказалось, такие проекты есть!
Я попробовал два самых популярных:
- https://github.com/yetone/avante.nvim
- https://github.com/olimorris/codecompanion.nvim
avante - самый популярный и имеет наибольшое количество звезд и контрибьюторов, но лично у меня он очень часто и много стрелял багами. Мягко говоря, ему далеко до генерации кода как в Cursor, хотя проект очень интересный и очень надеюсь, что его продолжат развивать.
А вот codecompanion мне понравился. В интеграции с моделями от anthropic качество кода, которое он генерирует - довольно неплохое, и количество багов в процессе работы сильно меньше, чем у avante.
Планирую в ближайшее время взять какую-нибудь более-менее реальную задачу и какой-нибудь проект с небольшим количеством контекста, и сравнить эти плагины на разных моделях (а заодно и chat-плагин для github copilot - https://github.com/CopilotC-Nvim/CopilotChat.nvim, он менее популярен и тоже имеет не мало багов судя по отзывам, но выглядит многообещающе, поэтому его тоже стоит попробовать).
Недавно выше я писал, что собираюсь подготовиться и попробовать пройти в американский или европейский бигтех.
Итак, что было сделано за первые 3 месяца этого года:
1. Прошел курс по system design от balun-cources.
Курс, в целом, неплохой. Очень много теории и практика под конец. Теория полезная, частично пересекающаяся с "Высоконагруженными приложениями"" Клеппмана, но с объяснением на кейсах из реальной жизни. В целом - курс стоит своих денег, хотя лично у меня изначальные ожидания были несколько выше. В любом случае, база получена, осталось попрактиковаться в изученных паттернах как на собесах, так и в реальной работе.
2. Начал проходить курс по алгосам от Влада Тена. С моим темпом прохождения и количеством свободного времени - думаю эта история затянется надолго, но я и не тороплюсь. Зачем вообще нужен курс по алгосам? Лично мне - для дисциплины. Решать задачки проходя курс мне сильно проще, в ином случае я просто забью на середине. А тут за меня уже кто-то продумал структуру задач, объяснил теорию и оформил все это в удобном виде.
3. Английский. Вот этого было действительно много. Зафиналил спринт на 30 занятий на lingoda, и параллельно 3 раза в неделю занимаюсь с нейтивом. Еще для себя обнаружил, что уровень сильно растет вверх от просмотра контента на английском, поэтому каждый день теперь смотрю какие-нибудь сериалы или видосы. В целом - становится лучше, надеюсь в таком же режиме довести его до конца года до того, чтобы проходить/проводить собесы на английском.
Еще из интересного - сходил тут, недавно, на собес. Я почти 2 года работаю в одной компании, пока не планирую ее менять и все эти два года не выходил на рынок. А тут меня позвали и я подумал - почему бы и да, и решил попробовать. Компания - большая, занимается мобильными играми и занимается этим лучше всех в Европе. Но с собесом у ребят, как по мне, большие проблемы.
Мне дали час времени, за который сначала попросили решить задачу, затем провести код ревью строк на 250, а затем написать кусок кода с довольно сложной конкурентной логикой. Задачи - в целом ок, но вот лимит по времени в час - это, конечно, сильно 🙂 За час я успел сделать все 3 задачи, но все 3 - не полностью, особенно код-ревью.
Для себя тут сделал вывод, что буду чаще ходить на собесы, чтобы понимать как сейчас дела на рынке и что там спрашивают.
Новости из мира Go: состоялся релиз Go 1.24 ❤️
Из интересного:
* Полная поддержка алиасов для дженериков, теперь можно делать такие вещи:
type set[P comparable] = map[P]bool
func NewSet[T comparable]() set[T] {
return make(set[T])
}
func main() {
s := NewSet[string]()
s["hello"] = true
fmt.Println(s["hello"]) // true
}
Swiss Tables
(почитать о том что это можно, например, тут или тут), что дает прирост производительности, в некоторых случаях, до 30-40%;go:wasmexport
для простого экспорта функций в webassembly и добавилась поддержка сборки программы как WASI reactor/library. Уже прилетела свежая статья от go-тимы, где эти улучшения описаны более подробно - https://go.dev/blog/wasmexportВ компании, где я работаю, последний год менеджмент активно продвигает нейросети: сотрудникам сделали подписки на большинство известных инструментов и моделей, и их использование, если ты понимаешь, что это делает тебя продуктивнее - только поощряется.
Я сам буквально каждый день общаюсь с ChatGPT, около двух лет уже юзаю Copilot и последнее время оценил, насколько круто работать в Cursor.
И вот сегодня я узнал, что сейчас в нескольких больших компаниях, где работают мои знакомые, уже начали формализировать и собирать метрики, которые покажут, насколько правильное использование этих инструментов позволит ускорить разработку и процесс доставки задачи на прод.
Я не верю, что условный chatgpt в интервале 4-5 лет сможет заменить разработчика, скорее он станет компаньоном, работать с которым в большинстве компаний станет не то, что желательно, а обязательно. В вакансиях к списку требований добавится еще пункт о том, что требуется опыт работы с ИИ. И помимо того, что изменится рабочий процесс, изменится так же и процесс найма. Скорее всего, если бы мне сейчас сказали "прособеседуй разработчика, но учти, что он все свое время будет работать в Cursor", я бы не стал спрашивать у него алгоритмы или код-ревью. Я бы попросил его открыть Cursor и показать мне, как он за час решит несколько заранее подготовленных мной задач и какие промпты он будет писать.
Вероятно, в ближайшие пару лет появятся интеграции между редакторами и досками, например между Cursor и Jira, и в каждой задаче послее ее выполнения можно будет посмотреть, сколько кода нагенерила сетка, а сколько написал или поправил разработчик (если такое появится - осуждаю, трекать время или любые другие критерии - это плохо, но думаю мы от этого не уйдем).
В общем, будем посмотреть. А я пока пошел пробовать новую модель от openai, которую они релизнули вчера - https://techcrunch.com/2025/01/31/openai-launches-o3-mini-its-latest-reasoning-model/
Как я писал выше - я хочу подготовиться к собесам в FAANG и компаниям около FAANG.
Сейчас найм в такие компании устроен так, что на собесах все еще спрашивают алгосы, и я думаю, что ближайшие года 2 еще точно будут спрашивать, пока нейросети не начнут кардинально менять и процесс работы, и, как следствие, процесс найма. Но пока он не меняется, и значит, алгосы нужно нарешивать.
Еще в конце прошлого года я купил курс по алгосам, и вот с начала этого начал его проходить. Зачем вообще нужен курс? Вроде бы открыл литкод и поехал, а если что-то не получается - на youtube есть решения буквально всех возможных задач.
Но, в моем случае курс мне нужен не для понимания задач, а для мотивации не забросить это дело. Создается впечатление, будто бы ты не один этим занимаешься, и процесс получается веселей. Плюс, автор очень круто объясняет, набрасываю интересную теорию там, где она нужна.
И вот спустя две недели какие я сделал выводы:
* Как только какая-либо тема начинает получаться, процесс решения задач по этой теме становится интересным. Начал прям кайфовать от того, когда получается написать какой-нибудь алгоритм быстро и завести его с первого раза.
* 🐍Python - очень хорош для алгосов. Когда у тебя есть возможность вызвать str.isalnum()
, а не писать свою проверку, что символ в диапазоне нужных значений, это очень упрощает процесс. Плюс, на собесах за это в 99% не спрашивают.
* Подобные задачки развивают умение искать в коде ошибки. Например, у меня в рабочем проекте, из-за его специфики, есть очень много мест где что-то вычисляется, перебирается или проверяется. И частые ошибки, которые пропускают на ревью, это не ошибки бизнес-логики, а ошибки в рассчетах: взяли срез не по нужным элементам или, наприме, забыли добавить что-то к переменной перед тем, как писать ее в метрику. И последнее время замечаю, что становится проще жонглировать такой логикой в голове.
В целом цель на год - отрешать ~150-200 самых частых задач, которые в теории могут быть на собесе.
В заключении скажу, что алгосы - абсолютно точно не панацея. Их стоит отрешивать или если ты хочешь подготовиться к собесу, или если тебе это нравится. В моем случае оба этих фактора совпали 🙂
Все начали писать про итоги и планы, и я тоже решил написать.
Изначально этот канал назывался "1000 дней программирования", и я вел его с целью показать мой путь от джуна до синьора, отсюда и id канала - @junsenior. 1000 дней прошли пару лет назад и лычка синьора у меня уже тоже давно есть. В целом, за 5 лет с момента первого поста я успел поменять PHP на Go, поработать в российском бигтехе, а потом перейти на валютную удаленку в большую и быстро развивающуюся компанию, в которой работаю уже полтора года.
В 2024-ом году я не сменил язык и работу (что для меня удивительно), но решил много интересных и сложных задач. Например, за год я развернул несколько сервисов с нуля, много работал с параллельным программированием, много строил архитектуру, рефачил большие объемы кода и собеседовал людей.
Но главный результат этого года для меня - у меня появилось понимание того, к чему я хочу стремиться дальше. А дальше я хочу попасть в бигтех, но уже американский. В идеале - MAANG, под которым я понимаю пару десятков самых больших американских тех-компаний. Дедлайн, который я сам себе выставил, - 3 года (если в ближайшие пару лет нас не заменят нейросети, конечно). Получается, новый челлендж в 1000 дней 🙂
Горизонт работы, для того, чтобы хотя бы собеседование в такую компанию стало возможным, прочерчивается через 3 основных вещи, на которые я буду делать упор.
1 - английский. Очевидно, собеседование будет на английском, и хотя я активно его учу последние пару лет, в ближайшее время я хочу повысить как эффективность этого обучения, так и интенсивность, о чем тоже планирую тут писать.
2 - алгоритмы. У меня уже было несколько подходов к алгосам, но каждый раз не хватало мотивации и усидчивости, и обычно я забрасывал это дело через месяц-два. Сейчас у меня есть и мотивация, и понимание как правильно решать алгосы так, чтобы запоминать решение, и купленный курс по алгосам от @tenfoundation :), который я планирую пройти в ближайшие полгода.
3 - system design. Тут в планах начать с курса от balun.courses, который я тоже купил и теперь жду старта 11-ого февраля. В общем и целом system design мне всегда очень нравился, так что тут не ожидаю никаких проблем, ожидаю только массив полезной и крутой инфы.
О каждом из этих пунктов я буду тут писать и делиться тем, что мне кажется интересным. Так же в планах возообновить стримы (да, олдфаги помнят, как года 3-4 назад я уже пытался это делать), и вернуться к менторству, в том числе и на бесплатной основе, о чем я тоже планирую тут написать.
В общем, планы намечены, что делать - тоже понятно. Впереди много работы, и, надеюсь, много контента, которым я тут буду делиться.
Желаю нам всем, чтобы 2025-ый год стал лучше, чем год уходящий. Мира вам, и с наступающим!
Посмотрел доклад "Debugging Go Application" от Matt Boyle из Cloudflare
Сначала коротко. Итак, у нас что-то сломалось, что делаем:
* Сначала проанализируй код глазами и удостоверься, что ты не видишь явных ошибок;
* Затем пишем тест на участок кода, который потенциально выдает ошибки и проходимся по нему отладчиком;
* Затем покрываем код логами, если он еще не покрыт;
* Затем добавляем метрики: успешные/не успешные запросы, время запроса, etc
* И последний шаг - покрываем код трейсами, если они еще не были добавлены.
Теперь более подробный конспект деталей, которые мне показались интересными.
Про логирование. В прошлом году в Go завезли расширенный логгер - slog, и если ты все еще по какой-то причине используешь стороннее решение, например logrus, то можно смело переезжать.
В коде, который Мэт демонстрирует, используется паттерн передачи логгера через контекст:
ctx := context.Background()
// Create a logger that writes to the log file
logger := slog.New(slog.NewJSONHandler(logFile, nil))
ctx = slogctx.With(ctx, logger)
Некоторое время назад я написал большой гайд о том, как настроить neovim с нуля и привести его к виду, пригодному для разработки: с подсветкой синтаксиса, пониманием кода, автокомплитом, и десятком других возможностей, к которым мы привыкли в IDE.
Мотивация у меня простая. Когда я только начинал разбираться в теме, я не видел ни одного гайда, следуя которому я не столкнулся бы с ошибками или в котором все сразу бы заработало. Большинство авторов или упускают множество моментов, или не раскрывают весь контекст, из-за чего новичку тяжело понять, в чем проблема и как ее решить.
Чтобы это исправить, в своей статье я настраивал neovim на чистой тачке, начиная с установки и конфигурирования консоли и заканчивая тем, что редактор стал похож на IDE.
Затем я перевел статью на английский и опубликовал ссылку на нее на различные тематические ресурсы.
И как показывает статистика, в 2024-ом году писать код в консоли все еще кому-то интересно 🙂
Поэтому я дописал вторую часть, где показал как настроить вторую пачку плагинов, которые вместе с первой составляют мой базовый набор: с ним можно без проблем писать код, иметь интеграцию с гитом и большинство привычных возможностей из IDE.
Го читать - https://poltora.dev/neovim-for-developers-2-ru/
Релиз Go 1.23
Самое большое обновление - появились итераторы. В пакеты slices и maps добавлены новые методы для работы с итераторами, например метод Chunk из пакета slices позволяет перебирать слайс пачками из заданного числа элементов:
people := People{
{"Gopher", 13},
{"Alice", 20},
{"Bob", 5},
{"Vera", 24},
{"Zac", 15},
}
for c := range slices.Chunk(people, 2) {
fmt.Println(c)
}
Вчера я писал о первом совете из книги Go with Domains: стоит определять бизнес логику так, как она звучит.
Продолжаем знакомиться с этой книгой, и сегодня хочу написать про второе правило внедрения DDD в приложение: всегда держите валидное состояние в памяти в одном месте.
Это значит, что вся логика работы с доменом должна быть определена в отдельном пакете и скрыта от других пакетов. Менять состояние домена должно только публичное API. Автор также не советует создавать геттеры и сеттеры.
Возвращаясь к нашему примеру с тренажерным залом из вчерашнего поста, инкапсулируем домен Hour:
type Hour struct {
hour time.Time
availability Availability
}
// ...
func NewAvailableHour(hour time.Time) (*Hour, error) {
if err := validateTime(hour); err != nil {
return nil, err
}
return &Hour{
hour: hour,
availability: Available,
}, nil
}
h := hour.NewAvailableHour("13:00")
if h.HasTrainingScheduled() {
h.SetState(hour.Available)
} else {
return some error
}
func (h *Hour) CancelTraining() error {
if !h.HasTrainingScheduled() {
return some error
}
h.availability. Available
return nil
}
h := hour.NewAvailableHour("13:00")
if err := h.CancelTraining(); err != nil {
return err
}
Большинство компаний, особенно относительно больших, сейчас имеют в своей инфраструктуре кубер или его аналоги, внутри которого множество подов обмениваются друг с другом запросами.
Часто под не светит свои порты во внешний мир и сервис в целом не имеет внешнего домена, доступного снаружи.
С опозданием, но теперь и я узнал о команде, которая позволяет бить в закрытые от внешнего мира поды с локальной машины (единственное условие - находиться в той же vpn сети и иметь права на выполнение команды port-forward
в кубере), спешу поделиться.
Сначала находим нужный под в нужном окружении:
➜ ~ kubectl get pods -n prod | grep pod_name_prefix
➜ ~ kubectl port-forward pod_full_name -n=prod 5050:5050
➜ ~ Forwarding from 127.0.0.1:5050 -> 5050
➜ ~ Forwarding from [::1]:5050 -> 5050
➜ ~ Handling connection for 5050
➜ ~ curl -s http://localhost:5050/mypage
Скорее всего у тебя в проекте, на каком бы языке он не был написан, в корне лежит большой Makefile. На команды, определенные там, часто завязывается ci/cd, через make удобно настраивать локальный запуск проекта.
Например, у меня в компании Makefile с определенными командами - обязательное требование, без которого невозможно поднять новый сервис.
Тема и правда удобная, но есть один большой минус - эти файлы очень плохо читаются и еще сложнее что-то туда добавить. В поисках альтернативы для go-проектов нашел mage - тулза, задача которой убрать сложность чтения и записи make-команд. Решается это с помощью определения тех же команд внутри go-файла, что выглядит, пишется и читается сильно удобней:
//go:build mage
package main
import (
"github.com/magefile/mage/sh"
)
// Runs go mod download and then installs the binary.
func Build() error {
if err := sh.Run("go", "mod", "download"); err != nil {
return err
}
return sh.Run("go", "install", "./...")
}
mage -h build
PGO-оптимизации или как бесплатно ускорить приложение
Узнал про интересную возможность бесплатно и очень быстро ускорить go-приложение на ~10%.
В Go 1.20, релиз которого был чуть больше года назад, добавили PGO - новые возможности компилятора оптимизировать приложение на основе его поведения.
В чем идея: с помощью профилировщика снимаем профиль процессора, затем запускаем go build с флагом -pgo и нужным профилем: go build -pgo ./pgo/profile.pprof
Компилятор на основе данных профилировщика понимает, где можно заинлайнить какие-либо данные, где можно предсказать ветвление по входным данным или где можно реорганизовать данные так, чтобы тратить меньше времени на переключение контекста. На выходе мы получаем тот же билд нашего приложения, но на 2-7% быстрее, чем его аналог скомпилированный без этого флага. В определенных случаях для специфических паттернов можно ускорить приложение на 10 и более процентов.
Так, например, ребята из cloudflare сумели такой бесплатной оптимизацией сэкономили 97 ядер из 3000, на которых было запущено приложение. Если перевести 97 ядер в стоимость облачной инфры, получается очень приятная цифра, которую получилось сэкономить за пару минут.
Последний год я активно пользуюсь ChatGPT, и хочу поделиться кейсами, как он помогает мне в работе.
Во-первых, SQL. Каждый раз, когда мне нужно написать запрос, будь то запрос на создание таблицы, запрос на select/insert/update, или мне нужно создать индекс - я копирую запрос, копирую структуру таблиц и кидаю в ChatGPT с вопросом "what can be improved in this query". Например, недавно я закинул запрос на создание таблицы в PostgreSQL, где была стандартная история с расширением для UUID:
create extension if not exists "uuid-ossp";
create table if not exists table (
id uuid primary key default uuid_generate_v4(),
...
if !(!(price <= discount) && !((!(!(quantity >= maxLimit)) || !!(minOrderValue > 100)) && !(price*quantity-discount <= 200))) {
...
}
Таким образом, условие !(!(price <= discount) && !((!(!(quantity >= maxLimit)) || !!(minOrderValue > 100)) && !(price*quantity-discount <= 200))) можно переформулировать так:
Условие выполнено, если цена товара больше скидки, и либо количество товара меньше максимального лимита, либо минимальная стоимость заказа больше 100, и при этом общая стоимость товара (цена умноженная на количество) минус скидка больше 200.
в очень темное время живем
светлая память Алексею Навальному
https://www.youtube.com/watch?v=wp66e-JRlj0
на фоне ужасных событий последних недель, и ужасных событий последних двух лет в целом, я хочу заняться социально-полезной работой и, вероятно, смогу кому-то помочь
если ты, или твои знакомые переехали из России и у тебя проблемы с работой разрабом - я готов бесплатно посмотреть твое резюме, дать советы и провести мок-собес, если твой стек находится в пределах go/php
я часто вижу подобные вопросы и жалобы от людей в твиттере, так что думаю, что проблема актуальна
пока не знаю, осталась ли тут какая-то активность за время моего отсутствия, так что если интересно - лайкай, если лайки будут - оставлю контакты куда можно написать
Итераторы в Go, про которые я несколько раз уже писал на этом канале, релизнули еще в прошлом году, но я так ни разу их и не применил в работе – просто не было задачи, которая не была бы решена без них.
Но недавно я поймал себя на мысли - у меня в рабочих проектах есть множество мест, где данные читаются из потоков: из файлов, из базы или из сокетов. И я решил разобраться, можно ли применить итераторы для этих кейсов и смогут ли они облегчить или оптимизировать потоковую обработку.
Спойлер - они действительно упростили жизнь и где-то даже позволили оптимизировать потребление ресурсов. Я решил выделить паттерны их использования, которые мне показались удобными, и получилась новая статья - https://poltora.dev/iterators-in-go-ru/
Велкоме.
В поиске интересных статей на канал с дайджестом, нашел на реддите пост, где автор рассказывает как он создал минималистичную реляционную БД на Go. Реализация поддерживает транзакции, индексы на основе B+ Tree и покрывает весь базовый набор операций по выборке данных.
Вот его проект: https://github.com/Sahilb315/AtomixDB
На первый взгляд выглядит очень даже неплохо, можно углубиться и разобраться в том, как работают индексы, как можно реализовать простую acid-модель и как в целом реляционная БД может быть устроена.
Меня еще со времен чтения Клеппмана очень зацепила тема внутреннего устройства СУБД, но никак не доходили руки поресерчить ее более тщательно. Кажется, настало время сделать это 📆
Как я уже как-то упоминал, я веду небольшой канал с дайджестом статей, материалов и новостей из мира Go - @digest_golang. И вот в процессе подготовки материалов, наткнулся на интересную заметку, которой хочу поделиться: https://antonz.org/go-map-shrink/
Автор рассказывает о том, как сборщик мусора работает с базовым типом map. Если кратко, то все зависит от того, сколько места занимает тип, который хранится в map.
* Если тип занимает < 128B, и мы храним его без указателя, то Go не освобождает выделенную память даже после того, как мы явно удаляем все элементы в мапе:
type Client struct {
id uint64
body [40]byte
}
...
m := make(map[int]Client)
for i := range 10000 {
m[i] = Client{id: uint64(i)}
}
runtime.GC()
printAlloc("after create")
for i := range 10000 {
delete(m, i)
}
runtime.GC()
printAlloc("after delete")
initial: heap size = 47 KB
after create: heap size = 1073 KB
after delete: heap size = 1073 KB
type Client struct {
id uint64
body [1024]byte
}
// the same logic
initial: heap size = 42 KB
after create: heap size = 11581 KB
after delete: heap size = 331 KB
Новость из мира Go: завезли proposal, который предлагает добавить в язык оператор ?
для обработки ошибок, что позволит вместо привычной обработки:
r, err := SomeFunction()
if err != nil {
return fmt.Errorf("%v", err)
}
r := SomeFunction() ? {
return fmt.Errorf("%v", err)
}
Попробовал на днях Cursor. Если вдруг кто-то еще не сталкивался, то это среда разработки, построенная на основе Visual Studio Code, куда встроена большая языковая модель, которая умеет держать в контексте открытый проект и дописывать его по запросам.
Ощущения - очень крутые ❤️ Открыл рабочий проект, описал на английском, что я хочу и в каких файлах стоит посмотреть пример, и через 10 минут и еще 3 промпта у Cursor написал столько кода, сколько я писал бы часа 2.
Затем он поправил тесты с учетом функционала, который сам же дописал, и суммарно у меня все все, включая освоение интерфейса, ушло минут 30.
В целом - буду использовать, вероятно даже попробую пробную версию и отпишусь потом, стоит ли она того. Как по мне это сильно круче Copilot от github, которым я пользуюсь уже пару лет.
Что еще на мой взгляд круто - так это возможность обучаться с его помощью. Например, я сейчас фоном учу Rust, и Cursor, помимо того, что умеет писать код, создавая все нужные файлы, умеет еще и описывать что происходит в том или ином файле.
Например, ты можешь попросить его создать новый проект на Rust, взять API какого-нибудь сервиса и интегрироваться с ним. А затем попросить его построчно объяснить, что происходит и какая конструкция за что отвечает.
Cursor обработает до 2000-х тысяч правок в коде бесплатно, затем нужно будет заплатить.
Единственный минус - это ощущение, что вся эта история с нейросетями через 3-5 лет очень сильно поменяет рынок труда 🙂
Посмотрел еще один доклад, который долго лежал у меня в закладках, на этот раз про отладку параллельного кода в Go - https://www.youtube.com/watch?v=D_S9qQ7jzkQ
Я последнее время активно юзаю Obsidian для конспектирования любой полезной инфы, и доклад выше не стал исключением. Чтобы эти заметки пылились не только у меня в базе знаний, ниже - конспект интересных моментов и тезисов.
Очевидно, что отладка параллельного кода сильно сложнее, чем отладка последовательного кода, выполняющегося в одном потоке. Самая большая при такой отладке - он часто имеет не повторяющееся поведение при одинаковых входных данных и параметрах железа, где он запускается.
Пакет https://github.com/xiegeo/coloredgoroutine подсвечивает вывод каждой горутины отдельным цветом, что может быть полезно при отладке.
Использование флага GODEBUG=schedtrace позволит вывести отладочную информацию про то, как планировщик управляет горутинами:
➜ GODEBUG=schedtrace=5000 ./main
SCHED 0ms: gomaxprocs=10 idleprocs=8 threads=3 spinningthreads=1 needspinning=0 idlethreads=0 runqueue=1 [0 0 0 0 0 0 0 0 0 0]
func main() {
go func() {
// some work...
}()
go func() {
// some work...
}()
go func() {
// some work...
}()
func main() {
go func() {
labels := pprof.Labels("fetcher", "main_goroutine")
pprof.Do(ctx, labels, func(ctx context.Context) {
// some work
})
}()
go func() {
labels := pprof.Labels("worker_1", "main_goroutine")
pprof.Do(ctx, labels, func(ctx context.Context) {
// some work
})
}()
go func() {
labels := pprof.Labels("worker_2", "main_goroutine")
pprof.Do(ctx, labels, func(ctx context.Context) {
// some work
})
}()
// ...
}
(dlv) goroutines -l -with label fetcher
Goroutine 21 - User: ./main.go:20 main.main.func1.1 (0x1003befcc) [chan send]
Labels: "fetcher":"main_goroutine"
[1 goroutines]
Паттерн Circuit Breaker
Сейчас я работаю над приложением с API с нагрузкой в несколько тысяч RPS. Каждый вызов API, помимо прочего, провоцирует запросы во внешние сервисы. Недавно у меня появилась типичная проблема: если внешний сервис перестает отвечать из-за каких-либо ошибок на его стороне, я хочу, чтобы все новые запросы временно прекращали попытки обращаться к нему. Спустя некоторое время, когда внешний сервис вновь заработает, мы снова начнем делать запросы.
Проблема распространенная, я много раз решал ее сам и видел множество сторонних решений. Объединяет их общий принцип: существует глобальный стейт, доступный всем потокам, в который сохраняется статус внешнего сервиса — работает он или лежит. Каждый обработчик, перед вызовом этого сервиса, читает этот статус и принимает решение: делать вызов или нет.
При этом был какой-то обработчик, который опрашивал внешний сервис, и, если тот начинал отвечать, менял глобальный стейт.
Недавно узнал, что у этого паттерна есть название — Circuit Breaker. Общий принцип паттерна я описал выше, за исключением того, что вместо дополнительного обработчика каноничный подход подразумевает, что через определенный промежуток времени “пропускается” несколько запросов, и, если они успешны, глобальный стейт изменяется.
Нашел хорошую статью с примитивной реализацией с нуля и объяснением, что и как работает: https://rednafi.com/go/circuit_breaker/
Хороший пример, который при желании можно доработать и использовать в своих проектах.
Domain need to be a database agnostic
Продолжаю читать книгу "Go with domains" и учиться строить DDD-приложения.
Ранее я уже писал о двух правилах построения DDD: вся логика должна быть упрощена до типов, которые имеют поведение и всегда держите валидное состояние домена в одном месте.
Дальше автор пишет про третье правило - домен не должен работать с базой данных (будь то реляционная, in-memory или любая другая БД) напрямую.
У нас есть как минимум три причины чтобы следовать этому:
• Домены по определению описывают сущности, связанные с бизнес-логикой, а не с DB-слоем, и их нужно различать.
• Вероятно, мы захотим хранить данные в базе в более оптимальном виде. Например, если в домене у нас есть тип []string
, то в DB-слое мы, вероятно, захотим использовать pq.StringArray
• В Go нет ORM-решений и магии в виде аннотаций из коробки, что сильно усложняет интеграцию домена с DB-слоем
И пример хорошего кода на основе всех трех правил:
func (g Server) MakeHourAvailable(ctx context.Context, request *trainer.UpdateHourRequest) (*trainer.EmptyResponse, error) {
trainingTime, err := protoTimestampToTime(request.Time)
if err != nil {
return nil, status.Error(codes.InvalidArgument, "unable to parse time")
}
if err := g.hourRepository.UpdateHour(ctx, trainingTime, func(h *hour.Hour) (*hour.Hour, error) {
if err := h.MakeAvailable(); err != nil {
return nil, err
}
return h, nil
}); err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &trainer.EmptyResponse{}, nil
}
Hour
, есть поведение - MakeAvailable()
, которое мы используем. И вся логика работы со слоем базы данных скрыта и так же инкапсулирована с помощью паттерна репозиторий - о котором я расскажу в следующем посте.
Читать полностью…
Сейчас читаю книгу "Go with domains" про то, правильно приготовить DDD в Go.
Автор пишет:
While implementing your domain, you should stop thinking about structs like dummy data structures or “ORM like” entities with a list of setters and getters. You should instead think about them like types with behavior.
training schedule
на 13:00", вместо этого человек ответит: "я запланировал тренировку на 13:00".
func (s TrainerService) ScheduleTraining(ctx context.Context, request Request) error {
hour, found := s.FindHour(request.Hour)
if request.HasTrainingScheduled && !hour.Available {
//
}
if request.Available && request.HasTrainingScheduled {
//
}
if !request.Available && !request.HasTrainingScheduled {
//
}
}
Hour
, для которого описать поведение ScheduleTraining
, где мы или получим ошибку что время уже занято, или займем его, если оно доступно:
func (h *Hour) ScheduleTraining() error {
if !h.IsAvailable() {
return ErrHourNotAvailable
}
h.availability = TrainingScheduled
return nil
}
Я активно уже несколько месяцев веду канал с дайджестом статей по Go: @digest_golang, пробил там первую тысячу и просмотрел за это время огромное количество статей и материалов.
Хочу продублировать сюда несколько интересных статей за последнее время, думаю что кто-то найдет это полезным:
* Generic Concurrency в Go - автор затрагивает тему дженериков и приводит паттерны, по которым их можно использовать в асинхронной работе вместе с горутинами. Дженерики добавили в Go больше года назад, но многие разработчики, исходя из моих наблюдений, все еще не используются или боятся использовать, хотя по-моему, инструмент очень крутой. У меня было несколько кейсов, когда получилось действительно убрать очень много дублирующего кода.
* No sleep until we build the ideal pub/sub library in Go - очень крутой материал, где автор, кстати с использованием дженериков и горутин, что отлично продолжит первую статью, пишет библиотеку для pub/sub. Отличный материал для понимания работы подобных библиотек и для практики работы с многопоточным программированием.
* Hash-Based Bisect Debugging in Compilers and Runtimes - сильно технический, но очень полезный ман о том, как искать и править баги в коде с помощью поиска по бинарному дереву через инструмент Bisect.
Открыл для себя google apps script - инструмент, позволяющий работать с инфрой гугла с помощью js-подобного кода и ставить задачи по времени like крон.
Например, задача: вам на почту приходят письма, и некоторые из них - супер важные. Предположим, что как только супер-важное письмо пришло - вам нужно сгенерировать отчет и отправить его заказчику.
И еще предположим, что супер-важное письмо содержит в заголовке строку "weekly report". При этом тот, кто отправляет письма не хочет и не может заинтегрироваться с вашим api. Тут на помощь приходит apps script.
Задача решается в несколько шагов:
* Сначала подготавливаем http-ручку на нашем бэкенде, вызов которой делает нужную нам работу.
* В моем случае я завел ещё одну почту, куда с помощью фильтров отправил только нужные мне письма, добавив к каждому из них определенный лейбл и пометив его звёздочкой.
* Затем идём на script.google.com и под нужным аккаунтом, почту которого нужно проверять, пишем обработку наших писем:
function checkEmails() {
const threads = GmailApp.search('is:starred label:"my_label"');
for (const thread of threads) {
const messages = thread.getMessages()
for (const message of messages) {
if (!message.isStarred()) {
continue;
}
...
let response = UrlFetchApp.fetch('https://myapi.com/api/test', options);
...
message.unstar()
}
}
}
написал большой и подробный ман о том, как с нуля настроить neovim и зачем в 2024 году это нужно
постарался описать все то, чего не хватало мне, когда я погружался в эту тему
https://poltora.dev/neovim-for-developers/
го читать
реакции есть, так что кто считает, что ему нужна помощь, пишите: @junsenpub
чтобы пост не был из одной строчки, поделюсь еще своими наблюдениями о собеседованиях
я уже давно собеседовал php-разработчиков на разные грейды, и около года как начал собеседовать go-разработчиков на senior и middle позиции
сейчас популярна такая тема, как хак-собеседований: когда ты не готовишься и разбираешься, а учишься умело наебать интервьюера, ответить на его вопросы с помощью чата таких же хакеров, как и ты, и задорно зателеть на работу, на которой потом работать ты, конечно же, не планируешь - только получать деньги
особенно громко таких ребят слышно в твиттере
да, на рынке есть разные неповоротливые конторы, где (если ты конечно как-то к ним пройдешь) в каком-нибудь отделе тебя просто могут не замечать полгода и платить тебе зарплату
но везде, где я работал (а я много где работал) - такая история не прокатила бы и человека уволили бы на испыте одним днем
в минусе от такого увольнения оказывается не компания - у компании постоянный поток кандидатов, и компания вместо него возьмет следующего, а мамких хакер - абсолютное большинство потратит в итоге на поиски компании столько времени, что можно было бы несколько раз подготовиться, получить знания и пройти собес
я разделяю мнение, что типичный собес плохо оценивает тебя как разраба, и скорее всего ты сможешь перекладывать json как бы ты его не прошел
но
мой опыт, как и опыт большого числа компаний показывает, что есть огромная корреляция между тем, насколько хорошо человек проходит собес (если на этом собесе была не только теория, но и задачи, дающие кандидату возможность показать, как он умеет думать), и тем, насколько хорошо он потом будет работать и делать задачи
сейчас риторика апологетов хака собесов стала меняться на "я просто хочу, чтобы на собесе проверяли знания; сейчас все собесы не работают, и мы это доказываем тем, что мы их хакаем"
как я уже сказал выше, если ты и найдешь собес, который можно наебать, то испытательный срок обойти будет сильно сложнее
ну и вседа держи в голове, что основное, что эти апологеты хотят - это бабки, так как все такие чаты платные
какой тут можно сделать вывод: если у тебя совсем нет знаний, и ты решил таким путем быстро получить работу - волшебной кнопки не бывает, и будь готов, что помимо денег за доступ к чату таких же "хакеров", ты еще потратишь очень много нервов, времени и сил на поиск компании, с которой этот номер пройдет
если у тебя уже есть какие-то знания - вероятно, полезней будет посмотреть примеры прохождения собесов на твоем стеке на youtube - это бесплатно, и очень часто совпадает с реальными собесами
как человек, работающим с докером только в рамках докеризации небольших кусков приложений, открыл для себя крайне интересную вещь
может быть, для многих очевидную, но как показал опрос коллег - мало кто про это знает :)
unionfs - это слоистая файловая система, 1 из 4-х базовых компонентов любого контейнера
почему система называется слоистой? потому что одно действие, например через директиву RUN в Dockerfile - выполняется на одном слое, затем этому слою выдается hash, который в дальнейшем может быть переиспользован на последующих слоях
так вот докер помечает все ранее выполненные слои как read only, и каждая следующая команда не может изменить данные, оставшиеся на слое выше
иначе говоря, если мы пытаемся скачать архив, затем распаковать его, переместить файлы и удалить, и делаем это 4-мя разными командами:
RUN wget https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip
RUN unzip terraform_0.12.28_linux_amd64.zip
RUN rm terraform_0.12.28_linux_amd64.zip
RUN mv terraform /usr/local/bin/
RUN wget https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_linux_amd64.zip \Читать полностью…
&& unzip terraform_0.12.28_linux_amd64.zip \
&& rm terraform_0.12.28_linux_amd64.zip \
&& mv terraform /usr/local/bin/