Статьи

Как устранить ошибки благодаря высокой сплоченности

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

01
02
03
04
05
06
07
08
09
10
CompilationTask getTask(
    Writer out,
    JavaFileManager fileManager,
    DiagnosticListener<? super JavaFileObject>
        diagnosticListener,
    Iterable<String> options,
    Iterable<String> classes,
    Iterable<? extends JavaFileObject>
        compilationUnits
);

Почему это так? Давайте углубимся в эту интуицию. Вот пример из JavaCompiler Javadoc :

1
2
3
4
5
6
7
Iterable<? extends JavaFileObject> compilationUnits1 =
    fileManager.getJavaFileObjectsFromFiles(
        Arrays.asList(files1));
 
compiler.getTask(null, fileManager, null,
                 null, null, compilationUnits1)
        .call();

Так что здесь не так? У нас есть множество очень несвязанных параметров, которые, скорее всего, будут установлены в null . Это уменьшает возможность многократного использования вышеописанного метода, или, с точки зрения ребят из JArchitect , мы, вероятно, находимся в «Зоне боли», поскольку у нас низкий уровень стабильности в сочетании с низким уровнем абстрактности .

  • Низкая стабильность: очень вероятно, что нам понадобится еще один очень специфический аргумент в будущей версии JavaCompiler , например, еще один Iterable чего-либо. Это приведет к несовместимому улучшению API
  • Низкая абстрактность: даже если вышеприведенный метод является интерфейсным, вероятность того, что этот метод будет реализован более одного раза, очень мала, поскольку довольно сложно выполнить вышеуказанный контракт полезным способом.

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

Высокая сплоченность вместо «Зоны боли»

Возможно, для этого API компилятора это не так важно, как вы думаете. Но самое большое значение «высокой сплоченности», то есть идеального баланса стабильности / абстрактности, заключается в том, что у вас есть код многократного использования. Это не просто хорошо, потому что ваши разработчики тратят меньше времени на реализацию конкретной задачи, это также означает, что ваш код чрезвычайно устойчив к ошибкам. Например, проверьте логику преобразования типов данных из внутренних частей jOOQ :

иерархия преобразования типов данных в jOOQ

иерархия преобразования типов данных jOOQ

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

  • Чрезвычайно локально для одного метода / одного листа представленного выше дерева
  • Чрезвычайно глобально для всего дерева

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

Как достичь высокой сплоченности

Все просто: путем беспощадного рефакторинга . Вы никогда не должны вводить новую функцию только локально. Например, давайте рассмотрим это исправление здесь [# 3023] DefaultRecordMapper не отображает вложенные UDT на вложенные POJO . Поэтому мы хотим, чтобы функция jOOQ RecordMapperProvider применялась к вложенным записям. Почему? Представьте, что у нас есть таблица PERSON с типами Oracle OBJECT для свойств ADDRESS и STREET. Да, вы также можете просто нормализовать эти данные, но представьте, что мы используем UDT:

01
02
03
04
05
06
07
08
09
10
CREATE TYPE street_type AS OBJECT (
  street VARCHAR2(100),
  no VARCHAR2(30)
);
 
CREATE TYPE address_type AS OBJECT (
  street street_type,
  zip VARCHAR2(50),
  city VARCHAR2(50)
);

И теперь мы хотели бы рекурсивно отобразить эти данные на пользовательские вложенные POJO:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class Street {
    public String street;
    public String number;
}
 
public class Address {
    public Street street;
    public String city;
    public String country;
}
 
public class Person {
    public String firstName;
    public String lastName;
    public Address address;
}

И отображение должно быть доступно через:

1
2
3
4
5
6
7
8
9
// The configuration object contains the
// Mapping algorithm implementation
Person person = DSL.using(configuration)
                   .selectFrom(PERSON)
                   .where(PERSON.ID.eq(1))
 
// We want to make the mapping algorithm recursive
// to automatically map Address and Street as well
                   .fetchOneInto(Person.class);

Сопоставление записи с POJO уже реализовано, а рекурсия – нет. И когда мы реализуем рекурсию, мы хотим уважать существующий, вышеупомянутый настраиваемый SPI сопоставления, который был представлен в jOOQ 3.1 . Это очень просто, у нас только одна точка реализации вверху в типе ConvertAll .

Реализация этого в очень связной кодовой базе означает, что:

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

Вы беспощадно рефакторинг?

Идеальный дизайн не может быть предвиден. Растет медленно. Сегодня мы знаем так много о Java и коллекциях, что новому Streams API потребовалось время, чтобы всплыть. Никто бы не реализовал такой великолепный новый API в JDK 1.2 с нуля, хотя с этой точки зрения он уже был довольно хорош в то время.

В основном это означает две вещи для вас:

  • Для вашего основного основного кода важно привести его в состояние, в котором вы достигнете высокой сплоченности. Если вы являетесь поставщиком E-Banking, ваша логика платежей и брокерских операций должна быть такой же, как указано выше, со сбалансированным соотношением стабильности / абстрактности
  • Что касается вашего несущественного кода (например, UI / DB-access), вы должны полагаться на стороннее программное обеспечение, потому что кто-то потратит гораздо больше времени на получение своего кода с высоким уровнем качества (UI: например, Vaadin , ZK или доступ к БД: такие как Hibernate , jOOQ , Spring Data и другие)

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