Prettier в крупных проектах: тратим 20 минут на настройку, забываем о форматировании навсегда

    Разработчики часто не могут договориться о форматировании кода, и типичный рабочий день для многих начинает выглядеть так: кофе, кодинг, всё мирно и хорошо, — а потом, бац, и наступает код-ревью, на котором выясняется, что ты где-то поставил скобочки не так или не перенес что-то на новую строчку.



    Год назад одна из команд в Skyeng сталкивалась с такими холиварами почти на каждом ревью. Но затем человек, у которого больше всех болело, сказал: «Теперь живем на Prettier'e, согласны?» За следующие месяцы ребята ни разу не поднимали вопрос о форматировании, а теперь эта штука стоит на всем монорепозитории фронтенда — и его использует каждая команда, которая туда заезжает.

    Что такое Prettier


    Prettier – инструмент для автоформатирования кода с поддержкой кучи инструментов, включая наши любимые Angular и Typescript. Он не модифицирует код, не заменяет тернарные операторы на if’ы и не разбивает длинные строки на несколько конкатенированных (об этом уже должен думать разработчик), а просто выводит то, что есть, с нужным форматированием.

    Как было раньше


    На тот момент в Skyeng было уже несколько десятков разработчиков, и каждый месяц могли приходить по 10-20 новичков. Все работали (и работают) в небольших командах — каждая, фактически, считается независимой “боевой единицей” со своими задачами и договоренностями.

    Давайте представим одну такую — безусловно, все совпадения случайны, — команду:

    Борис – миддл, который пришёл к нам из другой большой компании. У них был свой стайл-гайд, но он старается переучиться на наш: в целом, справляется, но время от времени делает что-то по части форматирования не так и узнаёт об этом только на код-ревью. Мелочь, но неприятно.

    Павел – более опытный разработчик. Знает своё дело, всегда выполняет задачи качественно и в срок, но иногда напрочь забывает про договоренности и пишет по-своему. В итоге его код не вписывается в большой проект, хоть и написан качественно и со вкусом.

    Артур – перфекционист с философией «чистый код – понятный код»: вечно парится по поводу код-стайла. Безусловно, отклонит код коллег с кучей комментов «здесь перенесите фигурную скобочку на следующую строчку» — и Борис потратит время на правки, а Павел – на споры уровня «тебе не нравится, ты и меняй».



    Как стало


    Задача Prettier – заставить разработчика не думать о форматировании: в нем минимум настроек. Это и подкупило Павла, когда Артур кинул в чат команды ссылку на проект:

    — поставил сам Prettier,
    — поставил пре-коммит хук (подробнее),
    — оставил в чате пару комментариев, что код-стайл там свой (например, логические операции в if’ах ставятся в конце строки, а не в начале).

    Всё, так команда перешла на Prettier. Вот примеры промышленного кода до применения Prettier и после. Он чуть переформатирован, чтобы показать возможности этого инструмента.

    До:

      public listenDndForFocusEvents(channel: string): Observable<boolean> {
        return this.drag
          .pipe(
            filter(
              event => event.channel === channel
            ), 
            filter(
              event => event instanceof DndDragStartEvent || event instanceof DndDragEndEvent
            ), 
            map(
              event => event instanceof DndDragStartEvent
            )
          )
      }
    

    После:

      public listenDndForFocusEvents(channel: string): Observable<boolean> {
        return this.drag.pipe(
          filter(event => event.channel === channel),
          filter(event => event instanceof DndDragStartEvent || event instanceof DndDragEndEvent),
          map(event => event instanceof DndDragStartEvent),
        );
      }
    

    Даже если бы это было написано в одну строку, оно всё равно бы переформатировалось так, как надо:

      public listenDndForFocusEvents(channel: string): Observable<boolean> {
        return this.drag.pipe(filter(event => event.channel === channel), filter(event => event instanceof DndDragStartEvent || event instanceof DndDragEndEvent), map(event => event instanceof DndDragStartEvent),);
      }
    

    Ещё кусочек кода, уже без точек с запятой. До:

    const lessonCount$ = this.studentLessonsCounterService
          .getCounter().pipe(map(featureInfo => featureInfo.lessonCount))
    const isItTimeForNotification$ = lessonCount$.pipe(map(lessonCount => lessonCount % REAL_TALK_NOTIFICATION_LESSON_INTERVAL === 0))
    

    После:

    const lessonCount$ = this.studentLessonsCounterService
          .getCounter()
          .pipe(map(featureInfo => featureInfo.lessonCount));
    const isItTimeForNotification$ = lessonCount$.pipe(
          map(lessonCount => lessonCount % REAL_TALK_NOTIFICATION_LESSON_INTERVAL === 0),
        );
    


    Теперь код-ревью проходит быстрее, Борис не тратит кучу рабочего времени на форматирование уже написанного кода, Павел продолжает писать так, как и писал (но теперь с ним никто не ругается), а Артур наконец-то доволен, заходя в репозиторий и улыбаясь от красоты кода. Используя простой инструмент с февраля 2019-го, ребята сэкономили кучу времени, перестав спорить из-за форматирования. А затем убедили остальные команды сделать так же.
    Skyeng
    Крупнейшая онлайн-школа Европы. Удаленная работа

    Комментарии 32

      0
      Форматирование листингов поехало
        +2
        спасибо, поправил
        0

        А можно эту же статью на английском? Я бы своим показал.

          +1
          Александр, привет! Да, закидываем контент-отделу в задачки, как будет готово — опубликуем и дадим ссылку.

          И спасибо за идею: как будете в районе Таганки в Москве, пишите, угостим чем-нибудь)
            0
            Привет, перевели специально для вас и ваших, буквально habr.com/en/company/skyeng/blog/488168 )
              +1

              Спасибо. Зашарил.

            +2
            Прочитал статью, сначала подумал о том, что надо б пойти возмутиться о том, что предлагается грести весь код под одну гребенку; но уже через десяток минут подумал, что плюсов от этого практически всегда будет больше, чем минусов.

            Ну и пошел включил prettier у себя в репах, до кучи. Это занимает сильно меньше 20 минут, если линтер уже есть.
              +4
              Всегда говорю — плевать как форматируется код, главное чтобы он форматировался автоматически.
                +3

                и одинаково

                0
                я такой же подход использую для php в связке codesniffer + pre commit hook.
                  +1
                  Почему pre-commit?
                  Имхо, гораздо удобнее post-save.
                  Например для VSCode есть плагин такой.
                    +1
                    Меня, например, крайне раздражает, когда код у меня переформатируется в что-то еще прямо перед глазами. Build / pre-commit — самый нейтральный вариант, когда изменения обязательно применятся, но не обязательно сию секунду.
                      +1
                      Pre-commit работает у всей команды, а хуки у каждого индивидуально
                      +2
                      Работали с Prettier в паре проектов, первое время восхищались, потом начало раздражать, что при изменении пары символов в коммите часто меняется по 10 строк и пулл-реквесты становятся нечитаемыми. Кроме того, часто бывает более выразительно написать в одном месте, скажем, разреженно, а в другом — компактно. В итоге в последних проектах от Prettier отказались. А вообще, странно видеть обзорную статью про проект двухлетней (или больше) давности, раньше на хабре такое минусовали.
                        0
                        Кроме того, часто бывает более выразительно написать в одном месте, скажем, разреженно, а в другом — компактно.

                        // prettier-ignore

                        А вот если вам постоянно нужно это куда-то писать — это повод задуматься о причинах. Исключения, подтверждающие правила, хороши только тогда, когда это и вправду исключения, а не когда под их соусом пытаются пропихнуть своё особое мнение.
                          0
                          Цель Prettier сделать код красивым и выразительным. Это не что-то критичное для проекта и, в добавок, довольно субъективное. Конструкции вроде prettier-ignore создают шум в коде и рассеивают внимание разработчика, что противоречит изначальной цели сделать код выразительным. Я иногда работаю с коллегами из Индии и других южных стран, со всеми вытекающими, так там использование Prettier может быть оправдано, так как люди косячат на пустом месте каждый день и снизить деструктив от них хотя бы на уровне синтаксических соглашений будет полезно. Но когда вы работаете в опытной команде людей, которые не косячат — Prettier играет роль того самого джуна, который услышал о лучшей практике и пытается пихать её везде, уместно и не уместно, создавая больше шума, чем пользы.
                          Мой тезис в том, что если в 2020-м человек о таких инструментах никогда не слышал — вероятно он и есть джун, и использование Prettier для него будет в пользу. Но я против практики навязывания этого инструмента как обязательного или как бесспорно хорошей практики.
                            +1
                            Цель Prettier сделать код красивым и выразительным.

                            Это не цель. «О вкусах не спорят». Цель prettier — привести весь код к констистентному виду. Можете это прямо с их сайта прочитать. Именно поэтому у prettier минимум опций, и они не собираются это менять.

                            Этого же можно добиваться и линтером, но линтеры наоборот тяготеют к полному конфигурированию каждого чиха, из-за чего с ними во-первых долго возиться, а во-вторых — применив парочку более сомнительных оформительских правил, можно вызвать бурные протесты внутри команды. В prettier экзотического форматирования нет вовсе, а по основным случаям можно не соглашаться, но это явно не случаи разряда «вижу такое форматирование — и аж аппетит портится, а рука сама выводит заявление по собственному».

                            Но когда вы работаете в опытной команде людей, которые не косячат — Prettier играет роль того самого джуна, который услышал о лучшей практике и пытается пихать её везде, уместно и не уместно, создавая больше шума, чем пользы.

                            Когда я работаю в команде опытных людей — я всё так же предпочитаю не раздражаться, увидев в коде «if() {» вместо «if () {». Вы можете сказать «ну так не раздражайся, и пиши у себя как надо, а читай чужой код с ледяным спокойствием», но в итоге это выливается в бесполезный шум в коммитах (если я буду править чужой if, я машинально добавлю пробел, а мой коллега, пробел не любящий — будет править, и уберет пробел), и служит вялотлеющим источником конфликтов.
                              +1
                              Согласен, что вкусы у всех разные, это не суть. Больше претензий вызывает когда строка кода становится из длинной короткой или из короткой длинной. Prettier старается её разбить или наоборот сжать, хотя иногда разница пара символов. Подобные инициативы автоматики портят читаемость пулл-реквестов, когда человек поменял число 25 на 115, а ощущение будто пол класса переписал. Кроме того часто бывает такое, когда Prettier как раз портит консистентность, в примерах вроде такого:
                              const { short, line } = this.props;
                              const {
                                longLongLineElement1,
                                longLongLineElement2
                              } = this.state;
                              

                              когда одни инструкции получаются развернутыми, а другие сжатыми, хотя они могут быть очень близки и структурно, и идейно, и даже по длине (например, 79 и 81 при максимуме стрики 80). Да, в 95% случаев Prettier срабатывает корректно, и только в 5% промахивается. Но когда работаешь в команде, где в 99% люди пишут верно — от Prettier с его промахами больше вреда.
                                0

                                Да, хороший пример, я кратко обмолвился ниже о "невнятности форматирования", это как раз то, что я имел ввиду. Более специализированный ESLint справляется с этим намного более предсказуемо

                          0
                          при изменении пары символов в коммите часто меняется по 10 строк и пулл-реквесты становятся нечитаемыми

                          А почему такое происходит? Вы используете prettier вместе с lint-staged, только для изменённых файлов? В такой ситуации можно однократно прогнать весь код через форматтер, закоммитить, и тогда все будущие коммиты не будут содержать лишних изменений

                            0

                            Prettier переносит строки в зависимости от длинны. Иногда увеличение\уменьние длины строки на пару символов попадает на брейкпоинт и происходит переформатирование.

                              0

                              А какие тут могут быть варианты? Если строка получается слишком длинная, руками разработчик бы сделал то же самое

                          0
                          del
                            0

                            Работаю front-end разработчиком, всегда код форматирую в последнюю очередь с помощью настроенной gulp сборки и потом отправляю на коммит и никаких проблем.
                            prettier реально напрягает в работе в реальном времени

                              0

                              Использую Prettier почти три года и могу сказать, что у него есть свои плюсы и минусы, как и у любого другого инструмента.


                              Порой Prettier совсем невнятно форматирует код или форматирование может зависеть от любопытных хаков. Например год назад я предложил вот такой https://github.com/prettier/prettier/issues/1974#issuecomment-385277713, правда теперь он уже не нужен, так как по итогам обсуждения обновили правила форматирования и теперь оно такое, какое я и хотел, по-дефолту))). Не всегда изменения, сделанные им, удобно просматривать в diff-ах.


                              Но в то же время, даже если не использовать Prettier как основной форматтер, он довольно удобен для форматирования вспомогательных файлов. Markdown, JSON, Yaml, вот это вот все. Я на проектах использую его вместе с ESLint и вполне доволен. Кстати ESLint еще может определять и частично исправлять всякие дурацкие ошибки, поэтому в плане чистоты кода пользы от него намного больше. Prettier предназначен исключительно для форматирования кода, и хотя его и пытаются иногда притянуть на проект в качестве линтера, это неправильно. Но вместе они позволяют на ревью обсуждать действительно важные вещи: что и как этот код делает, а не всякую фигню, вроде отсутствия/наличия пробелов в каких-то конструкциях.


                              Так что Prettier вполне успешно можно использовать в качестве форматтера поверх линтеров, а также для файлов, для которых линтер не особо нужен, а вот форматирование было бы выдерживать неплохо. При этом на проекте будет стайлгайд "prettier-like". Если Prettier чем-то не понравился (например ньюансами как в https://habr.com/ru/company/skyeng/blog/484992/#comment_21186642), помимо него есть еще куча "zero config" решений: xo, Lynt например. Можно тот же ESLint использовать и для форматирования с каким-нибудь популярным готовым набором правил, от airbnb например. Какой бы инструмент не был бы выбран, на хуки гита его можно повесить с одинаковым успехом и он так же перед коммитом поправит форматирование кода. Стайлгайд только будет немного отличаться. Но что останется неизменным — так это то, что стиль кода внутри проекта будет одинаковым. Чего собственно и добивались, и это экономит силы и нервы людей на проекте. Удачного code review!

                                +1
                                Не в первый раз слышу о проблемах с форматированием кода при code review

                                Мы сделали так. В CI на гитабе подключили в том числе прогон линтером. Тесты просто не проходят если есть какое-то форматирование отличное от того что задано линтером.

                                Ну и понятно — пока тест красный — код можно даже и не смотреть. Разве что написать реквестеру — что тесты не прошли.
                                  +1
                                  Делали также, в репу закоммитили конфиги линтеров для js & python и включили в CI это как первый шаг.
                                  Локально разработчики могут что-угодно настраивать, но если линтер ругнется, то будет красный билд и никто даже смотреть PR не будет.
                                  Поначалу может немного раздражать, но ребята быстро адаптировались и в итоге всем даже нравится, что больше не нужно обсуждать форматирование.
                                    0
                                    И не надо каждому настраивать разные pre-commit хуки и прочее. Все делается централизованно админом.
                                    Так что prettier пока не особо и нужен. Каждый форматит код руками как хочет — но в рамках общих правил. Если что — правила всегда можно скорректировать.
                                  0
                                  Проверил сейчас автоформат Prettier'а в Atom: не всегда работает гладко, приходится проверять, что он там наформатировал. Например, сложные миксины LESS превращает в дикую кашу, а закрывающие скобки иногда переносит в конец закомментированных строк.
                                  По итогу — годится причесывать небольшие файлы, которые можно сразу отсмотреть, или куски кода (хотя «формат выделенного» игнорирует отступы вокруг), а вот проходиться им по большому проекту я бы стал с крайней осторожностью. Ну и Format on save точно не стоит использовать.
                                    0

                                    Вместо кода типа:


                                      public listenDndForFocusEvents(channel: string): Observable<boolean> {
                                        return this.drag.pipe(
                                          filter(event => event.channel === channel),
                                          filter(event => event instanceof DndDragStartEvent || event instanceof DndDragEndEvent),
                                          map(event => event instanceof DndDragStartEvent),
                                        );
                                      }

                                    Я всегда наблюдал что-то вроде:


                                      public listenDndForFocusEvents(
                                         channel: string
                                      ): Observable<
                                       boolean
                                       > {
                                        return this.drag.pipe(
                                          filter(event => event.channel === channel),
                                          filter(
                                            event => 
                                               event instanceof DndDragStartEvent || 
                                               event instanceof DndDragEndEvent
                                          ),
                                          map(event => event instanceof DndDragStartEvent),
                                        );
                                      }

                                    Чего стоят if-ы вроде:


                                    if(
                                      exp1 &&
                                      exp 
                                    ) {
                                    // ....
                                    }

                                    В чем проблема использовать более вменяемый(конфигурируемый) корректировщик формата кода, я не знаю.

                                      +1

                                      Так в Prettier тоже есть опция printWidth, можно там выставить значение побольше, чем дефолтные 80

                                        0
                                        Более того, там можно выставить значение даже чуть побольше, чем согласованная ширина (выставленная у разработчиков в IDE), чтоб prettier не резал бы маниакально превышение на символ.
                                        Хотя в доках prettier пишут, что эту ширину стоит выставлять поменьше, я пришел к строго обратному выводу: стоит выставлять побольше.

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое