哈喽,大家好,我是小 ❤,一个在二进制世界起舞的探险家,幻想有一天可以将代码作诗的后台开发。
今天,我要和大家聊聊程序员的神秘技能——重构!别担心,我会用通俗易懂的语言和一些趣味对话来帮助你理解和掌握这个技能,我 8 岁的外甥女听了都说懂。
代码开发:
一个月后:
后面有时间了改一改吧(放心,不会有时间的,有时间了也不会改)。
六个月后:
如上,是任何一个开发者都会经历的场景:早期的代码根本不能回顾,不然一定会陷入深深的怀疑,这么烂的代码真是出自自己的手吗?
更何况,目前大部分系统都是协同开发,每个程序员的命名规范、编码习惯都不尽相同,就导致了一个系统代码,多个味道的情况。
妍妍:嘿,舅舅,听说你要分享重构,这又是什么新鲜事?
❤:嗨,妍妍!重构就是改进既有代码的设计,让它更好懂、更容易维护,而不改变它的功能。想象一下,它就像是给代码来了个变美的化妆术,但内在还是那个代码,不会变成"不认识的人"。
露露:哇,听起来好厉害,那为什么我们要重构呢?
❤:哈哈,好问题,露露!因为代码是活的,一天天在变大,当代码变得难以理解、难以修改时,它就像是一头头重的大象,拖慢了我们前进的步伐。重构就像是给大象减肥,使它更轻盈、更灵活,开发速度也能提升不少!
这和你们有小洁癖,爱收拾房间一样,有代码洁癖的程序员也会经常重构 Ta 们的代码呢!
妍妍:听起来有道理,但什么时候才应该使用重构呢?
❤:好问题,妍妍!有以下几种情况:
露露:明白了舅舅,那重构的具体步骤是什么呢?
❤:问得好,露露,看来你有认真在思考!接下来让我给你介绍一下重构的基本步骤吧!
重构之前,我们需要识别出代码里面的坏味道代码。
所谓坏味道,就是指代码的表面的混乱,和深层次的腐化现象。简单来说,就是感觉不太对劲的代码。
在《重构-改善既有代码的设计》一书中,讲述了这二十多种坏味道情况,我们下面将挑选最常见的几种来介绍。
方法过长是指在一个方法里面做了太多的工作,常常伴随着方法中的语句不在同一个抽象层级,比如 dto 和 service 层代码混合在一起,即逻辑分散。
除此之外,方法过长还容易带来一些额外的问题。
方法太长会导致逻辑难以理解,需要大量的注释,如果 10 行代码需要 20 行注释,代码很难阅读。特别是读代码的时候,常常需要记住大量的上下文。
面向过程的问题在于当逻辑复杂以后,代码会很难维护。
相反地,我们在代码开发时常常用面向对象的设计思想,即把事物抽象成具有共同特征的对象。
解决方法过长时,我们遵循这样一条原则:每当感觉要写注释来说明代码时,就把这部分代码写进一个独立的方法里,并根据这段代码的意图来命名。
方法命名原则:可以概括要做的事,而非怎么做。
一个类做了太多的事情,比如一个类的实现既包含商品逻辑,又包含订单逻辑。在创建时就会出现太多的实例变量和方法,难以管理。
除此之外,过大的类还容易带来两个问题。
当一个类里面包含两个模块的逻辑时,两个模块容易产生依赖。这在代码编写的过程中,很容易发生 “你带着我,我看着你” 的问题。
即在两个模块中,都看到了和另一个模块相关的程序结构或相同意图的方法。
当类的命名不足以描述所做的事情时,大概率产生了耦合结构不良的问题,这和我们想要编写 “高内聚,低耦合” 的代码目标相悖而行了。
将大类根据业务逻辑拆分成小类,如果两个类之间有依赖,则通过外键等方式关联。当出现重复代码时,尽量合并提出来,程序会变得更简洁可维护。
逻辑分散是由于代码架构层次或者对象层次上有不合理的依赖,通常会导致两个问题:
某个类经常因为不同的原因,在不同的方向上修改。
发生某种变化时,需要多个类中修改。
数据泥团是指很多数据项混乱地融合在一起,不易复用和扩展。
当许多数据项总是一起出现,并且一起出现时更容易分类。我们就可以考虑将数据按业务封装成数据对象。反例如下:
func AddUser(age int, gender, firstName, lastName string) {}
重构之后:
type AddUserRequest struct {
Age int
Gender string
FirstName string
LastName string
}
func AddUser(req AddUserRequest) {}
在大多数高级编程语言里面,都有基本类型和结构类型。在 Go 语言里面,基本类型就是 int、string、bool 等。
基本类型偏执是指我们在定义对象的变量时,常常不考虑变量的实际业务含义,直接使用基本类型。
反例如下:
type QueryMessage struct {
Role int `json:"role"`
Content string `json:"content"`
}
重构之后:
// 定义对话角色类型
type MessageRole int
const (
HUMAN MessageRole = 0
ASSISTANT MessageRole = 1
)
type QueryMessage struct {
Role MessageRole `json:"role"`
Content string `json:"content"`
}
这是 ChatGPT 问答时的请求字段,我们可以看到对话角色为 int 类型,且 0 表示人类,1 表示聊天助手。
当直接使用 int 来表示对话 Role 时,没办法直接从定义里知道更多信息。
但是用 type MessageRole int
定义后,我们就可以根据常量值很清晰地看出对话角色分为两种:HUMAN & ASSISTANT.
我们一般的系统都会根据业务 service、中转控制 controller 和数据库访问 dao 等进行分层。一般 controller 调用 service,service 调用 dao。
如果我们在 controller 直接调用 dao,或者 dao 调用 controller,就会出现层次混乱的问题,就可以进行优化了。
妍妍:舅舅,这些坏味道都需要解决吗,你说的这些坏味道代码会带来什么样的影响呢?
❤:是的,代码里如果坏味道代码太多,会带来四个 “难以”。
露露:哦,原来是这样啊,那我们可以去除它们吗?
❤:当然可以了!就像你们爱收拾房间一样,每一个有责任心(代码洁癖)的程序员,都会考虑代码重构。
而对于重构问题,业界已经有比较好的思路:通过持续不断地重构将代码中的 "坏味道" 清除掉。
一个好的命名规范应该符合:
我们拿华为公司内部的 Go 语言的开发规范来举例:
场景 | 约束 | 示例 |
---|---|---|
项目名 | 全部小写,多个单词时用中划线 '-' 分隔 | user-order |
包名 | 全部小写,多个单词时用中划线 '-' 分隔 | config-sit |
结构体名 | 首字母大写 | Student |
接口 | 采用 Restful API 的命名方式,路径最后一部分是资源名词 | 如 [get] api/v1/student |
常量名 | 首字母大写,驼峰命名 | CacheExpiredTime |
变量名 | 首字母小写,驼峰命名 | userName,password |
妍妍:哇,这么多成熟的规范可以用啊!那除了规范,我们还需要注意什么吗?
❤:好问题妍妍!接下来我还会介绍一些常见的重构手法:
露露:舅舅,你讲得太有趣了,我感觉我也会重构了!
❤:露露真棒,我相信你!重构的思想无处不在,就像生活中都应该留白一样,你们的人生也会非常精彩的。在编程里,重构可以让代码更美观、更容易读懂,提高开发效率,是程序员都应该掌握的技能。
妍妍:我也会了,我也会了!以后我也要写代码,做代码重构,我还要给舅舅的文章点赞。
❤:哈哈哈,好哒,你们都很棒!就像你们喜欢打扫卫生,爱好画画读诗一样,如果以后你们想写代码,它们也会十分的干净整洁,充满诗情画意。
最后,如果你觉得有所收获,别忘了点赞和在看,让更多的人了解重构的神奇之处,一起进步,一起写出更好的代码!
希望这篇文章对你有所帮助,也希望你能在编程的路上越走越远。感谢大家的支持,我们下次再见!🚀✨
妍妍说:看完的你还不赶紧分享、点赞、加入在看吗?