Рубрики
Без рубрики

Рекомендации по проверке кода Java

Мы составили список рекомендаций, которые помогут вам эффективно просматривать Java-код. Читайте дальше, чтобы узнать больше!. Помечено как java, обзор кода, программирование.

Наличие другой пары глаз, сканирующих ваш код, всегда полезно и помогает вам выявлять ошибки до того, как вы остановите производство. Вам не нужно быть экспертом, чтобы просмотреть чей-то код. Некоторый опыт работы с языком программирования и контрольный список обзора должны помочь вам начать работу. Мы составили список вещей, которые вы должны иметь в виду при просмотре кода Java. Читайте дальше!

1. Следуйте соглашениям о коде Java

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

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

2. Замените императивный код лямбдами и потоками

Если вы используете Java 8+, замена циклов и чрезвычайно подробных методов потоками и лямбдами делает код более чистым. Лямбды и потоки позволяют писать функциональный код на Java. Следующий фрагмент фильтрует нечетные числа традиционным императивным способом:

List oddNumbers = new ArrayList<>();
for (Integer number : Arrays.asList(1, 2, 3, 4, 5, 6)) {
    if (number % 2 != 0) {
      oddNumbers.add(number);
  }
}

Это функциональный способ фильтрации нечетных чисел:

List oddNumbers = Stream.of(1, 2, 3, 4, 5, 6)
  .filter(number -> number % 2 != 0)
  .collect(Collectors.toList());

3. Остерегайтесь исключения NullPointerException

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

class Items {
    private final List items;
    public Items(List items) {
            this.items = items;
    }
    public Integer highest() {
      if (items.isEmpty()) return null;
      Integer highest = null;
      for (Integer item : items) {
          if (items.indexOf(item) == 0) highest = item;
          else highest = highest > item ? highest : item;
      }
      return highest;
    }
}

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

Items items = new Items(Collections.emptyList());
Integer item = items.highest();
boolean isEven = item % 2 == 0; // throws NullPointerException ❌
boolean isEven = item != null && item % 2 == 0 // ✅

Однако наличие нулевых проверок повсюду в вашем коде может быть довольно громоздким. Если вы используете Java 8+, рассмотрите возможность использования Необязательного класса для представления значений, которые могут не иметь допустимых состояний. Это позволяет легко определять альтернативное поведение и полезно для методов цепочки.

В приведенном ниже фрагменте мы используем Java Stream API для поиска наибольшего числа с помощью метода, который возвращает Необязательно . Обратите внимание, что мы используем Stream.reduce , который возвращает Необязательное значение.

public Optional highest() {
    return items
            .stream()
            .reduce((integer, integer2) -> 
                            integer > integer2 ? integer : integer2);
}
Items items = new Items(Collections.emptyList());
items.highest().ifPresent(integer -> {             // ✅
    boolean isEven = integer % 2 == 0;
});

В качестве альтернативы вы также можете использовать аннотации, такие как @Nullable или @NonNull , что приведет к появлению предупреждений, если при создании кода возникнет конфликт с нулем. Например, передача аргумента @Nullable методу, который принимает параметры @NonNull .

4. Прямое назначение ссылок из клиентского кода полю

Ссылками, отображаемыми в клиентском коде, можно манипулировать, даже если поле является окончательным. Давайте лучше поймем это на примере.

private final List items;
public Items(List items) {
        this.items = items;
}

В приведенном выше фрагменте мы напрямую назначаем ссылку из клиентского кода полю. Клиент может легко изменять содержимое списка и манипулировать нашим кодом, как показано ниже.

List numbers = new ArrayList<>();
Items items = new Items(numbers);
numbers.add(1); // This will change how items behaves as well

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

private final List items;
public Items(List items) {
    this.items = new ArrayList<>(items);
}
}

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

5. Обращайтесь с исключениями осторожно

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

try {
    stack.pop();
} catch (Exception exception) {
    // handle exception
} catch (StackEmptyException exception) {
    // handle exception
}

Если ситуация исправима и может быть обработана клиентом (потребителем вашей библиотеки или кода), то полезно использовать проверенные исключения. например. Исключение IOException – это проверенное исключение, которое заставляет клиента обрабатывать сценарий, и в случае, если клиент решит повторно создать исключение, это должен быть сознательный призыв игнорировать исключение.

6. Поразмышляйте над выбором структур данных

Коллекции Java предоставляют Список массивов , Список ссылок , Вектор , Стек , Набор хэшей , Хэш-карта , Хэш-таблица . Важно понимать плюсы и минусы каждого из них, чтобы использовать их в правильном контексте. Несколько советов, которые помогут вам сделать правильный выбор:

  • Карта : Полезно, если у вас есть неупорядоченные элементы в виде пар ключ-значение и требуются эффективные операции поиска, вставки и удаления. Хэш-карта , Хэш-таблица , LinkedHashMap – все это реализации интерфейса Карта .

  • Список : Очень часто используется для создания упорядоченного списка элементов. Этот список может содержать дубликаты. ArrayList является реализацией интерфейса Список . Список можно сделать потокобезопасным с помощью Коллекции.synchronizedList таким образом устраняется необходимость использования Вектора . Здесь еще немного информации о том, почему Вектор по сути устарел.

  • Набор : Аналогично списку, но не допускает дубликатов. HashSet реализует интерфейс Set .

7. Подумайте дважды, прежде чем разоблачать

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

Например, у вас есть класс Библиотека у этого есть следующий способ проверить книгу по названию:

public checkout(String bookName) {
    Book book = searchByTitle(availableBooks, bookName);
  availableBooks.remove(book);
  checkedOutBooks.add(book);
}

private searchByTitle(List availableBooks, String bookName) {
...
}

Если вы не сохраняете метод searchByTitle закрытым по умолчанию, и он становится открытым, другие классы могут начать использовать его и создавать логику поверх него, которую вы, возможно, хотели быть частью класса Library . Это может нарушить инкапсуляцию класса Библиотеки или может оказаться невозможным отменить/изменить позже, не нарушая чужой код. Разоблачайте сознательно!

8. Код для интерфейсов

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

public Bill(Printer printer) {
    this.printer = printer;
}

new Bill(new ConsolePrinter());
new Bill(new HTMLPrinter());

В приведенном выше фрагменте кода использование интерфейса Принтер позволяет разработчику перейти к другому конкретному классу HTML Принтер .

9. Не заставляйте подгонять интерфейсы

Взгляните на следующий интерфейс:

interface BookService {
        List fetchBooks();
    void saveBooks(List books);
    void order(OrderDetails orderDetails) throws BookNotFoundException, BookUnavailableException;   
}

class BookServiceImpl implements BookService {
...

Есть ли польза от создания такого интерфейса? Есть ли область применения для этого интерфейса, реализуемого другим классом? Является ли этот интерфейс достаточно универсальным, чтобы быть реализованным другим классом? Если ответ на все эти вопросы отрицательный, то я бы определенно рекомендовал избегать этого ненужного интерфейса, который вам придется поддерживать в будущем. Мартин Фаулер очень хорошо объясняет это в своем блоге .

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

interface Shape {
        Double perimeter();
}

class Rectangle implements Shape {
//data members and constructors
    @Override
    public Double perimeter() {
        return 2 * (this.length + this.breadth);
    }
}

class Circle implements Shape {
//data members and constructors
    @Override
    public Double perimeter() {
        return 2 * Math.PI * (this.radius);
    }
}

public double totalPerimeter(List shapes) {
    return shapes.stream()
               .map(Shape::perimeter)
               .reduce((a, b) -> Double.sum(a, b))
               .orElseGet(() -> (double) 0);
} 

10. Переопределение хэш-кода когда переопределение равно

Объекты , которые равны по своим значениям , называются объектами значений . например, деньги, время. Такие классы должны переопределять метод equals , чтобы возвращать значение true, если значения одинаковы. Метод equals обычно используется другими библиотеками для сравнения и проверки равенства; следовательно, необходимо переопределить equals . Каждый объект Java также имеет значение хэш-кода, которое отличает его от другого объекта.

class Coin {
    private final int value;

    Coin(int value) {
        this.value = value;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Coin coin = (Coin) o;
        return value == coin.value;
    }
}

В приведенном выше примере мы переопределили только метод равно объекта .

HashMap coinCount = new HashMap() {{
  put(new Coin(1), 5);
  put(new Coin(5), 2);
}};

//update count for 1 rupee coin
coinCount.put(new Coin(1), 7);

coinCount.size(); // 3 🤯 why? 

Мы ожидаем, что coin Count обновит количество монет в 1 рупию до 7, так как мы переопределяем equals. Но HashMap внутренне проверяет, равен ли хэш-код для 2 объектов, и только затем переходит к проверке равенства с помощью метода equals . Два разных объекта могут иметь или не иметь один и тот же хэш-код, но два одинаковых объекта всегда должны иметь один и тот же хэш-код, как определено контрактом метода Хэш-код . Таким образом, проверка хэш-кода в первую очередь является условием раннего выхода. Это означает, что оба метода равно и Хэш-код должны быть переопределены для выражения равенства.

Если вы пишете или просматриваете код Java, Deep Source может помочь вам автоматизировать обзоры кода и сэкономить массу времени. Просто добавьте файл .deep source.toml в корень репозитория, и Deep Source сразу же подберет его для сканирования. Сканирование позволит найти возможности для улучшений в вашем коде и поможет вам исправить их с помощью полезных описаний.

Зарегистрируйтесь и убедитесь сами!

Оригинал: “https://dev.to/deepsource/guidelines-for-java-code-reviews-3096”