Кроме классического подхода для обработки ошибок с помощью исключений, можно выделить также функциональный подход.
Вместо того, чтобы кидать исключение сразу, можно его локализировать, а потом выполнить над ним определеные действия.
Например, в языке Scala для этого используется определенный класс Try.
def inputStreamForURL(url: String): Try[Try[Try[InputStream]]] = parseURL(url).map { u =>
Try(u.openConnection()).map(conn => Try(conn.getInputStream))
}
В Java мире с помощью библиотеки Vavr также можно обрабатывать ошибки в функциональном стиле.
Try.of(() -> u.openConnection()).getOrElse(other);
В Java 8 для более коретной работы с null типами был добавлен класс Optional. Он позволяет обворачить объект, который может быть null, и в функциональном стиле безопасно с ним дальше работать.
Для работы с разными типами исключений, можно реализовать подобный к Optional класс, такой же потокобезопасный и немутабельный.
public final class Expected<T, E extends Throwable> {
private final T value;
private final E error;
private Expected() {
this.value = null;
this.error = null;
}
private Expected(T value) {
this.value = Objects.requireNonNull(value);
this.error = null;
}
private Expected(E error) {
this.error = Objects.requireNonNull(error);
this.value = null;
}
public static <T, E extends Throwable> Expected<T, E> of(T value) {
return new Expected<>(value);
}
public static <T, E extends Throwable> Expected<T, E> of(E error) {
return new Expected<>(error);
}
public static <T, E extends Throwable> Expected<T, E> of(Supplier<T> supplier) {
try {
return new Expected<>(supplier.get());
} catch (Throwable e) {
return new Expected<>((E) e);
}
}
public boolean isValue() {
return value != null;
}
public boolean isError() {
return error != null;
}
public T value() {
return value;
}
public E error() {
return error;
}
}
Также для проверки было брошено исключение или нет можно написать простой визитер.
Expected<Integer, SQLException> data = Expected.of(new SQLException());
matches(data,
Expected::error, e -> out.println("get error: " + e),
Expected::value, v -> out.println("get value: " + v)
);
Expected<Integer, ArithmeticException> expression = Expected.of(() -> 4 / 0);
matches(expression,
Expected::error, e -> out.println("get error: " + e),
Expected::value, v -> out.println("get value: " + v)
);
public static <T, E extends Throwable>
void matches(Expected<T, E> value,
Function<Expected<T, E>, E> firstFunction, Consumer<E> firstBranch,
Function<Expected<T, E>, T> secondFunction, Consumer<T> secondBranch) {
if (value.isError()) {
E arg = firstFunction.apply(value);
firstBranch.accept(arg);
} else {
T arg = secondFunction.apply(value);
secondBranch.accept(arg);
}
}
В С++23 планируется добавить подобный класс в стандартную библиотеку.
Такой класс мог бы составить хорошую компанию Optional в стандартной библиотеке Java. Но увы, в данный момент, чтобы работать с исключения в функциональном стиле можно использовать библиотеку Vavr или писать свои классы на подобии к Expected.
Полный исходной код класса можно посмотреть на github: link.
Автор: koowaah