代码优雅实现讨论

2021-08-16 17:59:05 +08:00
 beryl

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


fun() {

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

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

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

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

John Carmack on Inlined Code

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

不过,可不可以通过 MQ 等,把这些逻辑分开来写呢?我想这是一种有效的解决办法,也是符合直觉和逻辑的
mxT52CRuqR6o5
2021-08-16 20:05:15 +08:00
(如果是 spring 大概会是下面这种思路?)
数据操作,每张表一个类
每个第三方的所有操作一个类
日志一个类
缓存一个类
最后一起依赖注入进来
mxT52CRuqR6o5
2021-08-16 20:10:01 +08:00
我看着感觉日志啊缓存啊转换啊,可能有方法去优化掉,就比如日志和缓存有没有什么固定的逻辑,就能抽象出来
就比如做同一类操作的时候会固定记录日志,那你直接把记录日志的代码放到做这一类操作继承的 class 中去,就不用先调一遍这一类的方法,再手动去记日志
auh
2021-08-16 20:22:57 +08:00
这些操作具备对象的特征?个人感觉只能具备业务逻辑的特征。不管是不是面向对象。最后肯定有一个组合逻辑的过程。组合逻辑过程,就像 @misdake 引用的文章类似,采用哪种风格,来管理使得更加灵活或者更加封闭。就算是各个 func 归类到不同的对象中,这个组合过程,还是类似这种逻辑。充其量抽象成接口,能够灵活替换一下。
wangxiaoaer
2021-08-16 20:57:56 +08:00
某些 fun 是否提取成对象单独处理,把这个 fun 里面的逻辑交由那个对象负责,也就是 fun()----> anotherObj.fun()
TypeError
2021-08-16 21:05:15 +08:00
分层加上拆分模块吧
Kasumi20
2021-08-16 22:44:59 +08:00
日志用 AOP, 面向切面编程
felixin
2021-08-16 22:55:05 +08:00
学习 clean code
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
2021-08-17 00:05:53 +08:00
非业务相关的逻辑都可以从这个方法里移除。
比如缓存处理,就可以放到应该被缓存的对象的服务层那里,并通过 annotation,比如 @CacheResult/@Cachable 这类 spring-cache 的注解来完成。
对象转换,可以通过代码分层到专门做对象转换的类里,比如像他这样: https://juejin.cn/post/6844903685860884488
jorneyr
2021-08-17 09:04:44 +08:00
如果是业务代码,且重复出现的次数没有,就这么写,不要优化,否则优化后连自己都不认识了、
aliveyang
2021-08-17 09:12:08 +08:00
不只是优化代码,还要优化业务
sss15
2021-08-17 09:26:32 +08:00
@ljzxloaf 手动点赞
litchinn
2021-08-17 10:26:14 +08:00
观察者模式之事件编程之 spring event,可以试下
CasualYours
2021-08-17 10:48:31 +08:00
我之前曾经尝试把业务逻辑实现改为链式调用的形式,看上去是舒服了一点。
TaskWrapperProvider.init(wrapperMapper).createWrapper(taskId).signIn(signParams); // 任务签到的需求
masterclock
2021-08-17 10:50:43 +08:00
从上到下,没有分支,应该是最完美的代码了,仅次于没有代码。
最大的问题是写不出这样的代码
- 业务流程会变化
- 业务流程会失败
- 非业务流程的代码也会失败
- 到处都有可能失败
- ……

然后就可以考虑架构设计,代码抽象等来解决问题了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/796126

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX