重构你的代码风格 —— 从帮一个朋友改进代码说起

今天根据具体实例谈一个代码风格的问题。


在一个群里,朋友 A 发来一段代码:

public void convertData(Long companyId) {
    try {
        convertOperator(companyId);
        convertDict(companyId);
        converSupplier(companyId);
        converCustomer(companyId);
        convertEnum(companyId);
        convertMenu(companyId, LanguageEnum.defaultValue());
    } catch (Exception e) {
        log.error("数据label转换异常", e);
    } finally {
        clearTheadLocal();
    }
}

他问,如果一个方法报错,不影响后面的方法执行,有什么好办法吗

朋友 B 看到后说,每个方法里面单独 try catch

我看到后回了一句 你们函数式思维有待加强

朋友 A 回复朋友 B 说,我的解决方案和你一样,但是我不想写这么挫的代码,就过来问问你们

接着我发了一段下面的代码到群里:

List<Consumer<Long>> consumers = Arrays.asList(
        this::convertOperator,
        this::convertDict,
        // 自己补后面的,

        id -> this.convertMenu(id, LanguageEnum.defaultValue())
);

boolean result = consumers.stream().reduce(true, (a, b) -> {
    try {
        b.accept(companyId);
        return a;
    } catch (Exception e) {
        return false;
    }
}, Boolean::logicalAnd);

if (!result) {
    log.error("数据label转换异常", e);
}
clearTheadLocal();

如果你能看明白并且习惯于这种编码风格就不用往下读了。下面我会再唠叨几句。


1关于函数式风格

我们写代码的目的通常是为了处理数据,而编码的风格一般是针对数据,写一些处理数据的函数,然后将数据作为参数依次调用这些函数

这种编码风格的重心是 面向数据,函数只是处理数据的工具。

而我上面说的 函数式编程,它的重心是 面向函数,数据只是函数运行时依赖的参数。

这种重心的转变会造成完全迥异的编码风格。

函数式编程的一个特点,是把一个需求分成若干小的函数块。函数执行完,需求也就完成了。

切记,它的重点是整个过程是面向函数的。

结合上面朋友问的问题来说,对于数据 companyId,它要执行很多的 convertXX() 函数。单个函数的失败不会影响后续函数的执行,当所有函数执行结束后,如果有失败的,则打印一条日志,最终做一些清理工作。

以上,我们可以做如下封装:

  1. 一组 convertXX() 函数。每个函数都可能抛异常,为了不影响后续函数的执行,我们将函数的成功执行返回 true,失败返回 false
  2. 将这组函数的最终执行结果进行 逻辑与 操作,判断是否全部成功,如果不是,则打印日志。
  3. 做一些清理工作。

最终的实现:

我们可以把这组 convertXX() 函数打包成一个 Stream 流,依次执行它们并将执行结果通过 reduce()  操作转换成最终的值,最后再做日志和清理工作。

根据以上分析,我们甚至能给出更加函数式的代码(相对于上面发到群里的而言。同时为了方便解释,做了特殊的格式化):

public boolean convertData(Long companyId) {

    return Stream.<Consumer<Long>>of( // 一组函数
            this::convertOperator,
            this::convertDict,
            // 自己补后面的,

            // 把多参数的函数转换成单参数的
            id -> this.convertMenu(id, LanguageEnum.defaultValue())
    ).collect(collectingAndThen(
            reducing(
                    true,
                    (c) -> { // 依次执行单个函数,成功返回 true;失败 false
                        try {
                            c.accept(companyId);
                            return true;
                        } catch (Exception e) {
                            // todo log e
                            return false;
                        }
                    },
                    Boolean::logicalAnd // 汇总所有函数的执行结果
            ),
            r -> { // 所有函数执行完成后,执行日志和清理
                
                if (!r) {
                    log.error("数据label转换异常");
                }
                clearTheadLocal();
                return r;
            }));
}

其实,类似的编码风格我在之前的文章中也有提到:编码技巧:pipe 思想

怎么样,有收获吗?


原文始发于微信公众号(背井):重构你的代码风格 —— 从帮一个朋友改进代码说起

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/246646.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!