Введение в правила качества кода с помощью FindBugs и PMD

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 при инициализации переменных или возврате ценности Использование

@NotNull а также @Nullable аннотации Используйте java.util.Optional

Образец объекта ** ]

6. Заключение

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

Вы можете просмотреть полный набор правил для каждого из них, перейдя по следующим ссылкам: