Pull to refresh

Checkstyle и Java. Поможет ли автоматическая инспекция кодa?

Reading time 10 min
Views 11K


В последнее время на Хабре заметно участились статьи об автоматизации и оптимизации код-ревью, инспекциях кода и других способах поддержания кода крупных проектов в достойном виде (например, статья 1, статья 2, статья 3).

Думаю, постоянные читатели уже встречали предыдущие посты (первый, второй), где мои коллеги рассказывали об использовании Java-библиотеки Checkstyle и делились нашим совместным опытом по ее расширению.

Последние труды нашей команды по написанию дополнительных чеков (проверок) для Checkstyle вошли в новую сборку нашего дополнения для Eclipse Checkstyle-плагина Eclipse-cs (sevntu-checkstyle 1.5.3, 4.09.2012).

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

Разработка требует от программиста много сил и внимания. Время не стоит на месте: пожелания заказчика меняются, проекты «толстеют», а программный код благополучно забывается разработчиком и со временем устаревает. Приходится общими усилиями структурировать код и стараться всегда держать его в достойном состоянии. Для этого используются все возможные средства: код-ревью, рефакторинг, измерение «характеристик» кода в попугаях метриками, и т. д. Подобные проверки и реорганизации кода отнимают у разработчиков большое количество времени и нервных клеток. С другой стороны, есть и положительный момент: наличие регулярных код-ревью в компании разработчиков приводит к появлению и закреплению общих правил по написанию «хорошего» и «правильного» кода. Большая часть этих правил потом становятся стандартами, общепринятыми в компании и часто первое, чему учат новых сотрудников на предприятии – корпоративным правилам и стандартам написания кода.

На тему написания «хорошего» и «безопасного» кода написано множество книг, например, «Effective Java» и «Java Puzzlers» Джошуа Блоха. Эти книги содержат многолетний накопленный опыт «правильного» программирования и большинство советов из этих книг достойны того, чтобы также стать корпоративными стандартами.

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

Несоблюдение стандартов и «опыта предков» может обойтись разными последствиями. Если не соблюдаются правила, относящиеся к стилю кода, код просто становится тяжелее для понимания. Это исправимо, но в большинстве случаев у разработчика просто не возникает желания вернуться к давно написанному коду и отрефакторить его в соответствии со всеми стандартами. Срабатывают древние принципы программирования вроде «А зачем, если и так все прекрасно работает» и «Кому не нравится, тот исправляет» =)Оставшиеся в коде ошибки порой трудно обнаружить, особенно в крупных проектах. Здесь на помощь разработчику и должна прийти библиотека Checkstyle, которая указывает на ошибки и несоответствие заранее настроенным стандартам программирования, отображая не только место, но и причину возникновения проблемы в каждом проблемном участке кода.

В основном, Checkstyle используется для того, чтобы помочь разработчику соблюдать стиль кода и выявлять в нем потенциально опасные ситуации. Checkstyle также может быть использован для того, чтобы, в мягкой (или не очень мягкой) форме заставить подсказать Java-разработчикy как писать код, соответствующий определенным стандартам и правилам. К «стандартам и правилам» может относиться практически все: от способа расстановки скобок и отступов до потенциально опасных ситуаций. Причем набор проверяемых Checkstyle–ом «проблемных» участков и ситуаций соответствует давно сформированным и общепризнанным правилам «хорошего тона» в Java — программировании.

Обнаружив в коде ошибки/стилевые проблемы, Checkstyle может повести себя по-разному, в зависимости от настроек. При «мягких» настройках библиотека только предупредит о присутствующих ошибках, а при самых строгих будет фейлить сборку до тех пор, пока все предупреждения Checkstyle в проекте не будут исправлены.

Checkstyle поставляется с парочкой уже отлаженных конфигураций, одна из которых настроена в максимальном соответствии с Sun Code Conventions. Это делает удобным использование Checkstyle «из коробки». Однако каждому, кто всерьез и надолго собирается применять Checkstyle, я бы посоветовал создать собственную конфигурацию и, включая-настраивая чеки один за другим, постепенно прийти к подходящим настройкам. Это поможет познакомиться поближе с возможностями библиотеки и быть уверенным в том, что каждая включенная проверка действительно вам полезна. К тому же, если вы начинающий разработчик на Java, настройка Checkstyle с нуля вполне может стать для вас хорошим опытом изучением «хорошего тона» Java – программирования.

Также существует возможность добавлять в Checkstyle собственные чеки для специфических нужд. Наша команда уже давно занимается написанием чеков и их добавлением Checkstyle, причем многие из готовых чеков мы постоянно используем в разработке. Пример: в нашей компании плохим тоном считается создание объекта java.lang.NullpointerException. Для того, чтобы быть уверенным в том, что инстанцирования NPE нет ни в одном проекте, мы добавили в набор проверок Eclipse Checkstyle плагина собственный чек, запрещающий создавать объекты некоторого заданного класса (ForbidInstantiationCheck) и сконфигурировали его таким образом, чтобы чек «ругался» только на инстанцирование NPE.

В написании собственных чеков и улучшении Checkstyle весьма помогает то, что код движка Checkstyle и самих чеков написан качественно и понятно (ни один чек не добавляется в библиотеку официально до тех пор, пока не пройдет без ошибок проверку самим же Checkstyle с весьма «фашистскими» настройками).Существует простая возможность расширять возможности Checkstyle, добавляя в него собственные чеки, написанные на Java или модифицируя уже существующие. Написать собственный чек и добавить его в Checkstyle несложно. Для этого нужно написать код нового чека, руководствуясь инструкциями на сайте проекта Checkstyle и нашей wiki и добавить его в последнюю сборку Checkstyle с помощью нашего же дополнения. Результатом будет пересобранный jar-архив Checkstyle, который можно скормить Checkstyle–плагину для IDE, Maven Checkstyle плагину, и т.д. Среди существующих чеков несложно найти хороший пример для написания своего, поэтому написать чек для Checkstyle сможет даже начинающий программист без большого опыта работы с Java.

Как и у любого программного продукта, у Checkstyle есть свои плюсы и минусы. Плюсы просты: удобство и гибкость в настройке, поддержка большого количества различных правил (чеков), возможность написания собственных чеков и легкость их встраивания в библиотеку.

Самым главным минусом библиотеки я считаю то, что все проверки Checkstyle не могут выходить за пределы одного Java – файла. Также в ходе проверок Checkstyle опускает все комментарии, но это, на мой взгляд, не смертельно.

Проверка Java-кода всегда выполняется Checktyle-ом по следующей схеме:
  1. На основе LL-грамматик используемым в Checkstyle движком ANTLR генерируется парсер Java-кода, соответствующий спецификации используемой версии Java.
  2. С помощью полученного парсера Java-код представляется в виде абстрактых деревьев разбора (Abstract Synthax Tree или AST) – по одному дереву на один Java — файл. Каждое полученное синтаксическое дерево имеет примерно такую структуру:
  3. Каждая вершина каждого дерева обрабатывается всеми включенными чеками, в коде которых в качестве входных данных указан соответствующий тип вершины. Порядок обработки вершин синтакического дерева соответствует реальному порядку их следования при чтении кода: слева направо и сверху вниз.
  4. Для всех найденных ошибок генерируются соответствующие предупреждения.

Немного о нашем дополнении


За двухлетнюю практику наша команда, состоящая из нескольких человек, хорошо познакомилась с возможностями Checkstyle и расширила количество существующих в Checkstyle проверок. Большинство из них мы вынесли в отдельный проект под названием «SevNTU-Checkstyle». (для справки: СевНТУ — Севастопольский Национальный Технический Университет, большая часть нашей команды – бывшие или настоящие студенты этого университета, курируемые Java-разработчиками Севастопольского офиса компании “Reveredata LLC”.Суть нашего проекта заключается в том, что он позволяет без особых сложностей добавлять в набор проверок Checkstyle новые чеки (и тестировать их работу в реальном времени на реальных проектах) без необходимости отправлять каждый чек хозяевам-держателям проекта Checkstyle, ожидая, что его примут в официальный набор проверок. Как мы со временем убедились, основатели проекта Checkstyle — весьма занятые люди, а тому же — не разделяющие с нами некоторых идей качественного кода (как говорят, у каждого монастыря — свой устав). Поэтому в большинстве случаев не отвечают на новые предложения и фиксы в собственном патч-трекере, а также не добавляют иногда даже качественно оттестированные чеки в Checkstyle. В общем, теперь для желающих расширить возможности Checkstyle, или изменить его проверки под свои нужды это больше не является помехой.

Порядка года назад (09.11.11) наш проект стал частью Eclipse Checkstyle плагина, став «расширением» для Eclipse-cs. Установить его, как и любые другие дополнения для Eclipse, легко можно через update-site. Процесс установки состоит из двух простых пунктов:

  1. Открываем Eclipse.
  2. Вбиваем в Help --> “Install New Software” URL нашего апдейт-сайта: eclipse-cs.sourceforge.net/update/ и ставим обе галки, т.е. – выбираем на установку собственно сам Checkstyle-plugin и наше дополнение к нему:

К тому же, установить наше дополнение (да и сам Eclipse-cs) можно через Eclipse Marketplace, без ручного вбивания ссылок.

После перезагрузки Eclipse и создания новой Checkstyle – конфигурации (Window --> Preferences --> Checkstyle --> New...) в настройках Checkstyle появится группа с 25 нашими дополнительными проверками:


Большинство из 25 наших чеков, включенных в дополнение, занимаются не проверками «стиля кода», а поиском в нем потенциальных опасностей (Хотя, конечно, это справедливо только для тех случаев, когда проблему можно однозначно выявить с помощью Checkstyle: без анализа байт-кода и проверки одновременно нескольких связанных Java — файлов).

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

ChildBlockLengthCheck – чек, проверяющий процентное соотношение размеров вложенных операторов (сравнивает количество строк, занимаемых телом операторов). Данный чек выводит предупреждение в том в случае, если размер тела вложенного оператора составляет более чем N% от размера оператора, лежащего уровнем выше. Чек выводит предупреждения только при условии, что вложенный оператор занимает более чем m строк. Это удобно, когда нужно найти блоки, которые, не помещаются полностью на экране и занимают слишком большое количество строк блока-родителя.

AvoidModifiersForTypesCheck – позволяет запретить использование одного или нескольких из 4 типов ключевых слов: static, final, transient, volatile для объектов заданных типов. В качестве параметров данный чек для каждого из 4 ключевых слов позволяет задать 4 отдельных регулярных выражения, задающих формат названия искомого типа.

Пример использования AvoidModifiersForTypesCheck: объекты графической библиотеки ULC (ULCComponents) не должны быть статическими: ссылка. Поэтому весьма разумно с помощью Checkstyle запретить присвоение ключевого слова «static» всем объектам, имя класса которых начинается, например, на “ULC”.

CauseParameterInExceptionCheck – позволяет заставить разработчика указывать Exception или Throwable в качестве параметра хотя бы одного конструктора любого класса, представляющего собой собственное исключение (Cause needed as one the parameter in any custom Exception constructor).

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

Также в чеке присутствует возможность игнорирования некоторых классов по их имени — опять же с использованием регулярного выражения.

ForbidsCertainImportsCheck – чек, запрещающий импортирование некоторых пакетов/типов по регулярному выражению (данный чек аналогичен соответствующей проверке Sonar).

ForbidInstantiationCheck – чек, запрещающий инстанцирование объектов заданного типа. Пример использования: допустим, нам необходимо на уровне целого проекта запретить создание объекта java.lang.NullPointerException (в нашей компании создание объекта NPE считается плохим тоном). В качестве параметров чек принимает список разделенных запятой qualified-путей к классам, запрещенным к инстанцированию:

«java.lang.NullPointerException, your.custom.Class, etc.»

ReturnCountExtendedCheck – чек, проверяющий в методах и конструкторах количество ключевых слов «return». Чек имеет гибкие настройки игнорирования, с его помощью можно не просто запретить написание методов с более чем n операторов «return». Данный чек может «посчитать» только те из операторов «return», которые:
  • не лежат в пределах первых m строк метода/конструктора (такие операторы «return» часто служат для «быстрого» выхода из конструкции при проверках параметров метода на null, либо ожидаемое значение выхода, либо...
  • лежат на уровне вложенности операторов большем k (такие «return» – ы могут служить для выхода из сложных конструкций и не подлежат подсчету)
  • не находятся в методах небольших размеров. Если метод помещается на экране, то часто для него вполне допустимо использовать большое количество операторов «return» без потери читабельности кода.
Также данный чек позволяет проигнорировать некоторые методы по их именам с помощью регулярного выражения.

AbbreviationAsWordInNameCheck – чек, проверяющий имена классов на количество подряд идущих заглавных букв. Т.е. – данный чек призван запретить именование Java-классов вроде «MDCContextListener», предлагая изменить имя на «MdcContextListener». Нужна подобная проверка не только для того, чтобы стандартизировать имена классов. Полезно то, что с помощью входных параметров можно задать чеку имена тех аббревиатур которые будет исключениями.

OverridableMethodInCtorCheck — чек, запрещающий вызов переопределяемых методов из конструкторов (проверка, сходная с существующей в NetBeans). Как известно, в Java вызов переопределяемого метода из конструктора может стать причиной неочевидного поведения программы (результат метода при вызове такого конструктора может отличаться от ожидаемого (в качестве примера можно взглянуть на это обсуждение проблемы на StackOverflow). Также проблемой может стать вызов непереопределяемого метода, который вызывает еще один, на этот раз уже переопределяемый метод — в данном случае тоже может начаться «странное» поведение программы. Поэтому «хорошим тоном» в Java-коде считается запрет переопределения методов, вызываемых из конструкторов (а также из методов clone() и readObject(), которые работают, как конструкторы). Для этого нестатическим методам, вызываемым из конструкторов(а также из clone() и readObject()), присваивают ключевое слово final, либо модификатор области видимости private.

Как мы убедились в процессе написания чеков, есть немало ситуаций в Java-коде, которые сложно или даже невозможно проверить без прямого анализа байт-кода с одновременным открытием в памяти нескольких файлов. Checkstyle такого делать не умеет, поэтому могу сказать, что наш OverridableMethodInCtorCheck достаточно далек от совершенства (в основном, по причине именно ограничений Checkstyle). В общем, качественную проверку на вызов переопределяемых методов из конструктора я бы советовал поискать не в Checkstyle, а в PMD или FindBugs, умеющих работать со скомпилированным байт-кодом.

P.S. Надеюсь, вам окажется полезным Eclipse Checkstyle плагин и наше к нему дополнение. Набор написанных нами проверок хорошо покрыт тестами и оттестирован нами на собственных проектах. Однако, если вы найдете баг или у вас появятся идеи для новых чеков – смело создавайте issue в нашем github – репозитории, мы постараемся рассмотреть их максимально оперативно.

На этом все, спасибо за внимание )

Tags:
Hubs:
+25
Comments 18
Comments Comments 18

Articles