引起了很大的共鸣,对于代码质量以及设计方面在思想上面有很多共同点,有很多地方很赞同作者的观点,特此引用其中部分内容,同时加上个人的思考(链接文件内容很长,值得花时间阅读)。
项目重复代码过多
重复代码过多一方面是这些重复逻辑内嵌在方法里面,没有独立抽出来,一方面是rd在实现功能时不知道有已实现代码可用。
- 对于内嵌方法可以加强codeview,rd在开发过程中尽量将其抽离成独立方法。
- 对于是否有可复用代码,rd需要先对需求熟悉,然后对功能了解下,可以询问对于原有功能开发的rd是否有可复用代码,对于model层,工具层等方法,可以全量搜索check下,这些一般比较好找。
- 组件臃肿:Service 组件的个数跟领域实体对象个数基本相当,必然造成个别 Service 组件变得非常臃肿——API 非常多,代码行数达到几千行;
- 职责模糊:业务逻辑往往跨多个领域实体,无论放在哪个 Service 都不合适,同样的,要找一个功能的实现逻辑也无法确定在哪个 Service 中;
当我们说“代码中包含的业务逻辑”的时候,我们到底在说什么?业界并没有一个标准,大家经常讲的 CRUD 增删改查其实属于更底层的数据访问逻辑。
我的观点是:所谓代码中的业务逻辑,是指这段代码所表现出的所有输入输出规则、算法和行为,通常可以分为以下 5 类:
- 输入合法性校验;
- 业务规则校验:典型的如检查交易记录状态、金额、时限、权限等,通常包含数据库或外部接口的查询作为参考;
- 数据持久化行为:数据库、缓存、文件、日志等任何形式的数据写入行为;
- 外部接口调用行为;
- 输出/返回值准备。
当然具体到某一个组件实例,可能不会包括上述全部 5 类业务逻辑,但是也可能每一类业务逻辑存在多个。
单这样看你可能觉得并不是特别复杂,但是现实中上述 5 类业务逻辑中的每一个通常还包含着一到多个底层实现逻辑,如 CRUD 数据访问逻辑或第三方 API 的调用。
例如输入合法性校验,通常需要查询对应记录是否存在,外部接口调用前通常需要查询相关记录以获得调用接口需要的参数,调用接口后还需要根据结果更新相关记录状态。
显然这里存在两个 Level 的逻辑——High Level 的与业务需求对应且关联紧密的逻辑、Low Level 的实现逻辑。
如果对两个 Level 的逻辑不加以区分、混为一谈,代码质量立刻就会遭到严重损害:
- 可读性变差:两个维度的复杂性——业务复杂性和底层实现的技术复杂性——被掺杂在了一起,复杂度 1+1>2 剧增,给其他人阅读代码增加很大负担;
- 可维护性差:可维护性通常指排查和解决问题所需花费的代价高低,当两个 level 的逻辑纠缠在一起,会使排查问题变的更困难,修复问题时也更容易出错;
- 可扩展性无从谈起:扩展性通常指为系统增加一个特性所需花费的代价高低,代价越高扩展性越差;与排查修复问题类似,逻辑纠缠显然也会使添加新特性变得困难、一不小心就破坏了已有功能。
下面介绍一下 Template Method 设计模式的运用,简单归纳就是:
- High Level逻辑封装在抽象父类AbsUpdateFromMQ的一个final function中,形成一个业务逻辑的模板;
- final function保证了其中逻辑不会被子类有意或无意的篡改破坏,因此其中封装的一定是业务逻辑中那些相对固定不变的东西。至于那些可变的部分以及暂时不确定的部分,以abstract protected function形式预留扩展点;
- 子类(一个匿名内部类)像“做填空题”一样,填充模板实现Low Level逻辑——实现那些protected function扩展点;由于扩展点在父类中是abstract的,因此编译器会提醒子类的程序员该扩展什么。
那么它是如何避免上面两个方案的 4 个局限性的:
- Low Level 需要修改或替换时,只需从父类扩展出一个新的子类,父类全然不知无需任何改动;
- 无论是父类还是子类,其中的 function 对外层的 XyzService 组件都是不可见的,即便是父类中的 public function 也不可见,因为只有持有类的实例对象才能访问到其中的 function;
- 无论是父类还是子类,它们都是作为 XyzService 的内部类存在的,不会增加新的 java 类文件更不会增加大量无意义的 API(API 只有在被项目内复用或发布出去供外部使用才有意义,只有唯一的调用者的 API 是没有必要的);
- 组件依赖失控的问题当然也就不存在了。
上面的那种解决方案,其实简单点说就是把功能尽量独立出来,方法不要太大,这样可以保证代码复用,方法比较清晰。
本人给出的解决方案是:
比如一个互联网企业(腾讯) 和一个房地产企业(万达)都想进入电商领域,但是这领域和他们原有领域有一定交集但是也有比较大的差集(一个搞社交,但是有互联网基因,一个搞地产,有渠道基因) 如果可以融合就可以很好的合作了,那怎么融合呢,融合后是并入腾讯,还是融入万达,在大家看来其实都不合适,其实把它独立处理是一个比较好的选择,所以成立了飞凡,这个新的领域可以很好的融合两者的领域,同时又保证各自领域的独立性。
如何具体实现呢
有了独立的领域后难免会有类命名和代码复用,以及领域之间调用问题,继续看下去:
对于命名问题:
目前大部分的设计思路是将业务逻辑放在service层中,比如在user领域有userservice类,product下面有productservice,在userproduct领域也可以有userservice类和productservice类,(不要介意同样类名称)
这时如果需要查看user下面有哪些产品就可以直接寻找到userproduct领域下面的productservice。
对于领域之间调用问题:
通过上面的命名逻辑以及领域设计可以看出来,领域之间是会有交集的,存在复用的可能,比如userProduct领域里边的productservice 是对product领域里边的productservice有所依赖的。
在实际工程实现中对于小型的项目可以直接依赖,将对应的service引入就可以。一种比较好的处理方式是在领域里边新加一个类专门处理领域之间的依赖,方便后期维护及替换等等。
(关键点:业务逻辑一定要进行拆分,将方法抽离成小函数,保证代码质量,提高后续的代码复用)
同理service层可以采用上述设计方式,controll层也可采用上述设计方式。