- PVSM.RU - https://www.pvsm.ru -
Привет! Представляю вашему вниманию перевод статьи "Programming by contract on the JVM [1]" автора Nicolas Fränkel.
На этой неделе я хотел бы заняться интересным подходом, который я редко видел, но он является очень полезным.
Дизайн по контракту, также известный как контрактное программирование, является подходом к разработке программного обеспечения. Он предписывает, чтобы разработчики программного обеспечения определяли формальные, точные и проверенные спецификации интерфейса для программных компонентов, которые расширяют обычное определение абстрактных типов данных с предусловиями, постусловиями и инвариантами. Эти спецификации называются «контрактами», в соответствии с концептуальной метафорой с условиями и обязательствами деловых контрактов.
Wikipedia [2]
По сути, условия быстро перестают работать. Нет смысла запускать код, если в конце, вычисление завершится неудачно из-за неправильного предположения.
Давайте рассмотрим пример операции передачи между двумя банковскими счетами. Вот некоторые условия:
Пред-условия:
Константы:
Пост-условия:
Легко реализовать пред- и пост-условия «вручную»:
public void transfer(Account source, Account target, BigDecimal amount) {
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgument("Amount transferred must be higher than zero (" + amount + ")";
}
if (source.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgument("Source account balance must be higher than zero (" + source.getBalance() + ")";
}
source.transfer(target, amount);
if (source.getBalance().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalState("Source account balance must be higher than zero (" + source.getBalance() + ")";
}
// Other post-conditions...
}
Такой код является громоздким и трудно читаемым.
Возможно, вы уже работали с пред- и пост-условиями с помощью ключевого слова assert:
public void transfer(Account source, Account target, BigDecimal amount) {
assert (amount.compareTo(BigDecimal.ZERO) <= 0);
assert (source.getBalance().compareTo(BigDecimal.ZERO) <= 0);
source.transfer(target, amount);
assert (source.getBalance().compareTo(BigDecimal.ZERO) <= 0);
// Other post-conditions...
}
Существует несколько проблем при использовании Java-подхода:
-eaДокументация Oracle [3] прямо указывает на это:
Хотя конструкция assert не является полноценной конструкцией по контракту, она может помочь поддерживать неформальный стиль программирования по контракту.
Начиная с Java 8, класс Objects предлагает три метода, которые накладывают ограничения на программирование по контракту:
public static <T> T requireNonNull(T obj)
public static <T> T requireNonNull(T obj, String message)
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier)
Аргумент
Supplierв последнем методе возвращает сообщение об ошибке
Все 3 метода бросают NullPointerException, если obj равно null.
Более интересно то, что они возвращают, если obj не равно null. Это приводит к следующему виду кода:
public void transfer(Account source, Account target, BigDecimal amount) {
if (requireNonNull(amount).compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgument("Amount transferred must be higher than zero (" + amount + ")";
}
if (requireNonNull(source).getBalance().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgument("Source account balance must be higher than zero (" + source.getBalance() + ")";
}
source.transfer(target, amount);
if (requireNonNull(source).getBalance().compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalState("Source account balance must be higher than zero (" + source.getBalance() + ")";
}
// Other post-conditions...
}
Мало того, что это накладывает ограничения, так и ухудшает читаемость кода, особенно если вы добавляете аргумент сообщения об ошибке.
Spring Framework [4] предоставляет класс Assert, который предлагает множество методов проверки состояния:

В соответствии с собственными реализациями, проверки пред-условия вызывают исключение IllegalArgumentException, если условие не выполняется, тогда как проверки после состояния бросают исключение IllegalStateException.
На странице Википедии выше также перечислены несколько фрэймворков, посвященных программированию по контракту:
Большинство из вышеперечисленных фрэймворков основаны на аннотациях.
Начнем с плюсов: аннотации делают условия очевидными.
С другой стороны, аннотации не лишены недостатков:
Программирование на Kotlin по контракту основано на простых вызовах метода, сгруппированных в файле Preconditions.kt:

require методы реализуют пред-условия, а если их нет, то будет брошено IllegalArgumentExceptioncheck методы реализуют пост-условия, а если их нет, то будет брошено IllegalStateExceptionПереписать вышестоящий фрагмент при помощи Kotlin довольно просто:
fun transfer(source: Account, target: Account, amount: BigDecimal) {
require(amount <= BigDecimal.ZERO)
require(source.getBalance() <= BigDecimal.ZERO)
source.transfer(target, amount);
check(source.getBalance() <= BigDecimal.ZERO)
// Other post-conditions...
}
Поскольку это частый случай, то чем проще, тем лучше. Просто завернув проверку и бросаемые исключения в метод, можно легко использовать программирование по концепциям контракта. Хотя таких оболочек нет в наличии на Java, valid4j и Kotlin предлагают их.
Спасибо за внимание, до новых встреч!
Автор: superman_cherry
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/276791
Ссылки в тексте:
[1] Programming by contract on the JVM: https://blog.frankel.ch/programming-by-contract-jvm/
[2] Wikipedia: https://en.wikipedia.org/wiki/Design_by_contract
[3] Документация Oracle: https://docs.oracle.com/cd/E19683-01/806-7930/assert-13/index.html
[4] Spring Framework: https://projects.spring.io/spring-framework/
[5] OVal: http://oval.sourceforge.net/
[6] Contracts for Java: https://github.com/nhatminhle/cofoja
[7] Java Modeling Language: https://en.wikipedia.org/wiki/Java_Modeling_Language
[8] Bean Validation: https://en.wikipedia.org/wiki/Bean_Validation
[9] valid4j: http://www.valid4j.org/
[10] Email: https://habrahabr.ru/users/email/
[11] Источник: https://habrahabr.ru/post/352672/?utm_campaign=352672
Нажмите здесь для печати.