В конце 2018-го года в Самаре состоялся Panda-Meetup #9 Frontend. На этом мероприятии я попробовал себя в новой роли и выступил с докладом. Меня зовут Евгений Холмов. В программирование я пришёл более 10 лет назад, будучи студентом. Последние 5 лет я занимаюсь разработкой систем дистанционного обслуживания в крупнейших российских банках, из них два года на позиции руководителя. За это время я поучаствовал в создании нескольких ведущих российских enterprise-приложений, пройдя через тернии тяжелых архитектурных, интеграционных и процессных решений. Опираясь на свой опыт разработки нескольких крупных интернет-банков, я рассказал гостям митапа, какую выгоду даёт следование правилам хорошего стиля в разработке. Озвученные цифры впечатлили слушателей. Участники встречи меня буквально завалили вопросами. Не исключено, что и среди читателей Хабра эта тема найдёт живой отклик.
Видео моего выступления можно посмотреть на Youtube.
А для хабровчан я подготовил текстовую версию своего доклада.
Изучая вопросы читаемости кода, на StackOverflow я наткнулся на любопытную тему. В ней обсуждали, насколько важен для бизнеса читаемый код, и важнее ли он производительности и корректности. В итоге, по мнению участников сообщества, самым важным критерием оказалась именно читаемость кода.
Проблемами читаемости кода в то время я интересовался не случайно — я занимался разработкой очередного интернет-банка. На frontend’е использовались HTML5, AngularJS, LESS. Проект разрабатывался для одного из крупнейших банков России. Перешёл я на этот проект из Промсвязьбанка, где разрабатывал очень похожий интернет-банк: тот же сегмент клиентов, такое же количество пользователей, почти такой же стек технологий (HTML5, AngularJS, SCSS). Но, не смотря на сильную схожесть проектов, переход для меня был очень непростой. Всё дело в стиле. Код этих проектов разительно отличался по оформлению и общей читаемости. Если в Промсвязьбанке разработчики строго следовали гайдлайнам, то в новом проекте чётких правил стиля не было.
Вот тогда-то я и смог понять (и даже посчитать) какую выгоду крупному проекту приносит чёткое следование стайлгайдам. Проанализировав затраты на доработки на своём новом проекте, я пришёл к тому же выводу, что и читатели StackOverflow: читаемость – самый важный критерий.
Для AngularJS, и Angular 2+ известный эксперт в мире frontend-разработки John Papa создал стайлгайды, описывающие правила хорошего стиля при разработке Angular-приложений. Работая в Промсвязьбанке, я и узнал об этих стайлгайдах. Команда чётко им следовала на всех проектах. А вот на новом проекте код достался от подрядчиков, которые о стиле программирования настолько серьёзно не задумывались. Со своей новой командой я ежедневно сталкивался с задачами, которые раньше уже когда-то решал. Однако это не давало мне никаких преимуществ, а лишь боль и страдания.
В стайлгайде Джона Папы перечислено множество правил. Повторно перечислять их нет смысла, они прекрасно изложены в первоисточнике. Я ограничусь всего парой примеров, которые красочно продемонстрируют пользу рекомендаций эксперта.
Пример из практики. Пользователям интернет-банка отображались неактуальные курсы валют. Мне поставили задачу обеспечить подгрузку актуальных курсов в момент совершения операции. Я ожидал увидеть код, отвечающий за загрузку курсов валют, в сервисах и компонентах. Но, конечно, там его не оказалось (иначе мне было бы не о чем писать). Оказалось, курсы валют извлекались и фильтровались на уровне старта приложения, в файле app.run, после чего помещались в Session Storage. Это явно не лучший подход. К тому же всё равно для решения задачи код пришлось оттуда вынести. На простую задачу ушёл целый день.
А теперь смотрим правило Bootstrapping (Style 02-05) из стайлгайда.
Размещайте код, обслуживающий запуск приложения, в отдельном файле, и не размещайте в этом файле логику приложения. Логику стоит размещать в сервисах и компонентах.
Как видно выше, пренебрежение всего одним правилом может повлечь неприятности. Но по-настоящему серьёзные последствия мы получим, если не прислушаемся сразу к нескольким рекомендациям.
Пример из практики. Наша команда получила задание: для платежей ЖКХ добавить возможность включения в платеж суммы добровольного страхования. На frontend-части это сводилось к простой задаче: разместить на форме чек-бокс, включение которого добавляло бы к сумме платежа стоимость страховки. В Промсвязьбанке мы справились с похожей задачей очень быстро. Но на новом проекте всё пошло не так.
Из-за того, что код модуля платежей был крайне неудачно организован, решение растянулось на месяцы. В поисках наиболее подходящего варианта мы несколько раз откладывали эту задачу и брали её заново. А банк тем временем испытывал трудности с платежами в пользу ЖКХ. В конечном счете, мы, конечно, разобрались с проблемой. Но мне стало интересно, сколько может стоить организации плохой код на примере такой простой задачи. Я посчитал, сколько времени потратили на решение проблемы все вовлечённые в неё специалисты. Вооружился статистикой по средним зарплатам. Прикинул и округлил сопутствующие потери. В итоге пришёл к выводу, что при наличии изначально хорошего кода в модуле платежей мы бы сэкономили не менее 5 млн. рублей.
Сверившись со стайлгайдом Джона Папы, я увидел, что наш модуль нарушал сразу несколько правил хорошего кода. Прикинув степень влияния каждого из этих правил, я выделил три, нарушение которых сильнее всего затормозило нашу работу:
Наш интернет-банк поддерживал много платежей, часть из которых по своей модели были совсем не похожи друг на друга. Тем не менее, программисты постарались сделать «универсальную» платёжную форму, которая сможет работать со всеми вариантами платежей.
На самой форме было свёрстано много разных блоков, которые показывались при одних условиях и никогда не показывались при других. При этом их обслуживала общая логика, что сильно раздуло ViewModel’и.
В итоге всё это приводило к тому, что если что-то добавлялось в одном месте, то неизбежно отваливалось в другом. Сперва у нас получилось, что выбор чекбокса не менял сумму. Попробовали исправить поле суммы. Но оно стало нередактируемым на всех остальных видах платежей. Попробовали заменить поле суммы на новое. Но отвалилась общая логика валидации: чек-бокс работает, зато кнопка оплаты не доступна. Попадали в похожую ситуацию?
А теперь посмотрим, что говорит правило Rule of One.
Каждый отдельный элемент, например компонент или сервис, следует размещать в отдельном файле. Нужно следить, чтобы файл не разрастался. Если количество строк кода превышает магическое число 400, это верный признак того, что компонент стоит разбивать.
Когда мы закончили с самой формой, выяснилось, что перестали работать функции, лежащие за пределами формы. Например, повредилась работа с черновиками платежей. Появились ошибки в истории операций. А происходило это потому, что на модель и контроллер платежа формы были завязаны и другие компоненты, помимо самой платёжной формы. Ещё хуже оказалось то, что попытка починить этот функционал возвращала нас к сломанной платёжной форме.
Мы имели дело с классическим случаем нарушения Single Responsibility. Этот принцип давно вошёл в джентльменский набор опытных программистов. И его же стайлгайд явно рекомендует использовать при разработке Angular-приложений.
Применяйте single responsibility principle (SRP).
Самое неудобное, с чем пришлось столкнуться, это навигация по самому проекту. Логика была разбросана по нескольким файлам, объединённым в непонятную иерархию наследований и делегирования. Из названий этих файлов совсем не следовало их назначение. Файлы лежали в разных папках. В некоторых папках, например, Controllers, среди десятков других файлов, всё время приходилось искать те, которые нужны конкретно сейчас. Количество открытых вкладок в среде разработки не вмещалось на экран компьютера и в мою голову. Во время отладки, доходя до N-го по счёту компонента, я уже забывал, по какому пути и для чего я сюда пришёл. Периодически приходилось проводить полноценный reverse engineering с включённым отладчиком, чтобы найти, в какой папке проекта лежит файл с каким-то определённым функционалом. Накопившееся чувство ненависти к коду затуманивало взгляд и отвлекало от работы.
Мудрый стайлгайд знает и эту проблему. Для её решения создана группа правил LIFT. LIFT – это аббревиатура от слов Locate, Identify, Flat и Try to be DRY. Согласно принципам LIFT, нужно организовать структуру проекта таким образом, чтобы:
Overall structural guidelines предлагает под каждый отдельный компонент заводить свою папку и группировать в ней все файлы, относящиеся к этому компоненту. Например, удобнее, когда файл payment-form.controller.js лежит не в папке Controllers, а в папке payment-form вместе с payment-form.html и payment-form.css.
Folders-by-feature structure рекомендует для фич и предметных областей проекта создавать отдельные папки с соответствующими именами. Согласно этому правилу, упомянутая выше папка payment-form должна была бы лежать в папке payments вместе с другими компонентами, требуемыми для работы с платежами.
В итоге, если бы авторы кода следовали LIFT-правилам, структура проекта выглядела бы примерно так:
Согласитесь, такой вариант организации кода гораздо удобнее для читателя. И он бы сэкономил всей нашей команде очень много времени.
Надеюсь, приведённые выше примеры прозвучали достаточно убедительно, чтобы у читателя, не задумавшегося ранее о стиле программирования, появилось желание заглянуть в гайдлайн. Вообще, хорошо читаемый и поддерживаемый код приносит пользу не только бюджету проекта, но и самому программисту. Читаемый код легче проходит ревью. Если вам понадобится помощь коллег, в читаемый код они быстрее вникнут. Если вы ещё только начинающий специалист, то именно читаемость кода – то первое, над чем нужно работать. Даже если вы будете писать суперкорректный и прекрасно оптимизированный код, но абсолютно не читаемый и не поддерживаемый, в коллективе вас, безусловно, будут уважать, но в бар после работы приглашать уже не будут.
Если этот текст заставил вас о чём-то задуматься или напомнил что-то до боли знакомое, буду рад увидеть это в комментариях к посту.
Видео моего выступления можно посмотреть на Youtube.
А для хабровчан я подготовил текстовую версию своего доклада.
Изучая вопросы читаемости кода, на StackOverflow я наткнулся на любопытную тему. В ней обсуждали, насколько важен для бизнеса читаемый код, и важнее ли он производительности и корректности. В итоге, по мнению участников сообщества, самым важным критерием оказалась именно читаемость кода.
Проблемами читаемости кода в то время я интересовался не случайно — я занимался разработкой очередного интернет-банка. На frontend’е использовались HTML5, AngularJS, LESS. Проект разрабатывался для одного из крупнейших банков России. Перешёл я на этот проект из Промсвязьбанка, где разрабатывал очень похожий интернет-банк: тот же сегмент клиентов, такое же количество пользователей, почти такой же стек технологий (HTML5, AngularJS, SCSS). Но, не смотря на сильную схожесть проектов, переход для меня был очень непростой. Всё дело в стиле. Код этих проектов разительно отличался по оформлению и общей читаемости. Если в Промсвязьбанке разработчики строго следовали гайдлайнам, то в новом проекте чётких правил стиля не было.
Вот тогда-то я и смог понять (и даже посчитать) какую выгоду крупному проекту приносит чёткое следование стайлгайдам. Проанализировав затраты на доработки на своём новом проекте, я пришёл к тому же выводу, что и читатели StackOverflow: читаемость – самый важный критерий.
Правила хорошего стиля
Для AngularJS, и Angular 2+ известный эксперт в мире frontend-разработки John Papa создал стайлгайды, описывающие правила хорошего стиля при разработке Angular-приложений. Работая в Промсвязьбанке, я и узнал об этих стайлгайдах. Команда чётко им следовала на всех проектах. А вот на новом проекте код достался от подрядчиков, которые о стиле программирования настолько серьёзно не задумывались. Со своей новой командой я ежедневно сталкивался с задачами, которые раньше уже когда-то решал. Однако это не давало мне никаких преимуществ, а лишь боль и страдания.
Сказка о потерянном времени или для чего нам все эти правила
В стайлгайде Джона Папы перечислено множество правил. Повторно перечислять их нет смысла, они прекрасно изложены в первоисточнике. Я ограничусь всего парой примеров, которые красочно продемонстрируют пользу рекомендаций эксперта.
Начнём с простого
Пример из практики. Пользователям интернет-банка отображались неактуальные курсы валют. Мне поставили задачу обеспечить подгрузку актуальных курсов в момент совершения операции. Я ожидал увидеть код, отвечающий за загрузку курсов валют, в сервисах и компонентах. Но, конечно, там его не оказалось (иначе мне было бы не о чем писать). Оказалось, курсы валют извлекались и фильтровались на уровне старта приложения, в файле app.run, после чего помещались в Session Storage. Это явно не лучший подход. К тому же всё равно для решения задачи код пришлось оттуда вынести. На простую задачу ушёл целый день.
А теперь смотрим правило Bootstrapping (Style 02-05) из стайлгайда.
Размещайте код, обслуживающий запуск приложения, в отдельном файле, и не размещайте в этом файле логику приложения. Логику стоит размещать в сервисах и компонентах.
Пример на миллион
Как видно выше, пренебрежение всего одним правилом может повлечь неприятности. Но по-настоящему серьёзные последствия мы получим, если не прислушаемся сразу к нескольким рекомендациям.
Пример из практики. Наша команда получила задание: для платежей ЖКХ добавить возможность включения в платеж суммы добровольного страхования. На frontend-части это сводилось к простой задаче: разместить на форме чек-бокс, включение которого добавляло бы к сумме платежа стоимость страховки. В Промсвязьбанке мы справились с похожей задачей очень быстро. Но на новом проекте всё пошло не так.
Из-за того, что код модуля платежей был крайне неудачно организован, решение растянулось на месяцы. В поисках наиболее подходящего варианта мы несколько раз откладывали эту задачу и брали её заново. А банк тем временем испытывал трудности с платежами в пользу ЖКХ. В конечном счете, мы, конечно, разобрались с проблемой. Но мне стало интересно, сколько может стоить организации плохой код на примере такой простой задачи. Я посчитал, сколько времени потратили на решение проблемы все вовлечённые в неё специалисты. Вооружился статистикой по средним зарплатам. Прикинул и округлил сопутствующие потери. В итоге пришёл к выводу, что при наличии изначально хорошего кода в модуле платежей мы бы сэкономили не менее 5 млн. рублей.
Сверившись со стайлгайдом Джона Папы, я увидел, что наш модуль нарушал сразу несколько правил хорошего кода. Прикинув степень влияния каждого из этих правил, я выделил три, нарушение которых сильнее всего затормозило нашу работу:
- Rule of One – на нарушение этого правила я списал 25% потерянного времени;
- Single Responsibility – отклонение от этого принципа сделало вклад в общие потери в 35%;
- LIFT – нарушение этой группы подходов, считаю, затормозили нас сильнее всего, 40%.
Rule of One
Наш интернет-банк поддерживал много платежей, часть из которых по своей модели были совсем не похожи друг на друга. Тем не менее, программисты постарались сделать «универсальную» платёжную форму, которая сможет работать со всеми вариантами платежей.
На самой форме было свёрстано много разных блоков, которые показывались при одних условиях и никогда не показывались при других. При этом их обслуживала общая логика, что сильно раздуло ViewModel’и.
В итоге всё это приводило к тому, что если что-то добавлялось в одном месте, то неизбежно отваливалось в другом. Сперва у нас получилось, что выбор чекбокса не менял сумму. Попробовали исправить поле суммы. Но оно стало нередактируемым на всех остальных видах платежей. Попробовали заменить поле суммы на новое. Но отвалилась общая логика валидации: чек-бокс работает, зато кнопка оплаты не доступна. Попадали в похожую ситуацию?
А теперь посмотрим, что говорит правило Rule of One.
Каждый отдельный элемент, например компонент или сервис, следует размещать в отдельном файле. Нужно следить, чтобы файл не разрастался. Если количество строк кода превышает магическое число 400, это верный признак того, что компонент стоит разбивать.
Single Responsibility
Когда мы закончили с самой формой, выяснилось, что перестали работать функции, лежащие за пределами формы. Например, повредилась работа с черновиками платежей. Появились ошибки в истории операций. А происходило это потому, что на модель и контроллер платежа формы были завязаны и другие компоненты, помимо самой платёжной формы. Ещё хуже оказалось то, что попытка починить этот функционал возвращала нас к сломанной платёжной форме.
Мы имели дело с классическим случаем нарушения Single Responsibility. Этот принцип давно вошёл в джентльменский набор опытных программистов. И его же стайлгайд явно рекомендует использовать при разработке Angular-приложений.
Применяйте single responsibility principle (SRP).
LIFT
Самое неудобное, с чем пришлось столкнуться, это навигация по самому проекту. Логика была разбросана по нескольким файлам, объединённым в непонятную иерархию наследований и делегирования. Из названий этих файлов совсем не следовало их назначение. Файлы лежали в разных папках. В некоторых папках, например, Controllers, среди десятков других файлов, всё время приходилось искать те, которые нужны конкретно сейчас. Количество открытых вкладок в среде разработки не вмещалось на экран компьютера и в мою голову. Во время отладки, доходя до N-го по счёту компонента, я уже забывал, по какому пути и для чего я сюда пришёл. Периодически приходилось проводить полноценный reverse engineering с включённым отладчиком, чтобы найти, в какой папке проекта лежит файл с каким-то определённым функционалом. Накопившееся чувство ненависти к коду затуманивало взгляд и отвлекало от работы.
Мудрый стайлгайд знает и эту проблему. Для её решения создана группа правил LIFT. LIFT – это аббревиатура от слов Locate, Identify, Flat и Try to be DRY. Согласно принципам LIFT, нужно организовать структуру проекта таким образом, чтобы:
- было понятно, куда размещать новые компоненты и где искать уже имеющиеся (Locate);
- было понятно назначение компонента по названию файла (Identify);
- было легко ориентироваться во вложенности папок, использовать для них максимально плоскую структуру (Flat);
- стараться не повторять свой код, но не в ущерб остальным правилам (Try to be DRY).
Подход LIFT дополняют ещё два правила.
Overall structural guidelines предлагает под каждый отдельный компонент заводить свою папку и группировать в ней все файлы, относящиеся к этому компоненту. Например, удобнее, когда файл payment-form.controller.js лежит не в папке Controllers, а в папке payment-form вместе с payment-form.html и payment-form.css.
Folders-by-feature structure рекомендует для фич и предметных областей проекта создавать отдельные папки с соответствующими именами. Согласно этому правилу, упомянутая выше папка payment-form должна была бы лежать в папке payments вместе с другими компонентами, требуемыми для работы с платежами.
В итоге, если бы авторы кода следовали LIFT-правилам, структура проекта выглядела бы примерно так:
Согласитесь, такой вариант организации кода гораздо удобнее для читателя. И он бы сэкономил всей нашей команде очень много времени.
Хорошие программисты отправляются в бар
Надеюсь, приведённые выше примеры прозвучали достаточно убедительно, чтобы у читателя, не задумавшегося ранее о стиле программирования, появилось желание заглянуть в гайдлайн. Вообще, хорошо читаемый и поддерживаемый код приносит пользу не только бюджету проекта, но и самому программисту. Читаемый код легче проходит ревью. Если вам понадобится помощь коллег, в читаемый код они быстрее вникнут. Если вы ещё только начинающий специалист, то именно читаемость кода – то первое, над чем нужно работать. Даже если вы будете писать суперкорректный и прекрасно оптимизированный код, но абсолютно не читаемый и не поддерживаемый, в коллективе вас, безусловно, будут уважать, но в бар после работы приглашать уже не будут.
Если этот текст заставил вас о чём-то задуматься или напомнил что-то до боли знакомое, буду рад увидеть это в комментариях к посту.