go入门实践

0、和java一样,go也是跨平台,天生支持Unicode。但是go直接生成可执行文件,性能更高,内存占用少。但是又和java一样,go打出来的二进制包能够扫描到依赖的库,如果库有漏洞,安全扫描会被扫出来。

1、安装、环境配置及术语

从https://golang.google.cn/下载对应的版本。

使用命令行go env可以查看,如下:

[lightdb@lightdb-dev ~]$ go env
GO111MODULE=""   #设置是否打开go modules,auto/on/off三个取值,1.13开始默认,1.14开始推荐
GOARCH="amd64"
GOBIN=""    # go install最终拷贝到的目录,一般go程序打成tar.gz分发,所以关系不大,配置的话指向$GOPATH/bin
GOCACHE="/home/lightdb/.cache/go-build"
GOENV="/home/lightdb/.config/go/env"
GOEXE=""
GOEXPERIMENT=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/lightdb/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/lightdb/go"   # 项目的根目录,最重要的环境变量
GOPRIVATE=""
GOPROXY="https://goproxy.cn,direct"   # GOPROXY是用来设置go mod的代理,以英文逗号“,”分割,使Go在后续拉取模块版本时能够脱离传统的VCS方式从镜像站点快速拉取。它拥有一个默认:https://proxy.golang.org,direct,但proxy.golang.org在中国无法访问,所以建议使用goproxy.cn替代
GOROOT="/usr/lib/golang"    # golang的安装目录,跟JAVA_HOME一样
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/lib/golang/pkg/tool/linux_amd64"
GOVCS=""
GOVERSION="go1.20.6"
GCCGO="gccgo"
GOAMD64="v1"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
GOWORK=""
CGO_CFLAGS="-O2 -g"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-O2 -g"
CGO_FFLAGS="-O2 -g"
CGO_LDFLAGS="-O2 -g"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build261630818=/tmp/go-build -gno-record-gcc-switches"

hello world验证环境及熟悉目录

  [lightdb@lightdb-dev go]$ cd src/example/
  [lightdb@lightdb-dev example]$ go mod init

[lightdb@lightdb-dev go]$ go run src/example/first.go 
123 hello world 65 12.345
[lightdb@lightdb-dev go]$ pwd
/home/lightdb/go
[lightdb@lightdb-dev go]$ 
[lightdb@lightdb-dev bin]$ export | grep GO
declare -x GOBIN="//home/lightdb/go/bin"
declare -x GOPATH="/home/lightdb/go"
declare -x GOROOT="//home/lightdb/go/go"

 

 这样脚手架就可以运行了。

[lightdb@lightdb-dev src]$ cd go-web/
[lightdb@lightdb-dev go-web]$ go build ginweb.go   # go build也是个很大的范畴,可以参考https://blog.csdn.net/wan212000/article/details/124288970

相关的报错及原因和解决办法如下:

[lightdb@lightdb-dev go]$ go build
go: go.mod file not found in current directory or any parent directory; see 'go help modules'
[lightdb@lightdb-dev go]$ go mod init   # 理论上go mod init即可
go: cannot determine module path for source directory /home/lightdb/go (outside GOPATH, module path must be specified)

Example usage:
    'go mod init example.com/m' to initialize a v0 or v1 module
    'go mod init example.com/m/v2' to initialize a v2 module

Run 'go help mod init' for more information.
[lightdb@lightdb-dev go]$ go mod init example.com/m   # 可以提前预建目录,这样cd dir; go mod init即可,不用指定名称
go: creating new go.mod: module example.com/m
go: to add module requirements and sums:
    go mod tidy
[lightdb@lightdb-dev go]$ go build
$GOPATH/go.mod exists but should not    # 使用go module模块化机制后,go.mod需要放在具体的模块中而非在GOPATH目录下,如$GOPATH/src/example/go.mod,再运行即可
[lightdb@lightdb-dev go]$ go install
$GOPATH/go.mod exists but should not
[lightdb@lightdb-dev go]$ go run src/go-web/ginweb.go 
src/go-web/ginweb.go:7:2: no required module provides package github.com/gin-gonic/gin: go.mod file not found in current directory or any parent directory; see 'go help modules'
src/go-web/ginweb.go:5:2: package tool/controller is not in GOROOT (/usr/lib/golang/src/tool/controller)    # 需要在模块的根目录下打包和运行

package first is not in GOROOT (/usr/lib/golang/src/first)   # 在$GOPATH目录下执行  ln -s /usr/lib/golang go

1.1 主要文件

go.mod   Go Modules 的核心文件,包含module、require、replace 和 exclude 4部分。

go.sum   供 Go 命令在构建时判断依赖包是否合法

详见https://zhuanlan.zhihu.com/p/635696935,讲得很清楚了。

1.2 依赖关系

和java一样,go的依赖管理也经过了多次构造,最新为2019年的go mode。

模块是go管理依赖的基本单元,模块由多个package组成。go.mod 文件定义了模块的名称及其依赖包,通过导入路径和版本描述一个依赖。

[lightdb@lightdb-dev go]$ go help modules
Modules are how Go manages dependencies.

A module is a collection of packages that are released, versioned, and
distributed together. Modules may be downloaded directly from version control
repositories or from module proxy servers.

For a series of tutorials on modules, see
https://golang.org/doc/tutorial/create-module.

For a detailed reference on modules, see https://golang.org/ref/mod.

By default, the go command may download modules from https://proxy.golang.org.
It may authenticate modules using the checksum database at
https://sum.golang.org. Both services are operated by the Go team at Google.
The privacy policies for these services are available at
https://proxy.golang.org/privacy and https://sum.golang.org/privacy,
respectively.

The go command's download behavior may be configured using GOPROXY, GOSUMDB,
GOPRIVATE, and other environment variables. See 'go help environment'
and https://golang.org/ref/mod#private-module-privacy for more information.

 

2、IDE配置(vscode为例)及调试

  vscode支持远程开发和本地开发,attach/launch都一样,都支持本地和远程。所以除非要访问linux资源,否则直接windows开发也可以,这和c/c++语言需要依赖很多posix兼容库如pthread.h/uinstd.h/sys/select.h等具有很大的差别。

2.1 工程结构推荐

   一般来说,一个公司多个模块,下面的结构就行。

GOPATH  #环境变量配置
	bin
	pkg
    src
        com.xxx    顶级域名作为区分,便于和三方引入包保持一致
            lightdb
                main.go
                base
                service1
                service2
            unisql
                main.go
                base
                service1
                service2
            infra
                module1
                module2

   go以包为组织单位,虽然分目录,但包名和目录名不一定要相同(只是习惯上最后一层相同),而且包是单层如packageName而非com.xxx.packageName。一个文件夹下的所有文件必须具有相同的包名声明。

  import的是路径名而非包名(因为一个目录下的所有文件需要声明为一个包,所以import目录就相当于import包),引用的是GOPATH(跟CLASSPATH很像)开始的相对路径。需要注意的是如果目录里面有子目录,则不是同一个包,这个和java是一样的。

  如果多个目录下存在同名的package(这可是必不可免的),需要在import这些目录时为每个目录指定一个package的别名。

2.2、代码辅助、跳转及格式化配置

2.3、debug和attach配置

 

3、基本概念

每个 Go 程序都是由包构成的。程序从 main 包开始运行。

一个程序中可以有很多main包,这个main包之间无法相互调用,运行时通过go run xxx.go运行。

 按照约定,包名与导入路径的最后一个元素一致(但不强制)。例如,"math/rand" 包中的源码均以 package rand 语句开始。

go里面只有包和函数级别的变量,不存在对象级别。 也就是没有对象。

go里面,类型名在变量名之后,函数返回值在最后(这和java/c++相反),如下:

package main

import "fmt"

func add(x int, y int) int {   // 有点类似scala,和java/c都不一样
    return x + y
}

func main() {
    fmt.Println(add(42, 13))
}

函数可以返回多值,这个和主流编程语言(如java/c/c++)存在明显的差异,虽然c++在<utility>中支持返回pair带两个值

支持string、bool和无符号类型(和java/c的差别,java不支持无符号,c在17之前不支持布尔,一般模式typedef定义1和0来模拟),类型转换需要强制写,比如int到float,跟postgresql的强类型一样。而且转换是函数调用式的语法target_type(src_val)而不是java/c里面的(target_type) src_val。如下:

package main

import (
    "fmt"
    "math"
)

func main() {
    var x, y int = 3, 4
    var f float64 = math.Sqrt(float64(x*x + y*y))  // float64是类型转换
    var z uint = uint(f)
    fmt.Println(x, y, z)
}

for/if/else/switch更像是pl/sql的用法加上另外一半的c/c++/java,必须大括号、条件不必强制括号,switch不用带break(因为内置)。支持将某个语句延迟到函数调用(defer)返回后在执行(有点aop after的概念哈)

package main

import "fmt"

func main() {
    defer fmt.Println("world")

    fmt.Println("hello")
}
这与C++的析构函数十分相似,但是golang的defer只能保证在函数返回前执行,而C++的析构函数可以保证在当前scope退出前执行。

函数内外语句使用差异明显,var和:=

package main

import "fmt"

func main() {
    var i, j int = 1, 2
    k := 3   // 函数外的每个语句都必须以关键字开始(var, func 等等),因此 := 结构不能在函数外使用。
    c, python, java := true, false, "no!"

    fmt.Println(i, j, k, c, python, java)
}

包成员可见性,大写字母开头为默认导出,小写则不导出,因为大部分开发喜欢默认小写标识符,所以其实对于控制可访问性设计比java/c++可更妥些。

init()函数

和c/c++里面dlopen()加载so文件后自动执行_init()函数一样,init()函数在每次import一个包后自动执行。如果import相互依赖,也不会重复加载,每个包只会被初始化一次。

基本类型以外的高级数据类型

指针(虽然支持,但基本不推荐使用,除非为了效率),go中非简单类型也是传值、并且没有对象java的概念,没有c++的传引用,所以指针不可避免还是会用的多。

结构体(go不支持类,结构体上支持定义方法),结构体上的方法(按照go官方说法:方法只是个带接收者参数的函数)语法略微怪异,如下:

package main

import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

func main() {
    v := Vertex{3, 4}
    fmt.Println(v.Abs())
}

支持接口类型(也就是interface的概念)

type Abser interface {
    Abs() float64
}

可变长度数组(切片实现,也支持数组的数组)

map(原生支持)

支持函数作为参数和返回值(像函数指针或c++里面的function对象),此时函数支持闭包的概念(其实就是中间变量)。

上面两点更像c with object

协程(goroutine和channel):并发的核心是:不用使用共享内存来通信,而是用通信(channel)来共享内存。

5、go自动化回归单元测试

默认情况下,go的单元测试文件放在和源代码一样的目录下,运行go test会对所有的xxx_test.go进行测试,如下:

[lightdb@lightdb-dev go-web]$ go test
?       tool    [no test files]

这是一个比较大的主题,可以参考有赞paas的做法,https://cloud.tencent.com/developer/article/1684515,其中还结合了和sonar报告的集成

https://before80.github.io/go_docs_with_hugo/goBlog/2023/CodeCoverageForGoIntegrationTests/

https://go.dev/blog/integration-test-coverage  go 1.20在go build上增加了-cover选项,支持集成测试统计

对于集成测试,建议搭建一个专门的平台维护用例输出和输入,自动化回归和对比,开发、测试一体化,跟postgresql的make check/make checkworld一样。

java jacoco是一个可以单元测试和集成测试均覆盖的覆盖率工具,支持java agent模式统计。输出也很完整。

https://blog.csdn.net/d_chunyu/article/details/117136293

6、makefile不仅可用于c/c++,还可以用于go。也就是makefile可用于任何linux下的开发,跟语言无关。尤其是go涉及到部分代码使用c/c++开发时更是如此。

7、命令行程序设计

go提供了一个设计命令行程序的框架cobra cli(Kubernetes, Docker, Hugo,pgcenter均基于它)

8、go web开发之gin框架

9、go访问数据库之postgresql

10、模块分发

go build可以用来打包整个模块

go install将其安装到$GOBIN目录下

https://stackoverflow.com/questions/59741795/how-to-distribute-a-go-module-with-c-dependencies

11、三方库

  日志:logrus

  kafka:kafka-go ,https://blog.csdn.net/qq_30614345/article/details/131056586,https://zhuanlan.zhihu.com/p/431434480

  json:jsoniter

  orm:https://github.com/go-gorm/gorm

  sql解析:https://github.com/pingcap/parser,当然也可以用postgresql的c语言sql解析器(性能更强),pgpool-ii就是剥离的psotgresql解析器

12、docker中go get无法访问代理,如下:

[root@lightdb-dev pkg]# docker-compose up
[+] Building 31.4s (9/9) FINISHED                                                                                                                                                   docker:default
 => [helloweb internal] load .dockerignore                                                                                                                                                    0.0s
 => => transferring context: 2B                                                                                                                                                               0.0s
 => [helloweb internal] load build definition from helloweb.build                                                                                                                             0.0s
 => => transferring dockerfile: 439B                                                                                                                                                          0.0s
 => [helloweb internal] load metadata for docker.io/library/golang:1.20                                                                                                                      10.4s
 => [helloweb 1/5] FROM docker.io/library/golang:1.20@sha256:bfc60723228b88180b1e15872eb435cf7e6d8199eae9be77c8dfd8f8079343df                                                                 0.0s
 => [helloweb internal] load build context                                                                                                                                                    0.0s
 => => transferring context: 33B                                                                                                                                                              0.0s
 => CACHED [helloweb 2/5] WORKDIR /app                                                                                                                                                        0.0s
 => CACHED [helloweb 3/5] COPY helloweb.go /app                                                                                                                                               0.0s
 => CACHED [helloweb 4/5] RUN go mod init helloweb                                                                                                                                            0.0s
 => ERROR [helloweb 5/5] RUN go get github.com/go-redis/redis                                                                                                                                21.0s
------                                                                                                                                                                                             
 > [helloweb 5/5] RUN go get github.com/go-redis/redis:
20.99 go: module github.com/go-redis/redis: Get "https://proxy.golang.org/github.com/go-redis/redis/@v/list": dial tcp 142.251.43.17:443: connect: connection refused
------
failed to solve: process "/bin/sh -c go get github.com/go-redis/redis" did not complete successfully: exit code: 1

解决方法:docker.build中,使用RUN指定GOPROXY,如下:

# 导入依赖的Redis go module
RUN go mod init helloweb
RUN go env -w GO111MODULE=on
RUN go env -w GOPROXY=https://goproxy.cn,direct
RUN go get github.com/go-redis/redis

再运行docker compose up即可。

Golang 程序员开发效率神器汇总!

查看某个模块依赖的包清单

https://blog.csdn.net/aaqq123456654321/article/details/127113393

go性能分析

pgo,pprof

go与c/c++交互 cgo及环境变量

内存管理

gc

打印堆栈及性能

https://www.51cto.com/article/748837.html

posted @ 2024-01-14 22:55  zhjh256  阅读(30)  评论(0编辑  收藏  举报