Просто возьми и используй Go

Перевод статьи Just Fucking Use Go — Blain Smith, май 2026 года. Автор пишет предельно резко; в переводе тон местами смягчён, но суть сохранена.
Слушай сюда. Знаешь, что компилируется за две секунды, разворачивается одним бинарником и не разваливается, когда в три часа ночи из npm выдёргивают какую-нибудь транзитивную зависимость? Go. Ровно так же, как HTML всё это время спокойно лежал с самой зари интернета и ждал, пока ты перестанешь усложнять фронтенд, Go больше десяти лет лежит и ждёт, пока ты перестанешь усложнять бэкенд.
Но нет. Ты продолжаешь склеивать пятнадцать npm-пакетов, три сборщика на TypeScript и кластер Kubernetes, чтобы обслужить одну несчастную форму. Ты нанял целую платформенную команду нянчить твой монолит на Rails. Ты убедил своего CTO, что для CRUD-приложения на сорок запросов в секунду позарез нужен Rust. Поздравляю. Сам себя перехитрил.
Язык скучный — и это специально#
Знаешь, почему Go кажется скучным? Потому что он такой и есть — и в этом весь смысл. Никаких декораторов. Никаких метаклассов. Никаких макросов. Никаких трейтов, монад и прочих проклятых абстракций, которыми на этой неделе упарывается тусовка хаскелистов. Есть структуры, функции, интерфейсы, горутины и каналы. Всё. Спецификацию можно прочитать за обед и уже к вечеру писать полезный код.
Скучный — значит, джун, которого ты нанял месяц назад, спокойно читает код, написанный два года назад твоим ведущим разработчиком. Форматировать его можно ровно одним способом, и gofmt уже сделал это за тебя. Твой «слишком умный» коллега не протащит в кодовую базу семнадцатислойную абстракцию — язык ему просто не даст. Вот так выглядит работа, когда никто не любуется собственной гениальностью.
Стандартная библиотека и есть фреймворк#
Хватит искать фреймворк, честное слово.
Стандартная библиотека и есть фреймворк.
package main
import (
"embed"
"html/template"
"net/http"
)
//go:embed templates/*.html
var files embed.FS
var tmpl = template.Must(template.ParseFS(files, "templates/*.html"))
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl.ExecuteTemplate(w, "index.html", map[string]string{
"Name": "asshole",
})
})
http.ListenAndServe(":8080", nil)
}
Это рабочее веб-приложение. HTML-шаблоны вкомпилированы прямо в бинарник. Никакого webpack. Никакого Vite. Никакого «dev-сервера». Никакого node_modules размером с грузовик. Делаешь go build — и отгружаешь один файл. Кидаешь его на сервер. Готово.
Нужна база данных? database/sql. JSON? encoding/json. Хочешь сходить в другой сервис? net/http — это ещё и клиент. Надо сделать пять дел одновременно? Ставишь go перед вызовом. Тесты? go test. Бенчмарки? go test -bench. Профилирование? pprof уже на месте и посмеивается над твоей отладкой через console.log.
И стандартная библиотека чертовски глубокая#
io.Reader и io.Writer — это два интерфейса с одним методом каждый. И именно благодаря им ты можешь в три строки перегнать тело HTTP-ответа в gzip-писатель, а оттуда в файл на диске, вообще об этом не задумываясь. Их понимает каждый серьёзный пакет в экосистеме. Как только это доходит, половина «магии» Go оказывается всё теми же двумя интерфейсами, которые всплывают повсюду.
context.Context — это то, чем ты отменяешь операции. Пользователь закрыл вкладку — контекст запроса отменяется, запрос к базе отменяется, вызов нижестоящего сервиса по HTTP отменяется. По всей цепочке вниз. Никаких утёкших горутин. Никаких зомби-запросов, выгрызающих твой пул соединений. Ты передаёшь контекст первым аргументом и уважаешь его. Вот и весь API.
encoding/json, encoding/xml, encoding/csv, encoding/binary — всё в стандартной библиотеке. Один и тот же приём со struct-тегами. Одна и та же эргономика «декодируем в указатель». Разобрался с одним — считай, знаешь их все.
Конкурентность, от которой не хочется плакать#
Горутины — это не потоки. У них есть собственный стек, среда выполнения мультиплексирует их на потоки ОС, а старт одной стоит около 2 КБ. На ноутбуке их можно запустить сотню тысяч. Попробуй провернуть такое со своим событийным циклом в Node — и посмотри, как он обделается.
Каналы — это типизированные трубы между горутинами. Отправляешь с одного конца, получаешь с другого, а синхронизацию берёт на себя рантайм. Если вместо этого нужно общее состояние — рядом лежит sync.Mutex, а детектор гонок подскажет, где ты накосячил.
results := make(chan string, len(urls))
for _, url := range urls {
go func(u string) {
resp, _ := http.Get(u)
results <- resp.Status
}(url)
}
for range urls {
fmt.Println(<-results)
}
Это параллельный HTTP-загрузчик. Ни библиотеки, ни фреймворка, ни церемоний с async/await. Всё делает сам язык.
Реальный пример, а не hello-world#
Вот CRUD-маршрут, который читает из Postgres и рендерит HTML. Целиком.
//go:embed templates/*.html
var tmplFS embed.FS
var tmpl = template.Must(template.ParseFS(tmplFS, "templates/*.html"))
type Post struct {
ID int
Title string
Body string
}
func postsHandler(db *sql.DB) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
rows, err := db.QueryContext(r.Context(),
"SELECT id, title, body FROM posts ORDER BY id DESC LIMIT 50")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
var posts []Post
for rows.Next() {
var p Post
if err := rows.Scan(&p.ID, &p.Title, &p.Body); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
posts = append(posts, p)
}
tmpl.ExecuteTemplate(w, "posts.html", posts)
}
}
База данных, шаблоны и HTTP-обработчик на одном экране. Контекст запроса проброшен в запрос к базе, так что закрытое соединение отменяет SQL. Никакой ORM, никакого DI-контейнера, никакого сервисного слоя, никакой папки controllers/ с семнадцатью абстрактными базовыми классами. Читаешь сверху вниз и точно понимаешь, что оно делает.
Зависимости, которые не рушат тебе выходные#
go mod init. Готово. Твои зависимости живут в go.mod и go.sum. Файл go.sum — это криптографическая запись того, что ты на самом деле получил, так что ты сразу заметишь, если кто-то устроит тебе left-pad. Никакой директории node_modules. Никакого расхождения lock-файлов между dev и CI. Ни peer-зависимостей, ни опциональных зависимостей, ни devDependencies, ни peerDependenciesMeta. Есть один файл со списком того, что ты используешь, и один файл, доказывающий, что ты получил именно то, что ожидал.
Нужны офлайн-сборки? go mod vendor складывает всё в директорию vendor/, и тулчейн подхватывает её автоматически. Весь проект вместе с зависимостями умещается в один tar-архив. Твоя служба безопасности прослезится от благодарности.
Инструменты идут в комплекте с компилятором#
gofmt форматирует твой код. Никаких споров. Никаких священных войн вокруг .prettierrc. Формат — это формат, и его используют все. Диффы остаются маленькими, потому что никто не тасует пробелы.
go vet ловит очевидные ошибки. go test гоняет твои тесты. go test -race запускает их с детектором гонок и находит те гонки данных, которых у тебя якобы не было. go test -bench гоняет бенчмарки. go test -cover показывает, что ты пропустил. go tool pprof выдаёт флеймграфы по CPU и памяти прямо с работающего боевого сервиса — через HTTP-эндпоинт, который ты поднимаешь двумя строчками.
Ничего из этого не сторонний пакет, не плагин и не требует конфига, который надо поддерживать. Всё уже в коробке.
Деплой — это команда копирования#
Вот от этой части у народа с Rails и Node подгорает по-настоящему. Ты собираешь бинарник Go. Копируешь его на сервер. Запускаешь.
GOOS=linux GOARCH=amd64 go build -o myapp ./cmd/myapp
scp myapp user@server:/usr/local/bin/
ssh user@server 'systemctl restart myapp'
Три команды. Готово. Никакого Dockerfile. Никакой многоступенчатой сборки. Никаких CVE в базовом образе каждый вторник. Никакого манифеста Kubernetes. Никакого Helm-чарта. Никакого ArgoCD. Никакого service mesh. Никаких сайдкаров.
Статически слинкованный бинарник на 12 МБ плюс unit-файл systemd на 20 строк — это и есть боевой деплой. Он переживёт твою карьеру. Единственная причина тянуться за Docker — если твой отдел эксплуатации обязан его использовать по контракту, да и то ты можешь запихнуть бинарник в образ FROM scratch и на этом успокоиться.
«А как же Rails / Django / Express / Next?»#
А что с ними? Rails требует ритуала деплоя с Capistrano, тремя конфигами и жертвенным козлом. Django хочет, чтобы ты выучил его ORM, его админку, его систему middleware и его мнение обо всём на свете. Express держится на предупреждениях npm audit и молитвах. Next.js меняет соглашения по роутингу каждые полгода и делает вид, что так и было.
Твоему бинарнику Go на это плевать. Он скомпилировался и работает — и будет работать через пять лет на железе, которого ещё не существует. А твой фреймворк? К Рождеству объявят устаревшим, а мейнтейнер напишет на Medium пост про выгорание.
«Но микросервисы!»#
Нет.
Пиши монолит. Один бинарник Go. Один Postgres. Один Redis, если уж совсем никак. Отдавай HTML на том же порту, что и JSON API. Гоняй всё это на одном VPS, который стоит дешевле твоего месячного запаса овсяного молока. Масштабируй до десяти тысяч запросов в секунду не вспотев — Go буквально для этого и создан, а горутины дешевле некуда.
Когда тебе реально понадобится всё это разделить (а не понадобится), разбить монолит на Go — это просто вынести пакеты в отдельные репозитории. Интерфейсы уже на месте. Ты спроектировал под это, даже не стараясь, потому что язык тебя к этому подтолкнул.
«Но дженерики! Но обработка ошибок! Но нет исключений!»#
if err != nil — это фича, а не баг. Она заставляет тебя посмотреть на каждое место, где что-то может пойти не так, и решить, что с этим делать. Твой ад из вложенных try/catch не заставляет ошибки исчезнуть — он просто прячет их до того момента, когда они рванут в проде в два часа ночи.
Дженерики завезли в 1.18. С ними всё нормально. Используй, когда нужно. И хватит ныть.
Просто возьми и используй Go#
Хватит делать вид, что тебе нужен фреймворк. Микросервисы тебе тоже не нужны — как и переписывание на Rust или очередной JavaScript-метафреймворк, который вышел в прошлый вторник и спасёт тебя там, где не спасли предыдущие шесть.
Открой редактор. Выполни go mod init, напиши main.go, вкомпилируй шаблоны и собери. Отгружай уже эту штуку.
Скучный выбор — правильный выбор. Так было всегда.
Талисман Go на обложке — гофер работы Renée French, используется по лицензии CC BY 3.0.