这是我参与「第五届青训营 」笔记创作活动的第2天
本篇文章主要是对Go语言中所涉及到的语言进阶、依赖管理、测试以及项目实战的简单介绍。
1.语言进阶
对于Go语言,一个突出的优点是:快!!!
关于这个问题,其主要原因为:Go语言中存在并发和并行两种运行方式。即:多线程程序在一个核的cpu上运行;多线程程序在多个核的cpu上运行。
1.1 Goroutine
注意两个概念:
协程:用户态,轻量级线程,栈MB级别
线程:内核态,线程跑多个协程,栈KB级别
CSP(communicating Sequential Processes) 提倡通过通信共享内存而不是通过共享内存而实现通信
1.2 Channel
make(chan 元素类型,【缓冲大小】)
无缓冲通道 make(chan int); 有缓冲通道 make(chan int,2)
关于并发安全Lock问题, 这边给出一些小提示:
并发安全问题的发生有一定概率,通过程序的运行,加锁后输出的结果正确,不加锁输出的结果存在错误。
2.依赖管理
工程项目不可能基于标准库0~1编码搭建, 管理依赖库
2.1 Go依赖管理演进
GOPATH —> Go Vendor —> Go Module
2.1.1 GOPATH
1、环境变量 $GOPATH
2、bin 项目编译的二进制文件
3、pkg 项目编译的中间产物,加速编译
4、src 项目源码,项目代码直接依赖scr下的代码
5、go get下载最新版本的包到scr目录下
GOPATH的弊端是:无法实现package的多版本控制。 为了解决该问题,我们引入新的版本。
2.1.2 Go Vendor
该版本的主要方式为:项目目录下增加vendor文件,所有依赖包副本形式放在$ProjectRoot/vendor
依赖寻址方式:vendor => GOPATH
通过每个项目引入一份依赖的副本,解决了多个项目需要同一个package依赖的冲突问题。
Go Vendor的弊端:无法控制依赖的版本;更新项目又可能出现依赖冲突,导致编译出错。
主要导致的原因是:依赖项目原码,不能很清晰的叙述依赖版本的概念 为了解决该问题,再次引入新的版本。
2.1.3 Go Module
通过go.mod 文件管理依赖包版本; 通过go get/go mod指令工具管理依赖包。
终极目标:定义版本规则和管理项目依赖关系
2.2 GoModule依赖管理方案
1、配置文件,描述依赖 go.mod
2、中心仓库管理依赖库 Proxy
3、本地工具 go get/mod
在一般的项目中,根据简单的依赖关系图可以得出结论:选择最低兼容版本。
依赖分发-回源 无法保证构建稳定性(增加/修改/删除软件版本); 无法保证依赖可用性(删除软件); 增加第三方压力(代码托管平台负载问题)。
采用Proxy可以保证代码依赖的稳定性和可靠性
依赖分发-变量 GOPROXY 类似于缓存的方式
3.测试
主要包括:回归测试、集成测试、单元测试
特点为:从上到下,覆盖率逐层变大,成本却逐层降低
模式为:输入->测试单元->输出->校对
在测试单元中,一般包括函数、模块等。通过校对,我们可以保证质量、提升效率。
在单元测试中,为了更加方便的查找代码的问题所在,我们可以通过单元测试的相关规则来进行约束,主要可以分为:
1、所有测试文件以_test.go结尾 func TestXxx(*testing.T) 初始化逻辑放到TestMain中 当在单元测试过程中,出现问题时,则需要进行相关的修复assert
2、另外一个需要注意的问题是:通过使用代码的测试覆盖率来衡量代码是否经过了足够的测试,以及项目的测试水准,项目是否达到了高水准测试等级。
通过代码:go test judgment_test.go judgment.go --cover可以得到测试的覆盖率。
注意:一般项目的覆盖率在50%~60%,较高覆盖率80%以上。 测试分支相对独立、全面覆盖; 测试单元粒度足够小,函数单一职责 单元测试需要进行外部依赖可以推出其稳定和幂等。
在写代码的过程中,需要对代码进行基准测试,主要目的为:优化代码,需要对当前代码分析;内置的测试框架提供了基准测试的能力。
4.项目实战
通过具体的实战小题目进行检测对上述知识的运用:
具体问题的描述:
社区话题页面:展示话题(标题,文字描述)和回帖列表;暂不考虑前端页面实现,仅仅实现一个本地web服务;话题和回帖数据用文件存储
具体分析:
步骤一:通过实战的小项目,从中抽离出两个具体的实体:Topic和Post;
步骤二:再根据两个实体的具体需求,通过大纲的形式进行列出,如:Topic包括:id、title、content、create_time; Post包括:id、topic_id、content、create_time 具体。
具体代码实现:
1、Repository-index
func initTopicIndexMap(filePath string) error {
open,err := os.Open(filePath + "topic")
if err ≠ nil {
return err
}
scanner := bufio.NewScanner(open)
topicTmpMap := make(map[int64]*Topic)
for scanner.Scan(){
text :=scanner.Text()
var topic Topic
if err :=json.Unmarshal([]byte(text), &topic); err ≠ nil{
return err
}
topicTmpMap[topic.Id] = &topic
}
topicIndexMap = topicTmpMap
return nil
}
2 、Repository-查询
type Topic struct {
Id int64 'json:"id"'
Title string 'json:"title"'
Content string 'json:"content"'
CreateTime int64 'json:"create_time"'
}
type TopicDao struct {
}
var (
topicDao *TopocDao
topicOnce sync.Once
)
func NewTopicDaoInstance() *TopicDao {
topicOnce.Do(
func() {
topicDao = &TopicDao{}
})
return topicDao
}
func (*TopicDao) QueryTopicById(id int64) *Topic {
return topicIndexMap[id]
}
3、Service
func (f *QueryPageInfoFlow) Do() (*PageInfo, error) {
if err := f.checkParam(); err ≠ nil {
return nil, err
}
if err := f.prepareInfo(); err ≠ nil {
return nil, err
}
if err :=f.packPageInfo(); err ≠ nil {
return nil, err
}
return f.pageInfo, nil
}
4、controller
type PageData struct {
Code int64 'json:"code"'
Msg string 'json:"msg"'
Data interface{} 'json:"data"'
}
func QueryPageInfo(topicIdStr string) *PageData {
topicId, err := strconv.ParseInt(topicIdstr, base: 10, bitSize: 64)
if err ≠ nil {
return &PageData{...}
}
pageInfo, err := service.QueryPageInfo(topicId)
if err ≠ nil {
return &PageData{...}
}
return &PageData{...}
}
5、Router
fun main() {
if err := Init(filePath:"./data/"); err ≠ nil {
os.Exil(code:-1)
}
r := gin.Default()
r.GET( relativePath:"/community/page/get/:id", func(c *gin.Context) {
topicId := c.Param(key: "id")
data := cotroller.QueryPageInfo(topicId)
c.JSON( code: 200, data)
})
err := r.Run()
if err ≠ nil {
return
}
}
总结
该篇文章主要从一些基础知识对Go语言进行了阐述,并通过具体的实战进行演示。同时在文章中将一些概念及重要注意事项进行标注,方便读者查看,并快速获得所要知识点。
引用
该文章主要是根据字节内部课程进行学习并总结。
-
Go 语言进阶与依赖管理 - 掘金 juejin.cn/course/byte…
-
Go 语言进阶与依赖管理 - 掘金 juejin.cn/course/byte…