Некоторое время назад я написал большой гайд о том, как настроить 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
я часто вижу подобные вопросы и жалобы от людей в твиттере, так что думаю, что проблема актуальна
пока не знаю, осталась ли тут какая-то активность за время моего отсутствия, так что если интересно - лайкай, если лайки будут - оставлю контакты куда можно написать
пожалуй, лучшее объяснение того, как устроены мапы в golang - это доклад Кейта Рэндалла на GopherCon 2016 - https://www.youtube.com/watch?v=Tl7mi9QmLns
Кейт работает в google, занимается райнтаймом языка и точно знает, о чем говорит
ниже - небольшой конспект первой части доклада - устройство мапы в памяти, за деталями и подробностями советую просмотреть сам доклад
мапа - это структура данных, позволяющая хранить данные в формате ключ-значение
простейшая реализация мапы делается в два действия: создаем структуру полем-ключом и полем-значением, и объявляем массив этих структур
какая основная проблема такой реализации? в случае большого объема данных мы теряем в скорости: для поиска конкретного ключа нам нужно перебрать все данные
как решить? разбить данные на несколько групп
например, по алфавиту мы бы могли разбить на 4 группы:
A-F - первая, G-M - вторая, N-S - третья и T-Z - четвертая
возникает другая проблема - данные могут распределиться неравномерно
например, ключи могут быть ссылками, ссылки начинаются с http, и все данные в таком случае улетят у нас во вторую группу
в качестве решения мы можем взять хорошую hash-функцию, которая равномерно сможет распределить данные по группам
в go такие группы называются buckets
один бакет может содержать максимум 8 пар ключ-значение
в случае, если пачка данных не вмещается в один бакет (что решает hash-функция), структура может содержать указатель на дополнительный бакет
когда мы выделяем мапу через var m map[string]float64
m - это указатель
указывает m на область памяти, называемую map header
header хранит указатель на массив с бакетами, информацию о размерности мапы, информацию о количестве бакетов и другую мета-информацию
завтра разберем вторую часть - работу рантайма и его реализацию поиска по мапе ключам разных типов, что, кстати, отлично иилюстрирует ответ на вопрос "зачем нужны дженерики"
ну чтож, дубль 2
постом выше я собирался восстановить тут активность, но случилась мобилизация
активность, конечно, восстановилась, но не в канале :)
перед тем как начнем, важное напоминание для тех, кто остался: мобилизацию никто не заканчивал, военкоматы - избегайте, повестки - не берите, если есть возможность - уезжайте
3 месяца фултайма на go прошли успешно, испыт пройден
могу резюмировать, что свичнуть эти два языка между собой, если мы говорим о написании типового бэкенда, можно без особых проблем
если ты давно хотел, но никак не мог решиться - можно не бояться :)
теперь немного о том, о чем я планирую писать в ближайшие несколько месяцев
в общем и целом - я собираюсь подготовиться к ряду собесов, которые начну проходить с начала-середины 23-его года
опыта сложных собесов на go у меня мало, пробелов в знаниях в силу опыта на этом языке - тоже вагон, так что мне точно есть что разбирать и, соответственно, о чем писать, что-то в формате конспектов, что-то в формате заметок
в ближайшие пару недель буду делиться интересным из курса по concurrency: https://www.udemy.com/course/concurrency-in-go-golang
в целом большинство тем мне знакомы, многое пропускаю, но вот раздел про планировщик go прошел с большим интересом
в golang есть такая интересная вещь, как netpoller - это пакет, который позволяет преобразовать асинхронный ввод/вывод в блокирующий
зачем это нужно?
golang заточен работать под блокирующие вызовы
например, происходит синхронное чтение из файла, горутина блокируется и ждет, пока чтение завершится
планировщик, через 10 мс выбрасывает ее вниз очереди ожидания, и берет в работу другую горутину
с синхронными вызовами все просто и понятно
а что если вызов асинхронный? в go поступили просто - умеем работать с синхронными? значит сделаем из асинхронного синхронный :)
тут в дело вступает netpoller - компонент рантайма, который умеет блокировать горутину, при этом не блокируя текущий поток выполнения
текущий поток выполнения берет в работу новую горутину из очереди, а netpoller тем временем подписывается на API операционной системы, которое работает с асинхронными вызовами
как только API выбросит событие, что ответ получен - netpoller разбудит горутину и сообщит планировщику, что ей нужно выделить процессорное время
после чего планировщик заберет горутину в один из потоков и возьмет ее на выполнение
второй интересный момент из модуля про планировщик - asynchronous preempation
это механизм, добавленный в go 1.14, позволяющий вытеснить горутину, работающую дольше 10 мс, и дать возможность поработать другим горутинам из локальной очереди
да, до версии 1.14 в случае, если мы работали на одном ядре и запускали там горутину с бесконечным циклом - поток блокировался до явного вызова runtime.Gosched()
более подробно, если тебе интересно, про это можно почитать тут - https://habr.com/ru/post/502506/
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/
открыл для себя тулзу для интеграционного тестирования gonkey - https://github.com/lamoda/gonkey
если коротко, она позволяет поднять в пару строк моки на все внешние сервисы, в интеграции с которыми будет тестироваться приложение, описать сценарии в yaml-файлах, где можно задать нужные фикстуры и указать их порядок, описать ответы на запросы и проверить, что в итоге залетело в базу
затянул на все свои проекты, и крайне рекомендую, если ты пишешь на go