V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
beryl
V2EX  ›  程序员

代码优雅实现讨论

  •  
  •   beryl · 2021-08-16 17:59:05 +08:00 · 4359 次点击
    这是一个创建于 1200 天前的主题,其中的信息可能已经有所发展或是发生改变。

    RT, Java 代码,一个方法中,逻辑特别多,于是把里面逻辑封装成了几个单独方法,但是也有七八个单独方法,看起来也有点难受:

    
    fun() {
    
        fun1(); // 数据初始化
        
        // 这里有几行代码,处理特殊逻辑
        
        fun2();
        
        fun3(); // 调用第三方
        
        fun4();
        
        // 这里有打印日志
        
        fun5(); // 缓存处理
        
        fun6();
        
        // 这里有一些代码,做对象转换等
        
        fun7();
        
        // 打印日志等
        
        fun8(); // 缓存处理
        
        return xxx;
       
    }
    

    感觉还是在面向过程去写,有点难受看着,但是有没有优化思路,或者优秀代码参考。

    25 条回复    2021-08-19 10:51:58 +08:00
    cmdOptionKana
        1
    cmdOptionKana  
       2021-08-16 18:44:41 +08:00
    先建模型啊,单单给出正文里这些信息并不足够,你要看看你处理的对象是什么,是用户、商品、文章、图形还是别的什么东西,要先找到对象才能面向对象。
    Rwing
        2
    Rwing  
       2021-08-16 18:46:42 +08:00
    确实是面向过程,你要抽象一下
    然后缓存可以抽出来,日志可以抽出来
    如果可以你可以贴出具体代码
    cmdOptionKana
        3
    cmdOptionKana  
       2021-08-16 18:47:29 +08:00
    找到对象后,就看对象有哪些属性、哪些行为,比如商品有属性“价格”、“分类”,有行为“被预定”、“被销售”等。

    文章有属性“标题”、“发布时间”,有行为“被创建”、“被发布”等。
    misdake
        4
    misdake  
       2021-08-16 18:57:04 +08:00   ❤️ 2
    想到一篇很老的文章,有点无关又有点相关,我贴一下标题和链接。很老的文章,背景也比较复杂,仅供参考。

    John Carmack on Inlined Code

    http://number-none.com/blow/john_carmack_on_inlined_code.html
    CEBBCAT
        5
    CEBBCAT  
       2021-08-16 19:59:02 +08:00
    在我看来,这些逻辑它们本来就是面向过程的,因此你无法通过抽象代码来让代码更优雅。

    不过,可不可以通过 MQ 等,把这些逻辑分开来写呢?我想这是一种有效的解决办法,也是符合直觉和逻辑的
    mxT52CRuqR6o5
        6
    mxT52CRuqR6o5  
       2021-08-16 20:05:15 +08:00
    (如果是 spring 大概会是下面这种思路?)
    数据操作,每张表一个类
    每个第三方的所有操作一个类
    日志一个类
    缓存一个类
    最后一起依赖注入进来
    mxT52CRuqR6o5
        7
    mxT52CRuqR6o5  
       2021-08-16 20:10:01 +08:00
    我看着感觉日志啊缓存啊转换啊,可能有方法去优化掉,就比如日志和缓存有没有什么固定的逻辑,就能抽象出来
    就比如做同一类操作的时候会固定记录日志,那你直接把记录日志的代码放到做这一类操作继承的 class 中去,就不用先调一遍这一类的方法,再手动去记日志
    auh
        8
    auh  
       2021-08-16 20:22:57 +08:00
    这些操作具备对象的特征?个人感觉只能具备业务逻辑的特征。不管是不是面向对象。最后肯定有一个组合逻辑的过程。组合逻辑过程,就像 @misdake 引用的文章类似,采用哪种风格,来管理使得更加灵活或者更加封闭。就算是各个 func 归类到不同的对象中,这个组合过程,还是类似这种逻辑。充其量抽象成接口,能够灵活替换一下。
    wangxiaoaer
        9
    wangxiaoaer  
       2021-08-16 20:57:56 +08:00
    某些 fun 是否提取成对象单独处理,把这个 fun 里面的逻辑交由那个对象负责,也就是 fun()----> anotherObj.fun()
    TypeError
        10
    TypeError  
       2021-08-16 21:05:15 +08:00
    分层加上拆分模块吧
    Kasumi20
        11
    Kasumi20  
       2021-08-16 22:44:59 +08:00
    日志用 AOP, 面向切面编程
    felixin
        12
    felixin  
       2021-08-16 22:55:05 +08:00 via Android
    学习 clean code
    ljzxloaf
        13
    ljzxloaf  
       2021-08-16 23:04:11 +08:00
    需要换个角度去思考问题。

    打个比方,产品需求描述一般是用户场景和交互流程,而这些变化是非常快的,我们不可能给每个场景、每个流程写一套代码,所以需要从业务模型的角度去思考,而不能从业务流程的角度去思考。比如你这种就可以看做是几种业务模型组合起来的流程,其中的业务模型有第三方业务、缓存、日志等,我认为你这种写法没啥问题。如果有些逻辑重复读较高,可以根据业务更为细致的封装。比如 A 业务与 B 业务组合起来形成一个业务。这种组合就看具体需求了。

    流程与对象是相对的,流程的每个节点都是对象,对象的内部逻辑也是流程。

    实践中,我们一般可以同时提供两种接口:一种是不依赖其他业务的“原子”接口;另一种是依赖其他业务的“组合”接口。

    比如搜索,我可以同时提供:返回 id 集合的接口和返回 item 集合的接口。item 信息是我从 item 服务拿到的,这样我等于组合了 item 服务和搜索。反过来也类似,比如敏感内容过滤,我也可以提供两种接口:传参 id 集合和传参 item 集合,如果只传 id 我需要去 item 服务查 item 信息,就是组合接口;如果传给我 item 集合,我就不需要去依赖 item 服务。

    这种做法有什么好处呢?我们可以先考虑这样一个问题,我们有底层服务 A ( atom ),有两个上层服务 C1 、C2 ( combination ),现在有个需求要调用 C1 的接口 C1.I1 和 C2 的接口 C2.I1 ,这两个接口都要调用 A 的接口 A1.I1 ,这样我们为何不先调 A1.I1 ,然后把返回的信息传给 C1 和 C2 呢?当然当我们没有这种冗余调用的时候,还是用原来的接口,这样更方便。

    我分析下来这样做从性能方面应该是有利无害的。从 IO 方面来看,虽然直接传递 item 信息增加了传递参数的网络开销,但是由于不需要去查 item 服务,减少了一次 item 服务返回 item 信息的网络开销,这两者已经抵消了。而后者还减少了查 item 的请求网络开销,item 服务的计算开销,和依赖服务或 db 的计算和网络开销。所以性能无疑是提高了很多。

    上面只讨论了最简单的情况,其实实践中远比这复杂。上层服务会依赖多个下层服务,组合是千变万化的,不可能为每种组合都开个接口。比如 C 依赖 A1 和 A2,那就要四个接口,依赖 A1 的接口( A2 信息通过传参);依赖 A2 的接口( A1 信息通过传参);都不依赖(全部信息通过传参);都依赖。假设依赖 n 个服务,就需要(排列组合 n 选 n 、n-1...1,0 加和)个接口(感觉像科里化过程...),所以还是要根据实际情况具体问题具体分析,只提供那些用户普遍需要的组合。
    sutra
        14
    sutra  
       2021-08-17 00:05:53 +08:00
    非业务相关的逻辑都可以从这个方法里移除。
    比如缓存处理,就可以放到应该被缓存的对象的服务层那里,并通过 annotation,比如 @CacheResult/@Cachable 这类 spring-cache 的注解来完成。
    对象转换,可以通过代码分层到专门做对象转换的类里,比如像他这样: https://juejin.cn/post/6844903685860884488
    jorneyr
        15
    jorneyr  
       2021-08-17 09:04:44 +08:00
    如果是业务代码,且重复出现的次数没有,就这么写,不要优化,否则优化后连自己都不认识了、
    aliveyang
        16
    aliveyang  
       2021-08-17 09:12:08 +08:00
    不只是优化代码,还要优化业务
    sss15
        17
    sss15  
       2021-08-17 09:26:32 +08:00
    @ljzxloaf 手动点赞
    litchinn
        18
    litchinn  
       2021-08-17 10:26:14 +08:00   ❤️ 1
    观察者模式之事件编程之 spring event,可以试下
    CasualYours
        19
    CasualYours  
       2021-08-17 10:48:31 +08:00
    我之前曾经尝试把业务逻辑实现改为链式调用的形式,看上去是舒服了一点。
    TaskWrapperProvider.init(wrapperMapper).createWrapper(taskId).signIn(signParams); // 任务签到的需求
    masterclock
        20
    masterclock  
       2021-08-17 10:50:43 +08:00
    从上到下,没有分支,应该是最完美的代码了,仅次于没有代码。
    最大的问题是写不出这样的代码
    - 业务流程会变化
    - 业务流程会失败
    - 非业务流程的代码也会失败
    - 到处都有可能失败
    - ……

    然后就可以考虑架构设计,代码抽象等来解决问题了
    dqzcwxb
        21
    dqzcwxb  
       2021-08-17 11:24:28 +08:00
    思维导图
    沟通
    spring event
    lambda stream optional
    extract method
    cutepig
        22
    cutepig  
       2021-08-17 13:19:13 +08:00 via Android
    最近在看函數式編程,
    感覺如果你拆分的子函數 func1,。。。能達到函數式的那种重用性,那麽感覺会很不一样
    DICK23
        23
    DICK23  
       2021-08-18 14:00:36 +08:00
    用 AOP 去搞?
    securityCoding
        24
    securityCoding  
       2021-08-18 16:15:44 +08:00
    既然存在上下文关系,考虑定义一个 filter 接口来隔离各个功能实现吧
    funbox
        25
    funbox  
       2021-08-19 10:51:58 +08:00
    把业务逻辑代码写那么复杂 别人砍你
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3146 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 14:31 · PVG 22:31 · LAX 06:31 · JFK 09:31
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.