Перевод статьи Seven Deadly Sins of a Software Project автора Yegor Bugayenko.

Сопровождаемость — это наиболее ценное достоинство современной разработки программного обеспечения. Сопровождаемость может измеряться, в основном, рабочим временем, которое требуется новому разработчику, чтобы вникнуть в проект, до того, как он начнет вносить значимые изменения. Чем больше времени это занимает, тем меньше уровень сопровождаемости. В некоторых проектах это время близко к бесконечности, что означает, эти проекты практически не сопровождаемы. Я хочу рассказать вам о семи смертных грехах, которые делают программный продукт несопровождаемым.


Анти-паттерны



К сожалению, языки программирования, которые мы используем, слишком гибкие. Они слишком много позволяют и слишком мало запрещают. К примеру, Java никак не запретит вам разместить код всего приложения в одном классе с парой сотен методов. Технически, приложение скомпилируется и запустится. Но это — хорошо известный анти-паттерн Божественный объект.

Таким образом, анти-паттерн — это технически допустимый способ спроектировать приложение так, что бы оно было заведомо неправильным. В каждом языке программирования существует достаточно большое количество анти-паттернов. Их присутствие в вашем проекте схоже с опухолью в живом организме. Однажды она начала расти и её уже очень трудно остановить. В конце концов живой организм погибает. В конце концов ваш проект становиться несопровождаемым, и его необходимо переписать.

Однажды вы допустили парочку анти-паттернов, и их количество, как опухоль, будет только расти.

Это особенно актуально для объектно-ориентированных языков(Java, C++, Ruby, Python), в основном, потому что они много чего унаследовали от процедурных языков(C, Fortran, COBOL). Именно поэтому ООП-разработчики имеют тенденцию думать в процедурном и императивном стиле. К сожалению.

Кстати, в дополнение к существующему списку всем известных анти-паттернов, я хотел бы добавить эти несколько моментов от себя, которые я считаю плохими подходами к разработке.

Мой практический совет здесь — это читать и учиться. Возможно, эти книги помогут вам. Всегда относитесь скептически к качеству вашего кода и не расслабляйтесь, когда оно «просто работает». Как и в случае с раком, чем раньше его диагностируешь, тем выше шанс выжить.

Неотслеживаемые изменения



Когда я смотрю на историю коммитов, я должен иметь возможность рассказать о каждом изменении: что было изменено, кто внес эти изменения и почему эти изменения были внесены. Более того, время, которое требуется для того, что бы ответить на эти три вопроса, должно измеряться в секундах. В большинстве проектов этого нет. Вот несколько практических рекомендаций:

Всегда используйте тикеты. Не важно, на сколько проект или команда малы, даже если вы один, создавайте тикеты(GitHub Issues) для каждой проблемы, которую вам предстоит решить. Кратко опишите проблему в тикете. Используйте тикетную систему для промежуточных мыслей, что бы потом было понятно, к чему были «те несколько коммитов».

Связывайте тикеты и коммиты между собой. Само собой, каждый коммит должен сопровождаться сообщением. Коммиты без сообщений — это грязная практика, и я даже не буду обсуждать, почему. Но одно только сообщение — этого не достаточно. Каждое сообщение должно начинаться с номера тикета, с которым вы работаете. GitHub(А я уверен что вы используете его) автоматически свяжет тикеты и комиты, давая возможность лучше прослеживать изменения.

Ничего не удаляйте. Git позволяет нам делать «push --force», что перезаписывает целую ветку, которая до этого существовала на сервере. Это всего лишь один пример того, как вы можете уничтожить история разработки. Часто я видел, как люди удаляют свои комментарии на GitHub discussions, что бы их тикеты выглядели более «чистыми». Это просто-напросто неправильно. Никогда ничего не удаляйте; оставьте свою историю, не важно, на сколько она плохая(или некрасивая) на ваш взгляд.

Сложные релизы



Каждый кусок программного продукта должен быть упакован, прежде чем он будет доставлен конечному пользователю. Если это Java библиотека, она должна быть упакована в *.jar-файл и выпущена на каком-то репозитории; Если это — web-приложение, то оно должно быть развернуто на какой-то платформе, и т.д. Не имеет значения, на сколько проект большой или маленький, всегда должны быть стандартные процедуры, которые тестируют(test), упаковывают(package), и разворачивают(deploy).

Идеальным решением будет автоматизация этих процедур до такого уровня, что их можно будет запустить одно строчной командой:
./release.sh

или
mvn deploy


Большинство проектов далеки от этого. Их процесс релиза всегда включает в себя немного магии, где сотрудник, ответственный за это(так же известный, как DevOp), должен прокликать какие-то кнопки тут и тут, где-то авторизоваться, проверить какие-то метрики и т.д. Такой сложный процесс релиза все ещё является вполне типовым грехом всей индустрии разработки ПО.

Я могу дать лишь один практический совет: Автоматизируй это. Я использую rultor.com для этого, но вы можете использовать любые инструменты на свой вкус. Здесь важно то, что вся процедура полностью автоматизирована и может быть выполнена при помощи командной строки.

Добровольный статический анализ



Статический анализ — это то, благодаря чему наш код выглядит лучше и, следовательно, лучше работает. Но это случается только тогда, когда вся команда принудительно(!) следует правилам, диктуемым статическим анализатором. Я писал об этом в Strict Control of Java Code Quality. Я использую qulice.com для проектов на Java и rubocop для проектов на Ruby, но, помимо этого, существует множество вариантов для каждого языка.

Вы можете использовать любой анализатор, но сделайте это обязательным! Во многих проектах, где задействованы статические анализаторы, разработчики пишут красивые репорты и продолжают писать код как раньше. Такой «добровольный» подход не дает никакой пользы проекту. Более того, это создает иллюзию качества.

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

Неизвестное покрытие тестами



Проще говоря, покрытие тестами — это процент кода, который тестируется модульными или интеграционными тестами. Чем выше процент покрытия, тем больше кода прогоняется во время тестирования. Разумеется, чем больше процент покрытия, тем лучше.

Однако, многие разработчики, просто-напросто не знают процент покрытия тестами. Они не замеряют эту метрику. У них могут быть тесты, но никто не знает, на сколько глубоко они проникают в код и какая часть кода не покрыта тестами вообще. Эта ситуация гораздо хуже, чем низкий процент покрытия, который можно измерить.

Высокий процент покрытия — это не гарантия высокого качества. Это очевидно. Но неизвестный процент покрытия — это четкий признак проблем с сопровождаемостью. Когда новый разработчик присоединяется к проекту, он должен иметь возможность вносить некоторые изменения и видеть, как тесты реагируют на это. В идеале, процент покрытия тестами должен измеряться таким же образом, как и в случае со статическим анализом, сборка не должна проходить, если значение падает ниже какого-то заранее определенного порога(обычно около 80 процентов).

Бесконечная разработка



Под «бесконечной» я имею в виду разработку без этапов(майлстоунов) и релизов. Без разницы, какое ПО вы пишете, вы должны периодически присваивать версии и делать релизы. Проект без четкой истории релизов — это несопровождаемое месиво.

В большей степени, сопровождаемость — это когда я могу понять вас, прочитав ваш код.

Когда я смотрю исходный код и историю его комитов и релизов, я должен видеть, каковы были намеренья автора, что происходило с проектом год назад, куда он идет сейчас, какой его план развития и т.д. Вся эта информация должна быть в исходном коде и, важнее всего, в Git истории.

Git tags и GitHub release notes — это два мощных инструмента, которые предоставляют мне всю информацию. Используйте их по полной. Так же не забудьте, что каждая бинарная версия продукта должна быть доступна для немедленной загрузки. Я хочу иметь возможность скачать версию 0.1.3 и протестировать ее прямо сейчас, даже если продукт сейчас находится в версии 3.4.

Недокументированные интерфейсы



Каждый кусок программного продукта имеет свой интерфейс, через который его следует использовать. Если это Ruby gem, то должны быть классы и методы, которые я хочу использовать, как конечный пользователь. Если это web-приложение, то должны быть web-страницы, которые конечный пользователь будет видеть и использовать. Каждый программный продукт имеет интерфейсы, и они должны быть аккуратно документированы.

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

Я говорю здесь о документации для пользователей, а не для разработчиков. По идее, я против документации внутри ПО. В этом я полностью поддерживаю Agile Manifesto — работающий программный продукт гораздо важнее, чем подробное документирование. Но это не относится к «внешней» документации, которая предназначена для конечных пользователей, а не для разработчиков.

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

Если вашим продуктом является библиотека, тогда его конечный пользователь — это программист, который будет ее использовать — не развивать ее, а именно использовать, как «черный ящик».

Это критерии, которые используются для оценки проектов с открытым исходным кодом в нашем award competition.

Комментарии (18)


  1. lair
    14.06.2015 15:47
    +7

    Бесследные изменения

    Не «бесследные», а неотслеживаемые. След изменения — это сам код (диф в версионнике). А его отслеживание — это как раз тикеты и дальше вверх.


    1. nh3000 Автор
      15.06.2015 12:49

      Спасибо, исправил


  1. JSmitty
    14.06.2015 17:12
    +2

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


    1. lany
      14.06.2015 18:49
      +3

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

      // Расчёт ведётся по формуле 5.13.27 из техзадания

      Или

      // Corner case x == 0 is not handled as it's impossible in our case

      Или

      // Workaround of Stream.skip() problems in parallel streams, see http://stackoverflow.com/q/28521382/4856258 for details

      В большинстве случаев комментарии к реализации излишни. Хотите пояснить, что делает метод? Назовите его так, чтобы было понятно. Метод делает слишком много всего, в двух словах не расскажешь? Разбейте на более мелкие методы.

      А вот комментарии к интерфейсам жизненно необходимы. Они рассказывают о контракте и спецификации: не как код работает, а как код должен работать.


      1. aikixd
        15.06.2015 11:52
        -5

        Метод делает слишком много всего, в двух словах не расскажешь? Разбейте на более мелкие методы.


        Мне кажется это неправильной практикой. Если есть длинный метод и его можно разбить на еще несколько методов то класс уже будет несколько пухлым и сходу можно запнуться об эти методы когда будешь читать код класса. Я думаю лучше помечать кусочки метода комментариями. А если IDE позволяет то дать названия этим кусочкам и свернуть их.


    1. myrrec
      14.06.2015 20:12
      +2

      Быть против документирования кода — уже что-то с чем-то, имхо


      1. shadowjack
        14.06.2015 21:48
        +5

        Да нормально. Из практики:

        В одном чисто российском проекте (в котором никаких зарубежных разработчиков нет и никогда не было) руководитель запрещает использовать русский язык для написания документации, только английский. Само по себе это обстоятельство достаточно безвредное, вот только при найме на работу никто уровнем знания английского у программистов почему-то не интересуются, в результате вся документация, скопившаяся в проекте — это какие-то корявые, убогие предложения из 5 слов, из которых непонятно, что автор пытался сказать, и которые не то, что не несут никакой полезной нагрузки, а даже мешают нормально работать.

        В другом проекте видел, как было покрыто документацией в коде вообще всё — даже абсолютно самоочевидные вещи, типа атрибутов классов, из названия которых совершенно понятно, что они делают, или методов из одной строчки. Ну т.е. покрытие документацией ради 100%-ого покрытия документацией. А вот нормальной документации об общих принципах функционирования всей этой поделки не было ни строчки.

        TL;DR: в ИТ полно всяких наркоманов, документацию в коде нужно писать только по делу и далеко не все могут её нормально писать.


        1. myrrec
          14.06.2015 21:53

          Далеко не все. Другая крайность, которая тоже часто встречается — совсем не документированный код. Я подумал, что jsmitty как раз возражает против документирования в принципе


          1. xaizek
            14.06.2015 22:59

            А Вы точно внимально прочитали комментарий jsmiitty? Он как раз говорит, что документировать нужно, а в посте написана вот «такое»:

            По идее, я против документации внутри ПО.


            1. Sindicollo
              15.06.2015 11:25
              -3

              если код пишется одновременно с юнит тестами, то документация внутри кода не особо нужна, юнит тесты лучше помогают сделать код сопровождаемым, чем какой-нибудь doxygen.


              1. lair
                15.06.2015 12:54
                +1

                Агу. Простая задачка: OSS-проект, публичный интерфейс, метод без документации. Как найти, что он делает, не покидая гитхаба?


                1. Sindicollo
                  15.06.2015 13:34

                  Мы же об идеальном сферическом коде говорим, а не о конкретном проекте. Во первых нужно стремиться к тому, чтобы код был самодокументируемым, с понятными названиями функций. Во-вторых, если юнит-тесты писались одновременно с кодом, то они тоже будут на гитхабе. Если они не писались, тогда конечно нужна какая-то замена — например простыня комментариев перед каждой функцией.


                  1. lair
                    15.06.2015 13:44

                    Да не проблема: и названия функций понятные, и юнит-тесты на гитхабе есть (даже в том же репозитории). Вы же в названии контракт класса «а вот в таком случае возвращается такое, а в таком случае возвращается такое» и так далее не выразите? Так что возвращаемся к вопросу: как найти, что делает метод?

                    (заметим, придумать код идеальнее описанного сложно — если, конечно, не считать документацию частью идеала)


                    1. Sindicollo
                      15.06.2015 14:53

                      Если проект — это какая-то библиотека или SDK для внешнего использования — то конечно, документация должна быть, и желательно полная, это неотъемлемая часть продукта. Если же это вещь в себе, то полная документация всего внутреннего устройства, всех интерфейсов классов, и т.п. вещь совершенно не обязательная для того, чтобы его сопровождать. Если она есть — хорошо, но в реальных условиях она всегда запаздывает, ее мало кто из разработчиков обновляет.

                      Что делает метод в идеале должно быть понятно из его названия. Как его использовать и что он возвращает в разных ситуациях с разными параметрами — в случае идеально написанных и читаемых юнит-тестов можно понять из них.

                      Преимущество юнит-тестов над doxygen-style документацией — если их не обновлять — это сразу будет видно, тесты упадут, поэтому их вынуждены держать в актуальном состоянии. Но разумеется это все в сферическом случае.


      1. Throwable
        15.06.2015 11:38
        +2

        Обычно коментарий ставят, чтобы пояснить, что делает нижестоящий кусок кода. Однако, как советуют многие, коментарии в коде должны отвечать на вопрос ЗАЧЕМ, а не на вопрос КАК. Последнее должно всегда быть понятно из кода.
        Поэтому если кусок кода нетривиален, лучше его вынести в отдельную функцию и дать ей имя, поясняющее это действие. Таким образом в вышестоящей функции остаются только простые конструкции со смысловыми идентификаторами. Нужны детали — спускайтесь ниже.
        На Java я иногда вместо создания новой функции отделяю рутину областью видимости, а результат записываю в переменную с поясняющим именем:

        String myVeryImportantBusinessValue; {
            // код, вычисляющий myVeryImportantBusinessValue
        }
        


        P.S. Что бы ни говорил автор, в реальной разработке со сжатыми сроками и ограниченным бюджетом очень сложно быть «святым». Впрочем, как и в реальном мире :)


        1. lair
          15.06.2015 12:56

          Однако, как советуют многие, коментарии в коде должны отвечать на вопрос ЗАЧЕМ, а не на вопрос КАК. Последнее должно всегда быть понятно из кода.

          Я вот для себя делаю чуть более сложное разделение:
          • код отвечает на вопрос как
          • именование и комментарии отвечают на вопрос что
          • метаданные (комментарии к коммитам, тикеты, документация) отвечают на вопрос зачем


  1. lany
    15.06.2015 10:24
    +2

    Любопытно, что в моём проекте for-fun у меня только один грех из семи, а на работе практически всё имеется в той или иной степени. Думаю, если спросить программистов, которые пишут опен-сорс проект забесплатно, зачем они это делают, многие ответят, что хочется почувствовать вкус правильного и безгрешного программирования, а не как обычно.


    1. MrEsp
      15.06.2015 22:42
      +1

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