前言
之前介绍了一些Golang的基础使用,从学习的角度上demo化了比较重要的几个要点,这一篇主要介绍一些从个人角度以及开发习惯上对于Golang布局的理解。
1. (golang package) vs (.net nuget/ java jar )
我本人之前是一个csharper,第一门接触的语言是php,所以在学golang的时候会不自觉的是用传统概念的三层架构去理解golang的布局,有的时候会发现,怎么写代码是如此的变扭,越写越怀疑golang这语言写起来怎么那么尴尬,为此我特地问了些先行者,也网上看了一些golang如何布局的帖子,最后我总结了原因,后来别人问我我都是那么回答的,可能有不足之处。
golang的package的导包比较特殊,当import了一个package后,该package的东西就可以使用了,这在我看来有点类似于领域驱动的概念,又有点组件化的概念,也和golang的特性直接相关,因为Golang的interface编程更贴合组装式的。
java或者.net则是继承式的,当然也有interface的概念,但是因为有天然的继承,所以interface也会向继承的概念侧重,并且目前最流行的IOC的编程,更加让这些语言向继承倾斜,所以他们的包管理适合全量,简单的项目分类可以侧重model, business,data式的分层。
这恰恰是该类开发者转golang的缺点所在,因为对于这样的编程模式太熟悉了,导致概念的转换显得有些笨拙。
golang的import式编程目的在于让项目按功能块划分的更细,一个小业务块有一个自己的包,多包之间可以是并列的,更像最近火起的微服务概念的抽想,我们可以通过golang开源项目docker(现在叫moby)的开源库来理解,其中container和image各有一个属于自己的包,如果以java或者.net来,很可能这2个都是model层的一个class了。
在我自己的编写的项目中,我是这样理解的,拿区块链钱包举例,币种的概念和交易以及公私钥和后台爬块这4个点我会抽象成 coin、trans、key、daemon,这样开源后,如果别人感觉我的coin包写的好,那么就直接借用吧,并不需要其他3个包,如果他觉得key的公私钥管理包很适合他,其他的没有可取之处,那么走您,import key包。
golang更考验开发者对于对象化概念的抽象,如何量化一个package的边界,如何实现package之间的依赖,这些都是学问。
2. interface 与 struct 结合使用
- 相对于面向对象的封装、继承、多态,还是以区块链为例,多种主链币之间,每个币种也业务实现都有一定自由规则,但是概念上区块链交易概念是类似的,我们可以接口化约束币种钱包的功能,使得更易于业务逻辑的管理
type BTC struct{
}
type ETH struct{
}
type Walleter interface {
Transfer()
GetBalance(account string)int
}
func(*BTC)Transfer(){
}
func(*BTC)GetBalance(account string)int{
}
func(*ETH)Transfer(){
}
func(*ETH)GetBalance(account string)int{
}
因为上面的封装,我们可以使主流程单一化,另外一点interface把一个业务块的逻辑体现在了几行简短的代码块中,也是对业务梳理的一个提纲,一个目录,所以即使不是多态的概念,也可以适当的运用interface。
- 然而有时候我们还会需要数据库的操作
type UserStorage struct{
Save(account string,balance int)
Remove()
}
type BlockChain struct{
db *UserStorage
Walleter
}
func(bc *BlockChain)GetBalance(account string){
bal := bc.Walleter.GetBalance(account)
bc.db.Save(account,bal)
}
如此封装的好处在于db的引用全是在BlockChain内部的,方便存储介质的替换,相比于db逻辑传参进来更易于维护和拓展,如果有BlockChain,BlockChain1,BlockChain2都这样使用,那么我们替换BlockChain1时,不管是查找引用,还是模块化替换都更加工程化。
- 另一外一个interface的妙用就是,将struct解除对外的暴露,直接暴露interface,所有需要对外提供的数据都已interface的func对外返回,如果开发者觉暴露struct会导致其他开发者调用中引起问题的话。
type core struct{
}
type Corer interface{
GetCoreNum()
SetCoreName()
}
func NewCore()Corer{
}
3. go和channel的使用
这2个是Golang的一个关键点所在,具体原理我在golang 基础介绍part-2中介绍过,然而对于go和channel的使用,我个人认为,一定要在把控好的基础上使用,比如go并发量一旦大过头了可能会让数据库读写跟不上,channel乱用会导致一个流程不见底,这些都会导致代码的后期拓展和维护存在大问题,一个项目的好坏是把复杂的逻辑写的更简单易懂,大道若简,而不是为了炫技把一个简单的逻辑复杂化导致维护拓展成本的上升
4. 单元测试,性能测试
关于单元测试,很多程序员都不喜欢自己写,当初我在开发.NET项目时,起初也是认为我的代码没问题,我能通过自己的方法确保他是OK的,但是在开发过程中,其实变相的还是写了很多单元测试,因为要确保逻辑通啊,方便修改啊,方便调试啊,在golang项目中,test的编写更加容易,单元测试的编写能够让你设计的项目结构依赖更顺畅,如果单元测试不好写,那么就说明项目结构有问题了,需要反思一下了,如何才能让单元测试的编写更简单,这也是一个反哺的过程,编写单元测试越容易说明代码结构解耦的越好,越健壮。
性能测试也是不得不提的一个点,在代码优化时,他能计算出的在并行状态下的运算时间,消耗了多少内存,实例化了多少个对象等等,能让人更好的把控自己的代码,同时也加深了对于Golang原理的了解,有助于编写出更加优秀的代码。
对于单元测试的数据及启动项,我个人习惯加一个Mock包用作全局测试数据的管理,全局的test直接应用这个包使用依赖或者数据,这样整个项目的启动依赖也体现在了mock包中,这样测试和实际就牢牢结合在了一起。
5. 项目结构方面
Golang 标准包布局[译]
这边帖子很好。
有疑问加站长微信联系(非本文作者)