Улучшение модификаторов видимости Java с помощью ArchUnit

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!

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

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

Public vs Private

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

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

Если бы я этого не сделал, мои руки были бы связаны, так как кто-то мог бы использовать некоторые из моих внутренних классов напрямую.

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

Возможности package-private

Для скрытия внутренней структуры пакета можно использовать модификатор package-private и ограничить видимость классов, к которым не следует иметь доступ извне пакета.

Давайте посмотрим на типичную структуру пакета:

В этом варианте, к сожалению, нам нужно оставить все внутренние классы public, потому что видимость package-private работает в пределах одного пакета, а не всей иерархии.

Учитывая это, если мы хотим использовать package-private, нам нужно будет разместить их все в одном пакете:

К сожалению, такой подход плохо масштабируется с увеличением количества классов.

Представляем ArchUnit

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

Например, мы можем воссоздать функциональность иерархического модификатора package-private, ограничив доступ к классам в подпакетах com.pivovarit.movies классами, находящимися во всей иерархии пакетов:

public class ArchitectureTest {

    private static final JavaClasses classes = new ClassFileImporter()
      // ...
      .importPackages("com.pivovarit");

    @Test
    void com_pivovarit_movies_shouldNotExposeInternalClasses() {
        classes().that().resideInAPackage("com.pivovarit.movies.*")
          .should()
          .onlyBeAccessed().byClassesThat()
          .resideInAPackage("com.pivovarit.movies..")
          .check(classes);
    }
}

А теперь, если мы создадим класс вне пакета и будем использовать public API (Rentals), тесты будут зелеными:

package com.pivovarit;

import com.pivovarit.movies.Rentals;

public class Starter {
    public static void main(String[] args) {
        Rentals instance = Rentals.instance();

        boolean rent = instance.rent(42);
    }
}

Но если мы попытаемся получить доступ к MovieDetailsRepository напрямую, мы получим нарушение:

package com.pivovarit;

import com.pivovarit.movies.repository.MovieDetailsRepository;

public class Starter {
    public static void main(String[] args) {
        MovieDetailsRepository movieDetailsRepository
          = new MovieDetailsRepository();
    // java.lang.AssertionError: Architecture Violation
    }
}

Расширяя идею

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

@Test
void shouldHaveZeroDependencies() {
    classes().that().resideInAPackage("com.pivovarit.collectors")
      .should()
      .onlyDependOnClassesThat()
      .resideInAnyPackage("com.pivovarit.collectors", "java..")
      // ...
      .check(classes);
}

… Или обеспечить существование единственного public класса:

@Test
void shouldHaveSingleFacade() {
    classes().that().arePublic()
      .should().haveSimpleName("ParallelCollectors")
      .andShould().haveOnlyPrivateConstructors()
      .andShould().haveModifier(FINAL)
      // ...
      .check(classes);
}

Больше примеров использования ArchUnit можно найти на официальной странице.

Заключение

Отделение внутренних компонентов от public - один из лучших способов повысить удобство сопровождения вашего программного обеспечения. К сожалению, собственные инструменты Java ограничены, но ArchUnit может охватить те случаи, когда Java не может.

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

Тем не менее, ArchUnit может помочь вам улучшить ваш public API. 

Но ArchUnit не поможет, если вы продолжите добавлять все больше и больше исключений в свои правила.

Примеры из этой статьи можно найти на GitHub.

Примечание переводчика. Дополнительно о применении ArchUnit можно прочитать:

Модульное тестирование архитектуры Spring Boot проекта с помощью ArchUnit

Обнаружение и удаление кода без ссылок с помощью ArchUnit

Внедрение рекомендаций по структуре кода с использованием ArchUnit

Обеспечение границ компонентов чистой архитектуры с помощью Spring Boot и ArchUnit

Источник: https://habr.com/ru/post/545842/


Интересные статьи

Интересные статьи

В скором времени у нас стартует новый поток продвинутого курса «Machine Learning Pro + Deep Learning», а сегодня мы делимся постом, в котором рассказывается о подходах к реставрации с пом...
Задача интеграции сервисов и различных систем является чуть ли не одной из основных проблем современного IT. На сегодняшний день самым популярным архитектурным стилем для...
Доброго времени суток, друзья! Представляю Вашему вниманию перевод статьи Martin Heinz «Implementing 2D Physics in JavaScript». Давайте немного развлечемся, создавая двухмерные симуляции и ...
10 октября мы провели JavaScript Meetup SuperJob. Рассказываем, что обсуждали участники, делимся видео и презентациями.
Если вы, до того, как заинтересовались JavaScript, писали на традиционных языках с сильной типизацией, то вы, возможно, знакомы с концепцией void. Это — тип, использование которого сообщает прогр...