Pull to refresh

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

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

Однако, в отличие от работоспособности кода, которую можно оценить с помощью выполнения проверочных тестов, качество кода не является простой оценкой 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, а также какую-нибудь внутреннюю статистику производительности. Результаты можно сохранять в базе данных, и через некоторое время накопить хорошие показатели того, как вы улучшаете, ускоряете или замедляете код в процессе разработки.


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

Ссылки


Tags:
Hubs:
+35
Comments 8
Comments Comments 8

Articles