1. Обзор
В этой статье мы расскажем о некоторых важных правилах, используемых в инструментах анализа кода, таких как FindBugs, PMD и CheckStyle.
2. Цикломатическая сложность
2.1. Что такое цикломатическая сложность?
Сложность кода важна, но трудно измерить метрику. PMD предлагает солидный набор правил в разделе Code Size правил , эти правила предназначены для выявления нарушений, касающихся размера методов и сложности структуры. ,
CheckStyle известен своей способностью анализировать код на соответствие стандартам кодирования и правилам форматирования. Однако он также может обнаруживать проблемы в разработке классов/методов, вычисляя некоторую сложность metrics .
Одним из наиболее важных измерений сложности, представленных в обоих инструментах, является CC (Cyclomatic Complexity).
Значение CC может быть рассчитано путем измерения количества независимых путей выполнения программы.
Например, следующий метод даст цикломатическую сложность 3:
public void callInsurance(Vehicle vehicle) {
if (vehicle.isValid()) {
if (vehicle instanceof Car) {
callCarInsurance();
} else {
delegateInsurance();
}
}
}
CC учитывает вложенность условных операторов и логических выражений, состоящих из нескольких частей.
Вообще говоря, код со значением выше 11 в терминах CC считается очень сложным и трудным для тестирования и сопровождения.
Некоторые общие значения, используемые инструментами статического анализа, показаны ниже:
-
1-4: низкая сложность - легко проверить
-
5-7: умеренная сложность - терпимо
-
8-10: высокая сложность - рефакторинг следует считать легким
тестирование ** 11 + очень высокая сложность - очень трудно проверить
Уровень сложности также влияет на тестируемость кода, чем выше CC, тем выше сложность для реализации соответствующих тестов .
Фактически, значение цикломатической сложности показывает точное количество тестовых случаев, необходимых для достижения 100% оценки охвата ветвей
Граф потока, связанный с методом callInsurance () :
ссылка:/uploads/flowgraph__CC-1.png%20324w[]
Возможные пути выполнения:
-
0 ⇒ 3
-
0 ⇒ 1 ⇒ 3
-
0 ⇒ 2 ⇒ 3
Математически говоря, CC можно рассчитать по следующей простой формуле:
CC = E - N + 2P
-
E: Общее количество ребер
-
N: Общее количество узлов
-
P: Общее количество точек выхода
2.2. Как уменьшить цикломатическую сложность?
Чтобы написать существенно менее сложный код, разработчики могут использовать разные подходы, в зависимости от ситуации:
-
Избегайте написания длинных инструкций switch с использованием шаблонов проектирования,
например шаблоны построения и стратегии могут быть хорошими кандидатами для решения с размером кода и сложностью ** Напишите повторно используемые и расширяемые методы, модулируя код
структура и реализация https://en.wikipedia.org/wiki/Single responsibility principle После других PMD
Правила code size могут иметь прямое влияние на CC ** , например правило избыточной длины метода, слишком много полей в одном классе, список избыточных параметров в одном методе … и т. д.
Вы также можете рассмотреть следующие принципы и шаблоны, касающиеся размера и сложности кода, например, принцип https://en.wikipedia.org/wiki/KISS principle[ KISS (оставайся простым и глупым) ]и https://en.wikipedia.org/wiki/D’t repeat__yourself[DRY (не ' Повторите себя)].
3. Правила обработки исключений
Дефекты, связанные с исключениями, могут быть обычными, но некоторые из них крайне недооценены и должны быть исправлены, чтобы избежать критических сбоев в рабочем коде.
PMD и FindBugs предлагают набор правил, касающихся исключений
Вот наш выбор того, что может считаться критичным в программе Java при обработке исключений.
3.1. Не бросайте исключение в конечном итоге
Как вы, возможно, уже знаете, блок finally \ {} в Java обычно используется для закрытия файлов и освобождения ресурсов, использование его для других целей может рассматриваться как Кодовый запах .
Типичная подверженная ошибкам подпрограмма вызывает исключение внутри блока finally \ {} :
String content = null;
try {
String lowerCaseString = content.toLowerCase();
} finally {
throw new IOException();
}
Предполагается, что этот метод генерирует NullPointerException , но неожиданно он генерирует IOException , что может ввести вызывающий метод в заблуждение для обработки неправильного исключения.
3.2. Возвращаясь в finally Block
Использование оператора return внутри блока finally \ {} может быть только запутанным. Причина, по которой это правило так важно, заключается в том, что всякий раз, когда код генерирует исключение, он отбрасывается оператором return .
Например, следующий код выполняется без ошибок:
String content = null;
try {
String lowerCaseString = content.toLowerCase();
} finally {
return;
}
NullPointerException не было перехвачено, но все еще отбрасывается оператором return в блоке finally .
3.3. Не удалось закрыть поток в исключении
Закрытие потоков является одной из основных причин, по которым мы используем блок finally , но это не тривиальная задача, как кажется.
Следующий код пытается закрыть два потока в блоке finally :
OutputStream outStream = null;
OutputStream outStream2 = null;
try {
outStream = new FileOutputStream("test1.txt");
outStream2 = new FileOutputStream("test2.txt");
outStream.write(bytes);
outStream2.write(bytes);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
outStream.close();
outStream2.close();
} catch (IOException e) {
//Handling IOException
}
}
Если инструкция outStream.close () выдает IOException , outStream2.close () будет пропущено.
Быстрое решение проблемы - использовать отдельный блок try/catch для закрытия второго потока:
finally {
try {
outStream.close();
} catch (IOException e) {
//Handling IOException
}
try {
outStream2.close();
} catch (IOException e) {
//Handling IOException
}
}
Если вы хотите хороший способ избежать последовательных try/catch блоков, проверьте IOUtils.closeQuiety из общего достояния Apache, он упрощает обработку закрытия потоков без выброса IOException .
5. Плохие практики
5.1. Класс определяет compareTo () и использует Object.equals ()
Всякий раз, когда вы реализуете метод compareTo () , не забывайте делать то же самое с методом equals () , в противном случае результаты, возвращаемые этим кодом, могут сбивать с толку:
Car car = new Car();
Car car2 = new Car();
if(car.equals(car2)) {
logger.info("They're equal");
} else {
logger.info("They're not equal");
}
if(car.compareTo(car2) == 0) {
logger.info("They're equal");
} else {
logger.info("They're not equal");
}
Результат:
They're not equal
They're equal
Чтобы избежать путаницы, рекомендуется убедиться, что Object.equals () никогда не вызывается при реализации __Comparable, вместо этого вы должны попытаться переопределить его следующим образом
boolean equals(Object o) {
return compareTo(o) == 0;
}
5.2. Возможная разыменование нулевого указателя
NullPointerException (NPE) считается наиболее встречающимся Exception в Java-программировании, и FindBugs жалуется на разыменование Null PointeD, чтобы избежать его выброса.
Вот самый простой пример броска NPE:
Car car = null;
car.doSomething();
Самый простой способ избежать NPE - выполнить нулевую проверку:
Car car = null;
if (car != null) {
car.doSomething();
}
Нулевые проверки могут избежать NPE, но при широком использовании они, безусловно, влияют на читаемость кода
Итак, вот некоторые приемы, которые используются, чтобы избежать NPE без нулевых проверок:
-
Избегайте ключевого слова null при кодировании _: _ Это простое правило,
избегайте использования ключевого слова null при инициализации переменных или возврате ценности Использование
-
https://en.wikipedia.org/wiki/Null Object pattern[** Implement the Null
Образец объекта ** ]
6. Заключение
В этой статье мы рассмотрели некоторые критические дефекты, обнаруженные инструментами статического анализа, и предоставили основные рекомендации по надлежащему решению обнаруженных проблем.
Вы можете просмотреть полный набор правил для каждого из них, перейдя по следующим ссылкам: