«1. Обзор

В этой статье мы расскажем о некоторых важных правилах, представленных в инструментах анализа кода, таких как FindBugs, PMD и CheckStyle.

2. Цикломатическая сложность

2.1. Что такое цикломатическая сложность?

Сложность кода — важная, но трудная для измерения метрика. PMD предлагает надежный набор правил в разделе «Правила размера кода». Эти правила предназначены для обнаружения нарушений, касающихся размера методов и сложности структуры.

CheckStyle известен своей способностью анализировать код на соответствие стандартам кодирования и правилам форматирования. Однако он также может обнаруживать проблемы в разработке классов/методов, вычисляя некоторые метрики сложности.

Одним из наиболее важных показателей сложности, представленных в обоих инструментах, является CC (Cyclomatic Complexity).

Значение CC можно рассчитать, измерив количество независимых путей выполнения программы.

Например, следующий метод даст цикломатическую сложность, равную 3:

public void callInsurance(Vehicle vehicle) {
    if (vehicle.isValid()) {
        if (vehicle instanceof Car) {
            callCarInsurance();
        } else {
            delegateInsurance();
        }
    }
}

CC учитывает вложенность условных операторов и логических выражений, состоящих из нескольких частей.

Вообще говоря, код со значением CC выше 11 считается очень сложным, его трудно тестировать и поддерживать.

Некоторые общие значения, используемые инструментами статического анализа, показаны ниже:

    1–4: низкая сложность — легко тестировать 5–7: умеренная сложность — приемлемо 8–10: высокая сложность — рефакторинг следует рассматривать для облегчения тестирования 11 + очень высокая сложность — очень сложно тестировать

Уровень сложности также влияет на тестируемость кода: чем выше CC, тем выше сложность реализации соответствующих тестов. На самом деле значение цикломатической сложности точно показывает количество тестовых случаев, необходимых для достижения 100%-го показателя покрытия ветвей.

Блок-схема, связанная с методом callInsurance():

flowgraph_cc-1

Возможные пути выполнения:

    0 =\u003e 3 0 =\u003e 1 =\u003e 3 0 =\u003e 2 =\u003e 3

Математически говоря , CC можно рассчитать по следующей простой формуле:

CC = E - N + 2P
    E: общее количество ребер N: общее количество узлов P: общее количество точек выхода

2.2. Как уменьшить цикломатическую сложность?

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

    Избегайте написания длинных операторов switch, используя шаблоны проектирования, например шаблоны построителя и стратегии могут быть хорошими кандидатами для решения проблем размера и сложности кода. Напишите повторно используемые и расширяемые методы путем модульной структуры кода и реализации принципа единой ответственности. Следование другим правилам размера кода PMD может иметь прямое влияние на CC, например. правило чрезмерной длины метода, слишком много полей в одном классе, список чрезмерных параметров в одном методе и т. д.

Вы также можете рассмотреть следующие принципы и шаблоны, касающиеся размера и сложности кода, например. принцип KISS (Keep It Simple and Stupid) и DRY (Don’t Repeat Yourself).

3. Правила обработки исключений

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

PMD и FindBugs предлагают несколько наборов правил, касающихся исключений. Вот наш выбор того, что может считаться критическим в программе Java при обработке исключений.

3.1. Не выбрасывать исключение в finally

Как вы, возможно, уже знаете, блок finally{} в Java обычно используется для закрытия файлов и освобождения ресурсов, его использование для других целей может рассматриваться как запах кода.

Типичная подверженная ошибкам подпрограмма генерирует исключение внутри блока finally{}:

String content = null;
try {
    String lowerCaseString = content.toLowerCase();
} finally {
    throw new IOException();
}

Этот метод должен генерировать исключение NullPointerException, но неожиданно он генерирует IOException, которое может ввести вызывающий метод в заблуждение для обработки неправильное исключение.

3.2. Возвращение в блок finally

«Использование оператора 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. commons, это упрощает обработку закрытия потоков без создания исключения 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) считается наиболее часто встречающимся исключением в программировании на Java, и FindBugs жалуется на разыменование нулевого указателя, чтобы избежать его создания.

Вот самый простой пример создания NPE:

Car car = null;
car.doSomething();

Самый простой способ избежать NPE — выполнить проверку на ноль:

Car car = null;
if (car != null) {
    car.doSomething();
}

Нулевые проверки могут избегать NPE, но при активном использовании они безусловно, влияют на читаемость кода.

Итак, вот некоторые приемы, используемые для избегания NPE без проверки null:

    Избегайте ключевого слова null при кодировании: Это простое правило, избегайте использования ключевого слова null при инициализации переменных или возврате значений Используйте аннотации @NotNull и @Nullable Используйте аннотации @NotNull и @Nullable java.util.Optional Реализация шаблона нулевого объекта

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

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

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