新的一年已经到来,祝各位读者2023年身体健康、家庭美满。
回到本篇的主题,我继续来聊聊本周的一些心得。
Go技巧 - 接口实现下的三个代码复用技巧
在面向对象开发的场景下,我们经常会写高度重复的Go
代码。为了帮助大家形成一定的方法论,这里以一个具体场景为例,分享我的三个技巧。
示例:一个接口 + 三个实现
我们以上图为例,看看示例代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
type Order interface { Create() error Close() error }
type Order1 struct {}
func (o *Order1) Create() error { }
func (o *Order1) Close() error { }
type Order2 struct {}
func (o *Order2) Create() error { }
func (o *Order2) Close() error { }
type Order3 struct {}
func (o *Order3) Create() error { }
func (o *Order3) Close() error { }
|
在实际场景中,Order1
/Order2
/Order3
的逻辑、数据高度相似,出现大量的重复性代码。如何提升这部分代码的开发效率呢?下面给出三个途径:
方法1:快刀斩乱麻 - 函数复用
最直接的方法就是抛开面向对象的一堆概念,单纯地用函数复用来解决问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| func createOrder(ctx context.Context, data interface{}) error { }
func closeOrder(ctx context.Context, data interface{}) error { }
type Order1 struct {}
func (o *Order1) Create() error { }
func (o *Order1) Close() error { }
|
这种编程思维是面向过程的,虽然不够抽象,但它确实是 最便捷的代码复用方式。而且,在很多情况下,我们不会对这块代码有大更新,函数复用是一个 高性价比 的选择。
但我们的追求不仅限于此:如果这块代码涉及业务核心,高频迭代,会出现什么样的现象呢?举3个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
func createOrder(ctx context.Context, data interface{}, other, more interface{}) error { }
func createOrder(ctx context.Context, data interface{}, otherData interface{}) error { if orderType == 1 { } else if price > 1000 { } else { } }
func createOrder2() error {} func createOrder3() error {}
|
以上这些代码是不整洁的,相信大部分人不愿在自己开发过程中看到。这种过程性代码复用的思路,见效虽快,但在复杂场景下弊端愈发明显。下面,我们引入第二个方法:
方法2:对象抽象的妙用 - 嵌套+Overwrite
Go
并不是一门完全面向对象的语言,但对于复杂场景,会用嵌套来支持一定的代码复用。代码示例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| type CommonOrder struct {}
func (o *CommonOrder) Create() error { }
func (o *CommonOrder) Close() error { }
type Order1 struct { *CommonOrder }
type Order2 struct { *CommonOrder }
func (o *Order2) Create() error { }
type Order3 struct { *CommonOrder }
func (o *Order3) Create() error { }
func (o *Order3) Close() error { }
|
嵌套+Overwrite
的组合能力,支撑了Go
语言面向对象的很多特性。与之前的函数复用对比,这种方法的可读性会更棒(这也非常依赖开发者面向对象的抽象能力)。
到这一阶段,维护绝大多数的项目已经足够。但如果你是一个苛求细节的人,在继续开发的过程中会发现一个问题:即便代码的逻辑一致,我们却常常因为数据结构不同,而编写出高度重复性的代码。那么,我们再看第三个方法:
方法3:剥离数据结构的差异 - 泛型
我们先看如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| type OrderInfo1 struct{}
func (o *Order1) Create() error { var order *OrderInfo1 err := mysql.Insert(order) if err != nil { return err } b,err := json.Marshal(order) if err != nil { return err } fmt.Println(string(b)) return nil }
type OrderInfo2 struct{}
func (o *Order2) Create() error { var order *OrderInfo2 }
type OrderInfo3 struct{}
func (o *Order3) Create() error { var order *OrderInfo3 }
|
这部分代码很难通过上述两个方法解决。而如果利用泛型,会变得非常巧妙:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| func Create[OrderInfo interface{}](order OrderInfo) error { err := mysql.Insert(order) if err != nil { return err } b,err := json.Marshal(order) if err != nil { return err } fmt.Println(string(b)) return nil }
func (o *Order1) Create() error { var order *OrderInfo1 return Create[OrderInfo1](order) }
func (o *Order2) Create() error { var order *OrderInfo2 return Create[OrderInfo2](order) }
func (o *Order3) Create() error { var order *OrderInfo3 return Create[OrderInfo3](order) }
|
泛型特性的引入,往往出现在数据处理层,即和基础库、工具库相关的地方,而在业务层很少出现。我们可以从如下两点进行分析:
- 业务层主要的特点在与 逻辑差异大,对数据结构也有各种校验等,不适用泛型;
- 数据处理层则往往逻辑一致,仅仅只有数据结构的差异,泛型非常适配。
小结
函数复用、嵌套+Overwrite
、泛型,是三种非常有效的代码复用技巧。希望大家能够循序渐进,在工程中找到属于自己的最佳实践。
编程思考 - 开发前的三个文档
在开发一个项目前,有三个文档是必备的,我们称为 - BRD、PRD、技术方案,它们在项目流程中依次编写。
- BRD(商业需求文档):这个文档有一个关键词 - 商业价值,不仅要了解用户痛点,更要结合市场,发掘价值
- PRD(产品需求文档):与产品经理角色相关,设计功能交互,体现出两种重要的思维:产品思维与用户思维
- 技术方案:开发者最熟悉的文档,最主要的是设计,但更重要的是评估能力,如排期、风险
编写技术方案不难,普通开发者工作两三年就能有一个很棒的呈现;而PRD则须要 视野转换,从用户与产品的角度来思考功能的开发;BRD则最为复杂,往往要多年行业经验积累以及深刻的用户洞察。
大家可以在日常开发中多主动地接触优秀的PRD、BRD,不仅能拓宽视野,更能提升个人认知。
工作生活 - 学会聚焦,才能做好取舍
不同人、在不同的阶段,对工作和生活的平衡点都有不同的理解。所以,我认为没有必要去过多地从他人经验里去探求 最佳平衡点,也没有必要把工作和生活当作对立面,而是在日常反复问自己:我究竟想要什么?
有取,往往就需要舍弃,这时就会犹豫代价是否过大。我总是过多地担忧所失去的,就扭曲了原问题:不再关注自己最想要的,而转过头去关注可能失去的,情绪上出现焦虑,甚至恐慌。简而言之,就是要认清自己,学会聚焦。
Github: https://github.com/Junedayday/code_reading
Blog: http://junes.tech/
Bilibili: https://space.bilibili.com/293775192
公众号: golangcoding