Go语言入门-工程实践 | 青训营笔记

87 阅读5分钟

这是我参与「第五届青训营 」笔记创作活动的第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语言进行了阐述,并通过具体的实战进行演示。同时在文章中将一些概念及重要注意事项进行标注,方便读者查看,并快速获得所要知识点。

引用

该文章主要是根据字节内部课程进行学习并总结。