
Технический вкус — это не то же самое, что технический навык. Вы можете быть технически подкованы, но иметь плохой технический вкус и наоборот. Как и любой вкус в принципе, технический вкус зачастую не зависит от наличия фактического навыка. Вы же можете отличить хорошую еду от плохой, не умея готовить? То же самое с ПО — вы вполне способны понять, нравится ли оно вам, не имея возможности его создать. И если технические навыки можно наработать через усердное изучение и повторение, то хороший вкус вырабатывается менее очевидным путём.
Вот некоторые индикаторы его наличия:
Какой код вы сочтёте «хорошим»? А какой «плохим»?
Какие проектные решения вам действительно импонируют, а какие просто устраивают?
Какие программные задачи интересны для вас настолько, что не выходят из головы даже после работы? А каких задач вы стараетесь избегать?
Я считаю, что вкус — это умение адаптироваться под набор инженерных ценностей, которые подходят для вашего текущего проекта.
Почему вкус и навык — это разные вещи
Но разве все перечисленные индикаторы не являются просто частью навыка? К примеру, не будет ли код выглядеть хорошим, если он действительно хорош? Не думаю.
Разберём практический случай. Лично я считаю, что код с использованием функций map
и filter
выглядит лучше, чем построенный на цикле for
. И с позиции разработки такое мнение вроде бы логично. Например, map
и filter
обычно задействуют в работе более понятные чистые функции и исключают целый класс ошибок итерации, а именно ошибок на единицу (off-by-one error). Мне кажется, что это уже не вопрос вкуса — в этом случае я прав, а те, кто считает иначе, ошибаются.
Но это резкое заявление, и я понимаю, что на деле всё намного сложнее. В языках вроде Go принципиально нет функций map
и filter
. С позиции производительности перебор элементов при помощи for
вроде как проще, и его легче расширить на другие подходы к итерации (например, когда перебирается по два элемента за раз). Но все эти причины важны для меня не так сильно, как преимущества от использования map
и filter
. Именно поэтому я не пишу много циклов for
— но с моей стороны было бы слишком надменным утверждать, что разработчики, которые предпочитают использовать эти циклы, менее опытны. Во многих случаях у них есть технические навыки, которых нет у меня. Просто для них важнее другие аспекты.
Иными словами, наше расхождение во мнениях сводится к различиям в ценностях. Я писал об этом в статье «I don’t know how to build software and you don’t either». Даже если для этих широких технических дебатов и существуют абсолютные ответы, ни один разработчик не может их знать, так как весь опыт индустрии в карьеру одного отдельного человека не втиснуть. Мы все как минимум отчасти опираемся на собственный опыт — на свой конкретный набор инженерных ценностей.
Что же такое вкус в разработке
При разработке ПО почти все решения представляют собой компромисс. Редко бывает, что мы выбираем из двух опций, среди которых одна явно лучше. Чаще каждая из этих опций имеет свои плюсы и минусы. Нередко приходится делать трудный выбор между ценностями — после достижения определённой точки уже не получается легко наращивать производительность кода без ухудшения, к примеру, его читаемости.1
Реальное понимание этого, на мой взгляд, является сильнейшим индикатором зрелости в сфере разработки ПО. Незрелые разработчики часто категоричны в принятии решений. Они считают, что всегда правильнее использовать подход Х, а не Y. Опытные же специалисты обычно с готовностью рассматривают все варианты, так как понимают, что каждый из них имеет свои преимущества. Смысл не в том, чтобы определить, какая технология лучше — X или Y — а в том, перевесят ли преимущества X возможности Y в конкретном случае.
Иными словами, незрелые разработчики излишне догматичны в своём вкусе. Они осознают, что им нравится, но принимают это видение за принципиальную позицию разработки. Так что же определяет конкретный вкус разработчика?
На мой взгляд, ваш вкус включает в себя целый набор ценностей, которые вы находите наиболее важными. Например:
Отказоустойчивость. Если компонент инфраструктуры даёт сбой (падает сервис, пропадает сетевое подключение), продолжит ли система работать? Может ли она восстанавливаться без вмешательства человека? Не затесались ли в критический путь программы какие-то не самые важные операции?
Читаемость. Легко ли воспринимается ПО на первый взгляд, и насколько быстро с ним можно познакомить новых разработчиков? Не растянуты ли функции, и насколько грамотно они проименованы? Хорошо ли система задокументирована?
Корректность. Есть ли возможность выразить в системе недопустимое состояние? Насколько система ограничена тестами, типами и утверждениями? Используются ли в тестах техники вроде фаззинга? И для самых крайних случаев, доказывалась ли корректность программы формальными методами вроде Alloy?
Гибкость. Легко ли расширять систему? Насколько сложно вносить в неё изменения? Если мне нужно что-то изменить, сколько частей программы придётся затронуть?
Портируемость. Привязана ли система к некой конкретной операционной среде (например, Microsoft Windows или AWS)? Если систему нужно будет развернуть где-то ещё, много ли для этого потребуется инженерных усилий?
Масштабируемость. Если трафик увеличится в 10 раз, система выдержит? А если в 100 раз? Потребуется ли ей выделение дополнительных ресурсов, или же она сможет масштабироваться автоматически? Какие узкие места потребуют вмешательства разработчиков?
Скорость разработки. Если мне нужно расширить систему, как быстро я смогу это сделать? Сможет ли с ней работать большинство инженеров, или же потребуется привлечение эксперта в предметной области?
В контексте проектирования систем есть и много других ценностей: изящность, современность, использование опенсорсных решений, денежная стоимость поддержания работы и так далее. Всё это важно, но ни один разработчик не будет придавать равную значимость каждому пункту. Ваш вкус определяется тем, какие из ценностей вы ставите во главу угла. Например, если для вас важно получить производительную и корректную систему, а не ускорить её разработку, то вы предпочтёте использовать Rust, а не Python. Если вам важнее обеспечить масштабируемость, а не портируемость, то вы наверняка сделаете упор на конкретных инструментах и особенностях вашего провайдера (например, AWS). Если вы готовы пожертвовать скоростью ради отказоустойчивости, то, скорее всего, разделите трафик между разными регионами. И так далее.2
Но все эти ценности можно разбить на ещё более детальные аспекты. Например, два разработчика, для которых очень важна читаемость, могут разойтись в своих мнениях, так как один предпочитает короткие функции, а другой — короткие стеки вызовов. Или же, если два разработчика в равной степени ценят корректность, то они могут поспорить из-за того, что один будет склонен к выполнению исчерпывающих тестов, а другой предпочтёт формальные методы проверки. Но суть здесь одна — в сфере проектирования ПО есть много разных ценностей. И поскольку они часто находятся в противофазе, каждому разработчику приходится делать акцент на одних в ущерб другим.
Как определить плохой вкус
Я уже говорил, что все перечисленные ценности важны, но несмотря на это, у человека может быть плохой вкус. В контексте разработки ПО это означает, что ваши ценности не очень подходят для проекта, над которым вы работаете.
Большинству из нас доводилось работать с такими людьми. Они приходят в проект, яро отстаивая какие-то свои идеи, предлагая использовать формальные методы, переписать всё на Go, применить мета-программирование Ruby, развернуть приложение в разных регионах и прочее — просто потому, что на их опыте это когда-то хорошо сработало. Независимо от того, является это правильным решением для вашего проекта или нет, они будут его отстаивать, так как это их вкус. И не успеешь глазом моргнуть, как уже будешь стремиться обеспечить максимальные показатели надёжности системы, делая её при этом совершенно непонятной для джунов.
Иначе говоря, основная причина плохого вкуса — это догматичность. Я никогда не буду доверять разработчику, который оправдывает свои решения словами «Это наилучший подход». Ни одно инженерное решение не является «наилучшим» во всех контекстах. Правильность того или иного подхода всегда будет зависеть от конкретной задачи.
Думаю, что разработчики с плохим вкусом подобны сломанным компасам — если вы находитесь в правильном положении, такой компас исправно будет указывать на Север. Но стоит вам начать перемещаться, он тут же собьёт вас с пути. То же касается разработчиков с плохим вкусом. Они могут вполне эффективно работать в конкретной области, где их предпочтения соответствуют потребностям проекта. Но в случае перехода на другой проект или работу процесс сразу выходит из колеи. Ни одна должность не остаётся неизменной долгое время, особенно в эти непростые времена после 2021 года.
Как определить хороший вкус
Разглядеть хороший вкус гораздо труднее, нежели технический навык. Дело в том, что в отличие от навыка, вкус представляет умение выбирать правильный набор ценностей для конкретной технической задачи. Поэтому определить наличие хорошего вкуса намного сложнее: его нельзя проверить на смоделированных задачах или простыми вопросами о технических фактах. Задача должна быть актуальной и включать запутанный контекст реальной действительности.
Утверждать о наличии хорошего вкуса можно тогда, когда проекты, над которыми вы работаете, преуспевают. Если вы не вносите ощутимый вклад в структуру проекта (например, просто обрабатываете тикеты), то признаком наличия у вас хорошего вкуса будет успешность тех направлений, со структурой которых вы согласны, и провал тех, структура которых вам не по душе. И здесь важна причастность к проектам разного вида. Если речь идёт лишь об одном проекте или о повторении аналогичных, то вы можете просто оказаться к ним удачно подготовленным. И даже если вы пройдёте через серию разных проектов, нет гарантии, что у вас есть хороший вкус в мало знакомых вам областях.3
А как выработать хороший вкус? Сложно сказать, но я советую работать в разных направлениях, обращая внимание на то, какие проекты или их части даются легко, а какие тяжело. Фокусируйтесь на гибкости: старайтесь не формировать категоричных универсальных мнений о том, как правильно писать ПО. Свой вкус я вырабатывал довольно долго, хотя не вижу препятствий для его ускоренного формирования у других. Уверен, есть одарённые люди, вкус которых значительно превосходит их опыт программирования. Здесь всё так же, как и в любых других областях.
Дополнение: эта статья вызвала бурное обсуждение на Hacker News, среди которого возникла интересная дискуссия на тему того, как вырабатывать хороший вкус у новичков. Было высказано мнение, что нужно как бы слегка зеркалить их вкус, чтобы они лучше видели и понимали его особенности. Некоторые комментаторы утверждали, что «вкус» не играет роли в разработке ПО. Они считают, что все задачи имеют единственно верное решение, к которому разработчики должны прийти аналитическим путём. Мне такое видение кажется вздором. Ведь очевидно же, что для любой инженерной задачи есть множество приемлемых решений, и в определённые моменты выбор сводится к личному предпочтению. Другие участники отметили, что я не упомянул о клиентах и бизнесе — согласен, но мне больше хотелось написать о том, что даже чисто технические решения подвержены влиянию вкусовщины.
Примечания
Естественно, это не всегда так. Бывают ситуации «win-win», когда можно одновременно улучшить ряд обычно противоречащих друг другу ценностных аспектов. Но всё же такие ситуации возникают редко. ↩
Как я уже говорил, в разных проектах, естественно, будут актуальны разные наборы ценностей. Но специалистам, работающим над этими проектами, всё равно придётся где-то провести черту, и здесь уже они будут опираться на личный вкус. ↩
Тем не менее я считаю, что хороший вкус человека может расширяться на другие сферы. У меня нет особого опыта по этой части, поэтому я решил написать примечание. Но если у вас достаточно гибкое мышление, и вы внимательны к деталям в области А, то наверняка также окажетесь гибким и внимательным к деталям в области В. ↩
Emelian
Я перелопатил «туеву хучу» опенсорсного кода на С++ и не разу он мне не понравился с точки зрения оформления и структуры.
Такое впечатление, что абсолютно все «приплюснутые» программисты, включая и наших и зарубежных, учились по одним и тем же сомнительным источникам, в части советов по стилю программирования.
В Си не нравится «монолитность» кода, точнее код там, обычно, оформлен как поток сознания их авторов, ничем не ограниченным «снаружи».
С++ – потенциально явно лучше. Он позволяет хорошо структурировать код на классы, неймспэйсы, файлы и каталоги, проекты и решения. Это, для меня, просто супер!
Но, вот стиль программирования, как правило, у всех ужасен. Главные претензии:
Когда нужно ставить разделители (пробелы и новые строки), – их не ставят, если это позволяет делать компилятор.
Когда не нужно делать лишние разделители – их, наоборот, делают.
Комментирование кода, обычно, небрежное.
Заголовки, сверху и снизу (в виде комментариев) для многострочных конструкций языка – не делаются либо делаются по принципу: «Нате вам и отстаньте!».
Не разделенная, мною, любовь всех злоупотреблять нижним регистром символов и излишне частым использованием символом «нижнее подчеркивание».
Часто, в одном файле, – слишком много классов.
Также не нравится, в пользовательских проектах, делать реализацию функций в h-файлах. Для библиотечных исходников, вроде, WTL – это нормально, поскольку их код я редактировать не намерен.
Ну, и так далее, по мелочам. Поэтому, Си-шный опенсорс я переделываю в С++-классы, вроде, консольного видеопроигрывателя FFPlay.c (скриншот подобной реализации для моей оконной программы можно посмотреть в http://scholium.webservis.ru/Pics/MediaText.png ).
А весь доступный «приплюснутый» код, я подвергаю, перед использованием, стилевому рефакторингу, под свой вкус, иначе, я не могу с ним работать. Хорошо, что, как программист, я всегда работал один. В команде я бы не ужился, разве что, только, в должности начальника «1С», по принципу: «Я начальник – ты дурак!» :) .
Для чужих проектов – те, которые компилируются без проблем. Если взять произвольный проект на С++, не говоря уже о Си, на Гитхабе и попробовать его скомпилировать из «коробки», то, с вероятностью 90% – не получится. Нужно, предварительно, долго плясать с бубном, чтобы данный проект просто скомпилировался. Не нравится, что авторы редко прикладывают к своим «шедеврам» скриншоты, не говоря уже о готовых бинарниках.
Поэтому, если чужой проект легко скомпилировался, то, тогда, даже его ужасный стиль не так раздражает. Ибо к стилевому рефакторингу мне не привыкать, тем более, что, при этом, лучше вникаешь в, собственно, код.
Интересны проблемы пользовательского интерфейса на C++ / WTL. Вот, неожиданно, для себя, столкнулся с вопросами рисования кнопок. Для кастомизации кнопок существует несколько методов. Наиболее типичные, это полностью собственные классы, наследование классов стандартных контролов, с внутренним сабклассингом либо без, и внешним сабклассингом стандартных элементов управления.
Мне нужна эмуляция кнопок, исключительно средствами пользовательского рисования. Другие методы работают, и даже этот метод работает, но «безоконное» рисование дочернего компонента (точнее, непосредственное рисование в главном окне приложения) приводит к «перемигиванию» других контролов, хотя, двойную буферизацию и обновление только субобластей, я использую часто, но, в данном случае, в буферизации не вижу смысла, а частичное обновление, почему-то, не работает.
Эти кнопки должны завершить мой очередной пет-проект, который может работать и без них (за счет клавиатурных команд, вроде, «энтера»). Однако хочется добавить их для «красоты», так, чтобы они не мешали работе других контролов. А почему мешают? – Пытаюсь разобраться.
Однако, поскольку я программирую как «свободный художник», то неинтересными задачами я просто не занимаюсь. Зачем? Когда и интересных хватает.