重构还是重写?聊聊 Java 代码臭味与重构技巧

会议信息
重构还是重写?聊聊 Java 代码臭味与重构技巧

在这场分享里,讲师会从他多年协助客户重构代码的经验里,提出常见的代码臭味警讯,并分享相应的重构技巧。

会议日期:2020 年 07 月 31 日

会议时间:PM 03:00

主办方:

 
讲师介绍
张博超
Odd-e 敏捷教练

张博超(Jackson)是 Odd-e 敏捷教练,专注于大型产品研发,拥有超过 15 年软件开发和敏捷实践经验。曾为多家知名企业提供教练与培训服务(如通用电气,工商银行,百度,大众点评,宝马集团等),涉及领域涵盖软件研发的多方面,包括组织,团队,产品,技术,工程实践等。对新鲜事物有浓厚兴趣,持续探索创新。多次在 Scrum Gathering,AgileTour 等社区活动发表演讲并参与组织。译有《用户故事与敏捷方法》,《.NET单元测试的艺术》和《实例化需求》。

在重构一书里,将有问题的代码称作代码臭味(Code Smells,有些翻译成”代码异味”、”代码坏味道”),表示这份代码可能有设计上的问题、或是因为写得太凌乱而难以维护。总得来说,这份代码需要通过重构来改善。这样的场景往往是许多开发者的每日工作,因此,判定一位开发者的等级,往往就是看他的重构技巧是否熟练。本次网路研讨会特别邀请到 Odd-e 敏捷教练张博超(Jackson)为大家演示如何用 IntelliJ IDEA 的重构功能来改善 Java 代码臭味。

主題分享

在本次的分享里,张博超先用一个日常工作的场景给大家举了个例子:我们在工作前可能会先收拾一下桌子、重启电脑、提前打开软件、在昨天写的代码上加注释,这些动作代表很多情况下,人会有直觉去做改善措施,这些改善措施的背后都会对应一个需要解决的问题。写代码也是类似的,每当我们说要改善代码前,需要先想清楚这个改善的目标是什么?是要解决什么问题?同时,我们也要思考这个改善的作法是不是能真正的解决问题?

代码示例

为了让大家具体体现重构的过程,张博超以一个计算预算的代码为例子,实际以 IntelliJ IDEA 演示该怎么综合运用提取变量、提取方法、提取类、引入参数对象、修改构造函数等重构功能安全地修改代码,将一个原本高达 30 行、充满原始类型迷恋(Primitives Obsession)、过分亲密(Inappropriate Intimacy)、特性嫉妒(Feature Envy)、抽象干扰(Abstraction Distraction)等臭味的方法,逐步修改成只有 1 行。让代码更简洁、有更好的表达能力、未来更好维护。在重构的过程中,张博超还提醒我们务必每重构一步,就自动化的运行单元测试,快速验证目前的重构工作是否有改动到原有代码的逻辑。从张博超的演示里可以看到,程序员需要练习感知代码臭味的能力,加上对 IntelliJ IDEA 重构工具的熟悉,就可以让我们在面对不同代码臭味时,高效的完成重构工作。

重构与重写的差异

在分享的最后,张博超为大家提点了什么是重构?重构与重写的差别又是什么?简单地说,重构就是在不改变外部行为的情况下,调整代码使其结构更合理或提升可读性。可是这样听起来,重写也能有一样的效果?所以张博超用一个很生动的比喻来形容重构,就是「开着飞机换引擎」。换言之,在开发的过程中,开发工作不会因为重构而停下来。这是一个随时都能发生、小步变动、持续改善的演进。所以张博超鼓励大家平时就可以重构,并不需要刻意找一个时间来做,千万不要等到代码真的无法维护时才重写。

問答

Q. 对老系统利用新的语言特性进行重构时会产生难以调和的矛盾,如 Lambda Function 中无法优雅的 Exception Handling,请教老师如何解决这样的问题呢?

使用新语言更像是重写的范畴,因为你并不是在演进设计,而是在做一个很难撤消的技术决定。所以会建议将老系统拆分成小块的独立组件,以不会影响到主体的前提小规模的导入,这样在实现在会比较容易些。而 Lambda Function 和 Exception Handling 其实并没有很直接的冲突,重点在于我们怎么使用 Exception,往往看到很多同学是随意地抛 Exception,这部份还是得根据不同的场景可能会有不同的作法。

Q. 参数多的时候提取参数对象,导致方法中的 get 方法超过,是因为没有重构完吗?

这背后隐藏的问题可能是有很多的数据簇,通过这些组合出一个很大的概念。所以往往不是很简单的通过提取类就可以结束的,可能要通过很多层的提取,才能将重构完成。

Q. 如何优雅的处理异常,可靠性和优雅可读之间寻求平衡?

大家会觉得异常不好设计,有可能是因为外面调用这个函数的调用方式不合理。我们从防衛性的角度可能会加很多防衛的处理来让代码更稳定可靠,但过度防衛却会造成代码里到处都是 if,而且有可能很多地方都是重复地在做判断。

有一个设计原则叫 Design by Contract,意思就是任何一个函数要保证返回值的合法性、函数的调用者要保证参数的合法性。双方要先沟通好两者间的契约。这样设计后,就只有边界的部份需要处理异常。把握这个原则,才不会让代码里到都在抛异常、也不会到处都在处理异常。

Q. 如果我想将一个成熟的程序移植到其他平台,那么老师我改怎么做才可以改动最少?

这其实已经不是一个重构的问题了。因为你这时才想到要移植到其他平台,表示之前并没有即时的做重构,导致代码极度依赖现在的平台。我的建议是,在写代码时,我们可以通过业务封装,让代码分层,中间层的这些代码应职责明确(概念上听起来与微服务有点类似,但不一定要用微服务才能做到分层)。用这样的方式来开发程序,在维护或移植时,即可以让改动与依赖降到最低。

从会后的问卷调研结果看来,多数的开发者都已经在使用 IntelliJ IDEA 做开发工作,相信这场干货满满的分享里提到的重构技巧,都能帮助大家在开发上更高效!我们未来也会针对不同编程语言举办重构主题的网络研讨会,敬请期待,我们下次见!