Перевод статьи Just Fucking Use GoBlain 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.