Function + 异常策略链:构建可组合的异常封装工具类

发布于:2025-08-09 ⋅ 阅读:(12) ⋅ 点赞:(0)

在这里插入图片描述

引言:异常到底该谁处理?

在日常 Java 业务开发中,我们常常写出这样的代码:

String result = someRemoteCall();
if (result == null) {
    throw new RuntimeException("接口返回为空");
}

看起来没问题,但一旦这种模式重复几十次,并在不同的团队成员手中以不同的形式出现(Optionalif-null-throw、try-catch 包装、日志输出混杂),你就会发现:异常的处理并不统一,也很难组合。

有没有办法像处理数据一样“组合”异常策略?答案是:有 —— 结合 Function<T, R> + 策略链思想,我们可以优雅地构建一套“异常装饰器”工具类。


核心思想:Function 的包装链 + 异常策略分离

我们想要实现的目标是:

  • 可以对任何 Function 进行统一的异常封装(比如异常转换、日志打点、返回默认值等)
  • 保持函数式接口的组合能力(支持 .andThen() / .compose()
  • 封装后的 Function 可被透明使用,不需要改业务代码

本质上是一种责任链模式 + 装饰器模式 + 函数式编程的融合。


工具类设计:FunctionChainWrapper

@FunctionalInterface
public interface CheckedFunction<T, R> {
    R apply(T t) throws Exception;
}

我们定义了一个能抛异常的函数接口,然后封装一个 FunctionWrapper 工具:

public class FunctionChainWrapper<T, R> {

    private final CheckedFunction<T, R> originalFunction;

    private final List<Function<Throwable, R>> exceptionHandlers = new ArrayList<>();

    private Function<Exception, RuntimeException> exceptionTranslator = RuntimeException::new;

    private Consumer<Throwable> exceptionLogger = e -> {}; // 默认无操作

    private R fallbackValue = null;
    private boolean hasFallback = false;

    public FunctionChainWrapper(CheckedFunction<T, R> originalFunction) {
        this.originalFunction = originalFunction;
    }

    public FunctionChainWrapper<T, R> onException(Function<Throwable, R> handler) {
        this.exceptionHandlers.add(handler);
        return this;
    }

    public FunctionChainWrapper<T, R> logException(Consumer<Throwable> logger) {
        this.exceptionLogger = logger;
        return this;
    }

    public FunctionChainWrapper<T, R> translateException(Function<Exception, RuntimeException> translator) {
        this.exceptionTranslator = translator;
        return this;
    }

    public FunctionChainWrapper<T, R> fallback(R value) {
        this.fallbackValue = value;
        this.hasFallback = true;
        return this;
    }

    public Function<T, R> build() {
        return input -> {
            try {
                return originalFunction.apply(input);
            } catch (Throwable ex) {
                exceptionLogger.accept(ex);
                for (Function<Throwable, R> handler : exceptionHandlers) {
                    try {
                        return handler.apply(ex);
                    } catch (Throwable ignore) {}
                }
                if (hasFallback) return fallbackValue;
                if (ex instanceof RuntimeException) throw (RuntimeException) ex;
                throw exceptionTranslator.apply(new Exception(ex));
            }
        };
    }
}

使用示例:封装远程调用函数

FunctionChainWrapper<String, String> wrapper = new FunctionChainWrapper<>(input -> {
    if ("bad".equals(input)) throw new IOException("网络失败");
    return "Result: " + input;
});

Function<String, String> safeFunc = wrapper
    .logException(e -> log.error("调用失败: {}", e.getMessage()))
    .fallback("默认值")
    .build();

String val = safeFunc.apply("bad"); // 输出:默认值

可以继续组合使用:

Function<String, String> func = safeFunc
    .andThen(res -> res.toUpperCase())
    .andThen(res -> res + " ✅");

String result = func.apply("bad"); // "默认值 ✅"

与 BiFunction 的扩展

只需简单改造:

@FunctionalInterface
public interface CheckedBiFunction<T, U, R> {
    R apply(T t, U u) throws Exception;
}

public class BiFunctionChainWrapper<T, U, R> {
    // 与 FunctionChainWrapper 类似,只是换成 BiFunction
}

你就可以封装两个参数的函数,如数据库查询、组合业务等:

BiFunctionChainWrapper<String, Integer, String> wrapper = new BiFunctionChainWrapper<>(
    (name, age) -> {
        if (age < 0) throw new IllegalArgumentException("非法年龄");
        return name + " is " + age + " years old.";
    }
);

BiFunction<String, Integer, String> safeFunc = wrapper
    .translateException(e -> new BusinessException("年龄不合法", e))
    .fallback("默认用户")
    .build();

单元测试建议(Function 单测思路)

  1. 输入正常、输出正常
  2. 输入异常(会触发 fallback / log)
  3. 异常链中的处理函数也抛异常(验证链的容错)
  4. 验证 log 是否触发(可 mock logger
  5. 组合测试(andThen)是否正常传递
@Test
void testFallbackTriggered() {
    FunctionChainWrapper<String, String> wrapper = new FunctionChainWrapper<>(s -> {
        throw new RuntimeException("fail");
    }).fallback("safe");

    Function<String, String> safeFunc = wrapper.build();

    Assertions.assertEquals("safe", safeFunc.apply("anything"));
}

避坑提示

  • 不要用 try-catch 把所有逻辑包死,应该聚焦于“异常感知点”来封装(I/O、反序列化、远程调用等)
  • 日志与 fallback 分离,不要耦合:打日志是一种副作用,fallback 是行为策略
  • 组合时注意状态性函数(如带缓存)之间的顺序

延伸思考:函数式异常封装的更大价值

  • 能让你的业务逻辑表达更加清晰(只关注 happy path)
  • 异常处理逻辑被拆分并复用(通用 fallback、打点策略可组合)
  • 可以在策略链中插入熔断器、缓存装饰器等 —— 用法无限扩展

总结

Function + 异常策略链 是一个十分实用且优雅的模式。它不仅解决了代码冗余和异常处理不一致的问题,还让我们重新认识了函数式接口的扩展能力。

推荐阅读文章


网站公告

今日签到

点亮在社区的每一天
去签到