Автоматический контроль качества Java-кода

    Код, который пишет программист, должен быть рабочим – самое первое правило успешной работы, с которым согласится и сам программист, и все его начальники. Но, кроме того, что код должен просто работать, часто к нему предъявляются повышенные требования – наличие комментариев (внутренней документации), читаемость, быстроту внесения изменений, совместимость с явными и неявными стандартами. Всё то, что можно назвать качеством кода.

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

    Какой может быть хотя бы начальный список пунктов для оценки?
    • Компилирование всего кода
    • Работоспособность всего кода
    • Соответствие стилю написания кода, используемого в проекте
    • Наличие документации для каждого участка кода
    • Удобочитаемость кода на уровне отдельных строк, функций, файлов
    • Возможность быстрого внесения изменений в код, затрагивая минимальный существующий функционал


    Компиляция


    К сожалению, ещё встречаются в нашей жизни ситуации, когда код на проекте не компилируется целиком. В дереве кода встречаются участки, написанные несколько лет назад, которые никто не трогал, никто не менял, и они сами собой как-то перестали не только работать, но и даже компилироваться. При этом эти участки кода не используются в 99%, 99,9% или даже в 99,98% случаев, но когда настанет час X (а по закону Мэрфи он обязательно настанет именно в production-системе), заказчик будет очень огорчён.

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

    Отдельной строкой стоит упомянуть компиляцию не-java файлов. Почти для любых скриптов, и для JSP-страниц, существует способ проверить файл на наличие ошибок компиляции. Например, в WebLogic вы можете указать параметр precompile для вашего веб-приложения. Если какая-нибудь JSP будет содержать ошибки, прекомпиляция прервётся и выведет ошибку в лог. Также существует утилита jspc, входящая в состав WebLogic, выполняющая ту же функцию для отдельных jsp.

    Работоспособность



    Ваш код работает? А откуда вы знаете? Можете ли вы сказать, весь ли код работает прямо сейчас, или же какая-то часть точно работала недели две назад, а сейчас может уже и не работает? Но ведь вы сами знаете, что практически любое изменение может «сломать» что-то не там, где надо, и даже там, где не надо.

    Или же ваша уверенность основывается на том, что отдел QA (quality assurance) поставил «passed» на все ваши bugs/issues/patches? Так это ещё ничего не значит! Во-первых, в некоторых компаниях QA тестирует только изменившиеся части системы. Во-вторых, QA бывает ленивым и непрофессиональным, и, как показывает мой недавний опыт в некоторой неназываемой компании, может просто поставить passed на тест, который просто лень было проходить. В-третьих, скорее всего в QA тоже работают люди, и они тоже могут допускать ошибки.

    Поэтому для оценки работоспособности уже везде используются автоматические тесты. Именно они позволяют оценить, работоспособен или нет ваш код на все 100%. Правда, для этого нужно чтобы:
    • автоматические тесты были
    • эти автоматические тесты должны покрывать 100% функционала


    Если первый пункт является организационной задачей, то второй — технической. И под «100%» нужно понимать не наличие теста на каждое требование в неких requirements'ах, а то, что каждая строчка кода будет исполнена в результате исполнения хотя бы одного теста. Данная характеристика называется «code coverage» и буквально означает степень покрытия кода тестами. Различаются следующие показатели:

    • Function Coverage — подсчёт по вызовам методов
    • Decision Coverage – подсчёт по возможным направлениям исполнения кода (then-else или case-case-default в управляющих структурах). Учитывает единственное ветвление в каждом конкретном случае.
    • Statement Coverage – подсчёт по конкретным строчкам кода
    • Path Coverage – подсчёт по возможным путям исполнения кода. Более широкое понятие, чем decision coverage, так как учитывает результат всех ветвлений.
    • Conditional Coverage – подсчёт по возможным результатам вычисления значениям булевских выражений и подвыражений в коде.


    Некоторые показатели являются чисто теоретическими и для практических приложений обычно не используются, например «Path Coverage». Так как достижение данного показателя до 100% для какого-нибудь цикла с переменным числом итераций будет означать необходимость проверки для всех возможных числа итераций… а если это зависит от пользователя? Да практически невозможно проверить. Поэтому обычно используется либо «functional coverage», либо «statement coverage» (из последнего легко получить decision coverage). Из open source проектов можно отметить:


    EclEmma — Java Code Coverage for Eclipse
    EclEmma — Java Code Coverage for Eclipse

    Стиль


    Код должен удобно читаться, а не удобно писаться
    Стив МакКоннелл (Steve McConnell), SD West '04
    (автор статьи не совсем согласен с данным высказыванием,
    но приводит его как авторитетное мнение авторитета)


    Хорошо, предположим ваш код работает. Ещё от него хочется, чтобы он был понятным. Для этого используется много техник и правил, и самое первое — соответствие оформление кода некоторому стилю. Например, у нас в компании используется следующий стиль: «Sun Java Conventions + {} on new line». Хочется выразить благодарность авторам столь понятной концепции. Потому что в моём IDE мне достаточно взять правила оформления «Java Conventions», скопировать в новые, и поменять всего одну (ну ладно, пять если честно), настройку.

    Почему сразу об IDE? Да потому, что стиль кода должен поддерживаться не программистом, а его IDE, а программист лишь должен следить, чтобы программа не зарывалась. Но если программа ошиблась, а программист не проследил? Тогда на помощью приходят опять же автоматические средства проверки:

    Но стиль не кончается оформлением и количеством пробелов в отступе. Следующей ступенью является использование общепринятых либо корпоративных стандартов в коде. Например, принято, что любой Serializable класс должен иметь serialVersionUID, что любой Exception должен быть immutable, а значение exception'а в catch-блоке не меняется. И хотя нарушение данных соглашений может и не создавать ошибок в настоящий момент, они делают код хуже понимаемым тем человеком, которых ожидает следованию стандартным правилам. Среди программ, которые проверяют код на наличие таких несоответствий хочется отметить всё тот же checkstyle, а также программу FindBugs:

    Данная область проверок кода называется статическим анализом. Сюда относятся как простые проверки, например, наличие serialVersionUID в Serializable-классе, так и более сложные, вроде незакрытых input/output потоков ввода-вывода. Повторюсь, данные утилиты могут найти ошибки в коде, который вроде бы проходит 100% тестов и имеет 100% Coverage. Просто тогда эти утилиты найдут потенциальные ошибки и несоответствия программы стандартному стилю программирования.
    Использование FindBugs в Eclipse
    Использование FindBugs в Eclipse

    Частично статистический анализ может выполнить и IDE (Eclipse — при компиляции, IDEA — «Code Inspections»). Утилиты разделяют на две группы — на те, которые работают с исходным кодом (большинство), и на те, которые работают уже со скомпилированными файлами. Стоит отметить, что FindBugs работает с компилированным кодом, то есть проверку может провести и ваш начальник, и ваш заказчик… думаю, лучше позаботится, чтобы там был хороший результат (смайлик).

    Документация


    Уж сколько слов сказано о необходимости документации к коду… Остановлюсь лишь на том, что современные IDE позволяют автоматически подчёркивать те места кода, где отсутствует javadoc-комментарий, проверять правильность его оформления, наличие всех необходимых тегов (@since, @param, @author, etc). Ищите в настройках и обязательно включите это хотя бы для public-методов и классов (а лучше и для protected).

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

    Удобочитаемость кода


    Разумеется, пока что даже Microsoft Word (OpenOffice Writer) не может отличить хороший текст сочинения от плохого. Максимум — исправить грамматические и стилистические ошибки. Но всё-таки хоть что-то.

    Поэтому и для кода сложно формализовать понятие «удобочитаемости», но есть некоторые характеристики, которые отмечают удобочитаемый код от неудобочитаемого:
    • размер отдельной строки не более 120 символов, чтобы помещалась на экране
    • размер метода не более одного экрана, чтобы читатель мог охватить весь метод взором и понять, что он делает
    • размер класса не более 1000 строк — отсутствие BLOB-антипаттерна
    • отсутствие «магических» констант (например, 5, 7, 10… читатель должен легко понять, что это за цифры и откуда они берутся)

    Большинство подобных проверок относятся к стилю кода, и проверяются уже называвшимися утилитами.

    Подводя «итоги»



    Что в итоге нужно сделать, чтобы утилиты автоматически помогали следить за качеством кода?
    • Если у вас нет системы контроля версий, то стоит её завести. Хотя, надеюсь, она у вас всё-таки есть, иначе к чему вся эта статья?
    • Если в компании существуют определённые правила (шаблоны) написания кода, ограничения, то их стоит формализовать, и, по возможности, описать на языке, понятным одной из автоматических утилит.
    • Настроить выделенный сервер (интеграционный), который каждую ночь будет брать из текущего HEAD/Stream весь код, компилировать его и «выкладывать» на сервер. Сервер, желательно, должен быть запущен в режиме -ea (enable assertions), хотя бы на тот код, который вы писали.
    • В процессе компиляции код надо проверить утилитами, работающими с исходным кодом (например, Checkstyle), а после — утилитами, работающими с байт-кодом (например, FindBugs)
    • Результат компиляции, особенно если есть ошибки, послать на почту
    • После перезагрузки сервера — провести на нём серию автоматических тестов.
    • При исполнении тестов было бы неплохо подключить утилиты для оценки coverage, а также какую-нибудь внутреннюю статистику производительности. Результаты можно сохранять в базе данных, и через некоторое время накопить хорошие показатели того, как вы улучшаете, ускоряете или замедляете код в процессе разработки.


    Придя с утра, программист должен получить от сервера задач — список задач на день, а от интеграционного — текущее положение дел с кодом.

    Ссылки


    • +35
    • 20k
    • 8
    Поделиться публикацией

    Похожие публикации

    Комментарии 8
      +1
      Хорошая статья, только по-моему, вы напрасно (случайно или сознательно) смешиваете в одну кучу юнит-тестирование, функциональное, интеграционное и UI; при этого слово «автотесты» звучит совершенно загадочно.
        +3
        Смешиваю сознательно.

        С точки зрения спецификаций, документации, QA и пр. эти тесты различны, выполняют различные функции и пр.

        Но с моей точки зрения все эти тесты (кроме UI) выполняют одну и ту же задачу — проверяют работоспособность системы. Unit-тесты — конкретного кусочка, функциональные — для выполнения отдельных задач, интеграционные — системы целиком. И все они (в 80% случаев) могут быть автоматизированы одним и тем же способом, опять же, кроме UI. Различается лишь время выполнения тестов.

        Но пока время выполнения всех этих тестов меньше 8 часов, их все можно (и нужно) автоматически запускать по ночам.

        Ручной запуск, разумеется, это не отменяет. А вот когда его делать — это как раз уже различается для юнит-тестов, функциональных и интеграционных.
        +1
        sonar.codehaus.org/
        На мой взгляд тоже весьма интересный комплект информации выдает.
          0
          Собственно он и базируется на перечисленных автором тулзах.
          0
          Забыт PMD, который может заменить CS и FB
            0
            Не хочется начинать holywar :)

            На странице PMD, кстати, есть целый список похожих проектов
            pmd.sourceforge.net/similar-projects.html
            Какие-то из них уже не развиваются, какие-то развиваются довольно активно.

            Кстати, PMD работает с исходным кодом, а FindBugs — со скомпилированным. Иногда второе удобнее. Плюс у FindBugs есть GUI, в отличии от PMD, хотя в случае с maven/ant-интеграцией это не принципиально.
              0
              Плагины для 2х основных IDE 100% есть, так что отсутствие GUI не должно сильно смущать.

              Лично у меня осталось впечатление что PMD умеет проверять больше случаев чем FindBugs.

              0
              Не заменить а дополнить. Эти три утилиты выполняют хоть и похожие, но всё же различные функции. Как раз недавно статью читал по этому поводу: sonar.codehaus.org/what-makes-checkstyle-pmd-findbugs-and-macker-complementary/

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

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