Спецификацию Go можно прочитать за вечер. Соответственно, синтаксис знают все и знание языка почти никого не выделяет. Грань между хорошим разработчиком и очень хорошим лежит не в знаниях, а в поведении. Ниже семь черт, которые повторялись у всех сильных инженеров в моей практике.
Сразу оговорюсь. Это не чеклист для найма и не тест с баллами. Ни одна из семи черт не про врождённый талант. Это обычные привычки и навыки, которые тренируются. Поэтому к каждой я добавил, как её проверить у себя и с чего начать, если черты пока нет.
1. Понимают механизм, а не рецепт
Обычный разработчик запоминает правило. Сильный держит в голове модель, из которой правило выводится.
Разница видна на мелочах. Один знает, что append копирует слайс в некоторых случаях, но всё равно на всякий случай выше пишет make с запасом. Другой понимает, когда именно происходит копирование и почему, и поэтому спокойно обходится без лишнего кода и логики. Первый носит с собой список рецептов и народных примет. Второй в новой ситуации способен вывести ответ с нуля.
Особенно это заметно, когда человек попадает на незнакомую территорию. Тот, кто держится за рецепты, сразу теряется: подходящей приметы он не знает. Тот, кто понимает модель, начинает задавать вопросы и за пару дней осваивается.
Понимание механизма формируется дольше, но и живёт дольше. Рецепты устаревают с каждым релизом языка (то есть примерно каждые полгода). Модель работы планировщика или сборщика мусора меняется куда медленнее. А общие принципы, на которых построена технология, меняются только в исключительно редких случаях.
Как проверить. Объясните тему коллеге, который её не знает. Своими словами, без терминов, которые вы сами же не можете расшифровать. Если объяснение разваливается на середине, значит, у вас пока рецепт, а не понимание. Это техника Фейнмана, и она безжалостно честна.
С чего начать. Возьмите одну библиотеку, которой пользуетесь каждый день, и разберите её до деталей. Откройте исходники стандартной библиотеки. Они хорошо структурированы и читаемы, это одно из главных достоинств Go. Если изучать по одной теме в месяц, за год наберётся двенадцать вещей, которые вы понимаете по-настоящему.
2. Приносят цифры, а не мнения
Классический пример: обсуждение производительности в рабочем чате. Оно почти всегда идёт по одному сценарию. Кто-то говорит, что вот здесь наверняка тормозит. Кто-то отвечает, что нет, тормозит в другом месте. Спор длится час и заканчивается ничем.
Сильный инженер в этом споре не участвует и не пытается в нём выиграть. Он его закрывает железобетонными аргументами. Собирает данные с профайлера или делает бенчмарки и показывает, куда на самом деле уходят время и ресурсы.
Я много раз видел, как цифры одного такого замера разворачивают обсуждение на сто восемьдесят градусов. Команда полдня спорит про алгоритм, а по факту время съедает сериализация или лишняя аллокация в горячем цикле. Догадки в этой области всегда мимо, и опыт тут помогает гораздо хуже, чем может показаться.
Но эта привычка работает и в обратную сторону. Прежде чем что-то оптимизировать, сильный разработчик снимает замер до исправлений. Потом после. Если разницы нет, правку он выбрасывает, даже если код стал красивее. Красота без замера это вкусовщина.
Как проверить. Вспомните последнюю оптимизацию, которую вы сделали. У вас есть числа до и после? Если нет, вы не оптимизировали. Вы поменяли код и понадеялись.
С чего начать. Освойте go test -bench и pprof. Это два достаточно простых инструмента. Я разбирал их в статьях про профилирование в Go и бенчмарки и оптимизацию. Отдельно стоит знать, сколько стоит сам профайлер, чтобы не бояться включать его в продовой среде.
3. Выбирают скучное решение
Новая библиотека всегда выглядит привлекательнее старой. У неё свежий README, красивый бенчмарк на главной странице и обещание, что вот теперь точно заживём по-новому.
Сильные инженеры к этому равнодушны. Не потому, что консервативны, а потому, что уже платили временем и нервами за свой и чужой энтузиазм. Они знают, что зависимость это не только код и функционал, который вы получаете. Это ещё и обязательство: обновлять, следить за уязвимостями, разбираться в чужих багах, объяснять новичкам, зачем тут нужна именно такая конструкция.
Поэтому у сильных другой порядок вопросов. Не «что интересного попробовать», а «чем плохо то, что уже есть». Если внятного ответа нет, побеждает стандартная библиотека. В Go это работает лучше, чем во многих других языках: net/http и database/sql закрывают удивительно много.
Ту же сдержанность видно в конкурентности. Начинающий, освоив горутины, начинает лепить их везде, потому что может. Строго однопоточная задача? Заведём рабочую горутину и вторую, которая ждёт первую. Сильный сначала спрашивает, а что мы, собственно, выигрываем. Горутина не бесплатна, а её жизненный цикл кто-то должен контролировать. Утечка горутин и гонка данных обходятся дороже сэкономленных миллисекунд. Про то, когда конкурентность оправдана, я писал в разборе паттернов и основах горутин и каналов.
Скучный выбор редко радует автора и не делает его самым умным в комнате. Зато он радует того, кто придёт в проект через два года. Иногда это кто-то другой, а иногда это вы.
Как проверить. Посмотрите на свой go.mod. Про каждую зависимость вы можете сказать, что именно она даёт и почему без неё было бы хуже? Если по половине строк ответ «так исторически сложилось», это диагноз проекту, а не вам лично. Но разбирать этот список рано или поздно кому-то придётся.
С чего начать. Возьмите одну зависимость, которая закрывает мало и тянет много. Посмотрите, что придётся написать руками, если её убрать. Часто это тридцать строк, которые вы полностью контролируете. Такую замену не обязательно делать сразу, но полезно знать её цену заранее.
4. Проектируют на следующий порядок, а не на бесконечность
Разговор «сделаем задачу быстро или сделаем правильно» есть в каждой команде. Мне он кажется поставленным неверно. «Правильность» не бывает одна на все случаи. Она всегда зависит от нагрузки, сроков и цены ошибки.
Типичная ловушка выглядит так. Сервис обслуживает несколько запросов в секунду. Команда полгода строит под него бесконечно масштабируемую схему с шардированием и переключением между ЦОДами. Нагрузка за это время вырастает лишь вдвое. Схема ни разу не пригодилась, а полгода потрачены.
Обратная ситуация не лучше. Всё пишется на коленке по принципу «и так сойдёт», а потом рост в три раза кладёт сервис целиком.
Выход не в компромиссе между двумя подходами, а в постановке другого вопроса. Не «а вдруг мы вырастем в тысячу раз», а «что сломается первым, если мы вырастем в десять». Ответ на него почти всегда конкретный: закончатся соединения с базой, упрёмся в предел одного инстанса, переполнится очередь. Вот это и надо чинить. Остальное подождёт следующего порядка, и к тому моменту вы будете знать о системе больше, чем сегодня.
Такой подход требует смелости. Осознанно оставить систему неготовой к нагрузке, которой ещё нет, психологически сложнее, чем построить с запасом. Но запас на вырост это тот же технический долг, только оплаченный вперёд и без гарантии, что он понадобится. Про баланс между «сейчас» и «потом» я подробнее писал в статье о техдолге и сроках.
Как проверить. Возьмите свой текущий сервис и назовите его следующий предел. Не «он масштабируется», а конкретно: при какой нагрузке и какой компонент ляжет первым. Если ответа нет, вы не знаете, устойчива система или просто ещё не проверялась.
С чего начать. Возьмите текущий пик нагрузки, умножьте на десять и пройдите по системе с этой цифрой. Пул соединений, размер очереди, память на инстанс, таймауты соседей. Первое место, где цифра не сходится, и есть ваш следующий предел. Это упражнение занимает час и обычно отменяет пару кварталов работы «на вырост».
5. Прячут сложность, а не выставляют её напоказ
Есть соблазн, которому поддаются многие способные инженеры. Задача была тяжёлая, решение получилось хитрым, и хочется, чтобы коллеги это заметили и оценили. Сложность просачивается наружу: в имена, в сигнатуры, в набор обязательных шагов для вызывающего.
Сильные разработчики делают наоборот. Чем тяжелее внутри, тем проще снаружи. Пользователь пакета не должен догадываться, сколько крови стоило заставить это работать.
В Go у такой заботы есть узнаваемые признаки. Интерфейсы маленькие и объявлены на стороне потребителя, а не там, где лежит реализация. Ошибки несут контекст, по которому понятно, что и где сломалось даже без похода в отладчик. Публичная часть пакета небольшая, а всё остальное спрятано. Имя пакета говорит о его области, а не о том, что туда сваливали всё подряд.
Как проверить. Дайте коллеге ваш пакет и не отвечайте на вопросы. Сколько он продержится, прежде чем полезет в исходники? Всё, что ему пришлось выяснять чтением реализации, это сложность, которую вы не спрятали, а переложили на него.
С чего начать. Три темы, с которых обычно и начинается наведение порядка: интерфейсы, идиоматичная обработка ошибок и организация вспомогательных пакетов.
6. Усиливают команду, а не становятся бас-фактором
В достаточно большой команде почти всегда заводится человек, который знает всё. Каждое архитектурное решение согласуют с ним. Каждый спорный вопрос идёт к нему. Он в курсе, почему в этом модуле такая странная логика, потому что писал её сам четыре года назад.
Такой человек чувствует себя незаменимым, и формально так и есть. Но по факту он узкое место. Команда не может двигаться быстрее, чем он успевает отвечать. Он уходит в отпуск, и половина решений подвисает.
Сильные инженеры от этой роли уходят сознательно. Не потому что скромные, а потому что видят потолок. Личная производительность конечна у всех. А влияние через других людей растёт быстрее, чем ваше личное.
На практике это выглядит буднично и незаметно. Комментарий в коде объясняет, почему сделано так, а не что делает эта строка. Неочевидное решение попадает в короткий документ рядом с кодом, а не остаётся в голове автора. Ревью пишется не в жанре «переделай», а в жанре «здесь выстрелит вот это, потому что вот так». Вопрос новичка получает не готовый ответ, а направление, куда смотреть и что исследовать. Это дольше на пятнадцать минут сегодня и дешевле на месяцы через год.
Как проверить. Составьте список задач, которые в вашей команде может закрыть только один человек. Если в этом списке есть вы, это не заслуга, а риск, который команда пока не осознала. Про то, почему команда работает как умножение, а не как сумма, у меня есть отдельная статья.
С чего начать. На следующем ревью замените одно замечание «переделай» на объяснение, что именно сломается и почему. Один комментарий за раз, не весь стиль сразу. А если эта черта у вас уже проявилась, возможно, стоит присмотреться к переходу в тимлиды.
7. Относятся к инциденту как к обучению
Ошибаются все. Разница между обычным инженером и сильным видна не в момент падения, а через неделю после.
Обычный сценарий: инцидент починили, постмортем написали, в разделе «что делать» прописано «быть внимательнее на ревью». Через полгода то же самое падает снова, хоть и немного иначе.
Сильный инженер задаёт другой вопрос. Не «как починить этот баг», а «как сделать, чтобы баги этого класса больше не доезжали до продуктивной среды». Ответ обычно скучный и технический. Тест, который воспроизводит ситуацию. Правило линтера. Инвариант, зашитый в тип, чтобы неверное состояние стало непредставимым. Таймаут там, где его не было.
Разница в масштабе. Первый подход закрывает один случай. Второй закрывает целый класс проблем и работает на всю команду, включая тех, кто придёт через год и об этом инциденте никогда не узнает.
Отсюда же вырастает нормальное отношение к собственным ошибкам. Человек, который боится ошибиться, прячет инциденты и тихо чинит в одиночку. Человек, который видит в них обучающий материал, разбирает их подробно и явно для всех. Второй растёт быстрее, потому что учится не только на своих случаях и помогает учиться остальным.
Как проверить. Откройте постмортемы за последний год. Сколько пунктов «что делать» превратились в код, который лежит в репозитории? Если большинство осталось обещанием быть внимательнее, класс проблем никуда не делся и ждёт повторения.
С чего начать. После следующего инцидента задайте один вопрос: что должно было упасть раньше и громче. Ответ и есть недостающая проверка. Обычно она превращается в тест, а иногда в фаззинг. Как фаззинг вылавливает то, что руками не придумаешь, я показывал на реальном баге в библиотеке.
Что связывает эти черты
Перечитайте список. В нём нет ничего специфичного для Go. Это поведение, и его можно поставить себе так же, как любую другую привычку.
Сильный инженер относится к своей нынешней версии как к временной. Он не защищает то, что уже знает. Он готов обнаружить, что его модель устарела, и перестроить её.
Это видно даже в том, как человек описывает свою работу. Один говорит через технологии: пишу на Go, у нас Kafka и Postgres. Другой описывает задачу, которую система решает, и уже потом, если спросите, чем именно. Второй обычно сильнее. Он держит в голове, зачем всё это, а технологии для него инструмент, а не идентичность.
И речь не столько про саморазвитие как таковое, сколько про трезвый расчёт. У знаний есть срок годности. Технологии, на которых держалась карьера десять лет назад, сегодня в лучшем случае строчка в резюме. Единственный навык, который не протухает, это скорость освоения незнакомого. Всё остальное надстройка над ним.
Отсюда, кстати, и простой способ измерить собственный рост. Не по должности и не по количеству прочитанных статей. Задайте себе вопрос: что я умею делать сегодня, чего не умел год назад? Именно умею, а не знаю о существовании. Если ответ есть и он конкретный, вы растёте. Если приходится напрягаться, вы просто набираете стаж. Это разные вещи, и повышение обычно приходит только за первое.
Итог
Лучшие Go-разработчики, которых я встречал, отличались не знанием языка. Они разбирались в механике, а не в приметах. Спорили цифрами. Выбирали скучное. Проектировали на следующий порядок, а не на бесконечность. Прятали сложность внутрь. Растили других вместо того, чтобы становиться бас-фактором. И вытаскивали из каждого инцидента что-то, что переживало инцидент.
Каждую из этих черт можно начать тренировать на следующей же задаче. Начните с одной, самой неудобной для вас. Обычно она и есть та, которой не хватает больше остальных.
Теги: