1. Портфолио как витрина доверия
Портфолио начинающего разработчика часто выглядит так: десять репозиториев, в каждом final_final2.go, а в README гордо написано “My cool project”. На собеседовании это даёт ноль очков к доверию, потому что не видно главного: как ваш код ведёт себя, какие у него контракты, и можно ли ему верить. Портфолио — это не про количество, а про демонстрацию качества.
Представьте, что ваш проект — это маленький прибор. Пользователь не обязан знать, как внутри устроены шестерёнки. Ему важно: включается ли он, что показывает дисплей, что будет при ошибке, и не взорвётся ли он от неправильной кнопки. В софте “дисплей” — это CLI/HTTP контракт, а “не взорвётся” — это тесты, аккуратные ошибки и предсказуемость.
Чтобы у портфолио была «форма», полезно держать в голове тройку: демо → гарантии → воспроизводимость. Демо отвечает на вопрос «что это делает и зачем», гарантии — «почему этому можно доверять», воспроизводимость — «как это запустить и проверить». В хорошем проекте это видно за 2–3 минуты, не включая режим археолога.
Мини-скелет демо для учебного приложения
Возьмём условный итоговый проект курса — менеджер задач (пусть называется tasker), у которого есть CLI и HTTP API. В демо вы почти всегда показываете одно и то же: happy-path и один-два error-path, но через контракты.
Небольшой фрагмент “правильного” входа в CLI, который хорошо смотрится в портфолио, потому что поведение централизовано и объяснимо:
package main
import (
"os"
)
func main() {
os.Exit(run(os.Args[1:]))
}
И рядом — точка, где вы сознательно управляете “внешним контрактом” CLI (аргументы → код выхода):
package main
import (
"errors"
"fmt"
"os"
)
var ErrUsage = errors.New("usage")
func run(args []string) int {
if len(args) == 0 {
fmt.Fprintln(os.Stderr, "usage: tasker <command>")
return 2
}
return 0
}
Это маленький пример, но он показывает зрелость: вы не раскидываете os.Exit по всему коду, а держите внешний контракт в одном месте.
2. README как интерфейс проекта
README — это не сочинение на тему «как я провёл лето с Go». Это инструкция, которая помогает человеку быстро ответить на четыре вопроса: что делает проект, как запустить, как проверить качество, где посмотреть примеры. Если README не отвечает на эти вопросы, то это не README, а декоративная открытка.
В портфолио особенно важна «скорость понимания»: чем быстрее проверяющий сможет получить сигнал “проект аккуратный”, тем больше шансов, что он вообще будет читать код. Ирония в том, что хороший README часто важнее изящной архитектуры, потому что архитектура без читателя — это как шутка без аудитории: у автора смешно, у остальных тишина.
Хороший приём — писать README в стиле “quickstart + контракт”. Ниже пример фрагмента, который можно держать как ориентир (это текст, но структура важнее конкретных слов):
tasker — минимальный менеджер задач (CLI + HTTP).
Quickstart:
go test ./...
go run ./cmd/tasker help
CLI contract:
stdout — данные
stderr — ошибки/usage
exit codes: 0 ok, 2 usage, 1 runtime
HTTP contract:
JSON, Content-Type: application/json
ошибки: { "error": { "code": "...", "message": "...", "fields": {...} } }
Обратите внимание: тут нет “мы использовали микросервисную архитектуру с квантовыми энумами”. Есть то, что можно проверить руками за минуту.
Example-тесты как часть портфолио
Обычный пример в README устаревает. Example-тест компилируется и может проверяться go test, поэтому он является “живым примером”. Это один из самых недооценённых способов показать качество: вы не убеждаете словами — вы показываете проверяемый сценарий.
Например, вы хотите, чтобы функция парсинга ID была понятна и человеку, и тестовому раннеру:
package tasker
import (
"fmt"
"strconv"
)
func ExampleParseID() {
id, err := strconv.Atoi("42")
fmt.Println(id, err == nil) // 42 true
// Output: 42 true
}
В портфолио это работает как маленькая гарантия: «я не только написал код, я зафиксировал поведение так, что оно не “поплывёт”».
3. Код‑ревью как навык
Код‑ревью многие новички воспринимают как экзамен: “сейчас меня будут ругать”. На практике код‑ревью — это способ уменьшить риск ошибок и повысить качество коммуникации. Это совместное проектирование. Иногда оно действительно неприятное, но чаще всего неприятно не ревью, а отсутствие привычек: непонятный дифф, хаотичные изменения, нет тестов, нет объяснения “зачем”.
Хорошая новость: код‑ревью — это навык, который прокачивается. Ещё лучшая новость: вы можете начать делать ревью уже сегодня, даже если у вас нет команды, потому что существует мощная техника — саморевью. Вы делаете PR “для себя”, проходите чек качества, и только потом показываете кому-то. Это резко снижает количество замечаний в стиле “ну gofmt-то можно было сделать?”.
В портфолио код‑ревью видно косвенно: по истории коммитов, по тому, насколько изменения атомарны, по наличию тестов и по тому, как вы оформляете ошибки. Даже если никто не оставлял вам комментарии в GitHub, проект может выглядеть как “прошёл ревью у взрослого инженера”.
Маленький ритуал саморевью на примере изменения в tasker
Допустим, вы добавили валидацию заголовка задачи. Плохая версия часто выглядит так: функция “на месте”, ошибки разные по стилю, тестов нет, а валидация размазана по трём слоям.
Аккуратная версия начинается с маленькой функции, которую легко тестировать:
package tasker
import "errors"
var ErrTitleEmpty = errors.New("title is empty")
func ValidateTitle(title string) error {
if title == "" {
return ErrTitleEmpty
}
return nil
}
Да, это простая функция. Но именно такие вещи формируют “вкус” к коду: маленькие, читаемые, тестируемые кусочки.
И рядом — маленький table-driven тест, который закрепляет контракт:
package tasker
import "testing"
func TestValidateTitle(t *testing.T) {
if err := ValidateTitle(""); err == nil {
t.Fatalf("expected error")
}
if err := ValidateTitle("buy milk"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
В ревью такой код обычно комментируют мало. Не потому что он “идеальный”, а потому что он не требует догадок.
Чек качества как привычка
Важно, чтобы качество не зависело от настроения (“сегодня я молодец, завтра устал”). Поэтому большинство команд превращают качество в ритуал. Вы уже знаете инструменты: форматирование, тесты, анализаторы. Дальше вопрос не “что запускать”, а “как сделать это привычкой”.
Тут полезна одна простая мысль: чем меньше ручных действий, тем выше шанс, что вы их реально сделаете. Даже в одиночном проекте можно завести одну команду, которую вы запускаете перед пушем. Иногда это делают через Makefile, иногда через скрипт, иногда через IDE-run configuration — не принципиально. Принципиально, чтобы “проверить себя” было так же легко, как открыть браузер и отвлечься.
4. Чтение исходников стандартной библиотеки
Читать исходники стандартной библиотеки звучит как что-то, что делают люди в тёмных плащах, когда компилятор не компилирует. На самом деле это очень практичная привычка. Стандартная библиотека Go — это огромный сборник решений: как оформлять ошибки, как проектировать API вокруг интерфейсов, как писать код, который будет жить годами, и почему простота иногда достигается не магией, а дисциплиной.
Читать stdlib нужно не с мыслью “я сейчас всё пойму”, а с мыслью “я сейчас пойму один маленький кусок и украду оттуда одну хорошую привычку”. Это как спортзал: вы не приходите туда один раз и не становитесь супергероем. Вы приходите регулярно, и через пару месяцев внезапно замечаете, что ваш код стал спокойнее и чище.
Есть ещё один бонус: когда вы читаете stdlib, вы перестаёте верить мифам. Увидели в реальном коде, что ошибки оборачивают с контекстом, что интерфейсы маленькие, что много внимания к границам ввода — и всё, “магическое мышление” начинает умирать.
Как найти исходники пакета
В Go это делается довольно прямолинейно: у вас есть GOROOT, и там лежит стандартная библиотека. Если вы используете IDE, чаще всего можно просто “Go to definition” по net/http или encoding/json и попасть в исходники. Если вы любите терминал (или IDE показывает путь), можно ориентироваться на GOROOT.
Если вы хотите “прочувствовать”, что Go — это не только язык, но и инструментальная экосистема, полезно знать, что у go команды много режимов и файлов вокруг проекта. Например, workspaces (go.work) — один из способов управлять несколькими модулями вместе; он не обязателен новичку каждый день, но сам факт, что это документировано и поддержано инструментами, — часть “культуры Go”.
А ещё полезно помнить, что некоторые команды — это отдельные шаги процесса. Например, go generate существует, но он не “встроен магически” в сборку: его нужно запускать явно, и это принципиально для воспроизводимости. Даже если вы не используете go generate прямо сейчас, понимание этой философии помогает писать проекты без скрытой магии.
Что читать в stdlib, если вы новичок
Не нужно начинать с runtime. Начинать лучше с пакетов, которые вы уже использовали в курсе: errors, fmt, io, bufio, net/http, encoding/json, context, sync. Там вы увидите знакомые идеи в “идеальной подаче”.
Очень полезная техника чтения: идти от интерфейса к реализации. Сначала смотрите, как выглядит контракт (например, io.Reader), потом — кто его реализует, потом — какие обёртки/декораторы вокруг него построены. Это повторяет наш курс: сначала контракт, потом реализация, потом тестируемость.
Мини‑практика: декоратор в стиле stdlib
Когда читаешь stdlib, особенно пакеты вокруг io, возникает чувство: “да они всё завернули во что-то, что реализует интерфейс”. И это правда: в Go очень любят маленькие обёртки, которые ничего “не ломают”, а добавляют поведение. Это идеальная тренировка для роста: вы учитесь расширять систему не переписыванием, а композицией.
Сделаем мини-шаг в нашем tasker: добавим обёртку над io.Writer, которая считает, сколько байт мы записали. Это полезно, например, для логов, метрик или тестов, и это ровно тот стиль мышления, который вы будете постоянно встречать в стандартной библиотеке.
package tasker
import "io"
type CountingWriter struct {
W io.Writer
N int
}
func (cw *CountingWriter) Write(p []byte) (int, error) {
n, err := cw.W.Write(p)
cw.N += n
return n, err
}
Теперь вы можете использовать это в любом месте, где у вас есть writer (stdout, файл, буфер). И это очень “по‑Go”: минимум магии, максимум прозрачности.
Например, вы хотите в CLI посчитать, сколько байт вы вывели пользователю:
package main
import (
"fmt"
"os"
"example.com/tasker"
)
func main() {
cw := &tasker.CountingWriter{W: os.Stdout}
fmt.Fprintln(cw, "hello") // hello
fmt.Println("bytes:", cw.N) // bytes: 6
}
Да, пример игрушечный. Но он учит трём взрослым вещам: интерфейсам, композиции и наблюдаемости. И в stdlib таких маленьких “кирпичиков” очень много — именно поэтому чтение её исходников так хорошо прокачивает мозг.
5. Ритм роста
Почти все планы развития ломаются об одно и то же: человек ставит себе цель “выучить всё”, и через неделю ненавидит себя и Go. Чтобы не повторять эту классическую трагикомедию, полезно принять простую идею: развитие должно быть маленьким, регулярным и измеримым. Не в смысле “метрики KPI”, а в смысле “я могу честно сказать, что сделал шаг”.
Очень удобный формат — недельный ритм, где у вас есть несколько повторяющихся активностей: чуть-чуть улучшить проект (портфолио), чуть-чуть потренировать ревью, чуть-чуть почитать хороший код. Важно не количество, а устойчивость. Если вы делаете по 20 минут, но каждую неделю — это лучше, чем один “марафон на выходных” раз в два месяца.
Ниже пример ритма, который хорошо ложится на реальность, даже если вы учитесь параллельно работе/учёбе:
| Привычка | Время | Что считается “сделал” |
|---|---|---|
| Мини-улучшение проекта | 30–60 мин/нед | один PR: тест, багфикс, мелкая фича |
| Саморевью | 10–15 мин/PR | прогнал проверки, перечитал дифф |
| Чтение stdlib | 20–30 мин/нед | один файл/одна функция + заметка |
| Документация | 15 мин/нед | улучшил README или добавил Example |
Обратите внимание: здесь нет пункта “выучить Kafka”. Не потому что Kafka плоха, а потому что без ритма вы не выучите даже fmt.Printf — будет только чувство вины и вкладка с курсом, открытая “на потом”.
И ещё одна важная мысль: следите за изменениями платформы. Go развивается, инструменты меняются, появляются новые дефолты. Например, в Go 1.25 изменилось поведение GOMAXPROCS в контейнерах: рантайм стал учитывать CPU limits по умолчанию, чтобы избежать неприятной троттлинг-латентности. Вам не нужно прямо сейчас “учить контейнеры глубоко”, но полезно иметь привычку читать такие заметки и понимать, что платформа живая.
6. Типичные ошибки в личной стратегии роста
Ошибка №1: делать портфолио “про количество”, а не “про доверие”.
Когда у вас много репозиториев, но ни один не имеет понятного quickstart, тестов и ясных контрактов, проверяющему трудно поверить в ваш уровень. Гораздо сильнее выглядит один проект, который запускается с первой попытки, имеет стабильные ошибки, тесты и понятный README.
Ошибка №2: воспринимать код‑ревью как наказание.
Если вы ждёте ревью “как приговор”, вы начинаете прятать изменения, делать их мелкими и бессмысленными, или наоборот — заливать огромные коммиты “пока не передумал”. Здоровая модель другая: ревью — это совместное снижение риска. Саморевью перед отправкой — лучший способ сделать ревью спокойным и полезным.
Ошибка №3: читать stdlib как роман (и страдать).
Попытка понять сразу весь net/http или encoding/json может демотивировать. Гораздо эффективнее читать маленькими порциями: один тип, одна функция, один тест. Смысл в накоплении привычки и “профессионального вкуса”, а не в разовом подвиге.
Ошибка №4: путать “знаю” и “могу показать”.
На собеседовании и в работе ценится не то, что вы “примерно понимаете”, а то, что вы можете быстро воспроизвести: запустить тесты, показать демо, объяснить контракт ошибок. Поэтому портфолио должно быть демонстрируемым: команды запуска, сценарий, ожидаемый результат.
Ошибка №5: улучшать качество “когда будет время”.
Если качество не встроено в процесс, оно не появится. В какой-то момент проект начинает жить, баги копятся, и вы откладываете тесты “на потом”. Гораздо дешевле — маленький ритуал: форматирование, тесты, статический анализ, и только потом коммит.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ