警告
本文最后更新于 2023-09-14,文中内容可能已过时。
原文
当我们复杂一个对象时,这个对象可以是内建数据类型,数组,结构体,map……我们在复制结构体的时候,当我们需要比较两个结构体中的数据是否相同时要使用到反射 reflect.DeepEqual()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
import (
"fmt"
"reflect"
)
func main() {
v1 := data{}
v2 := data{}
fmt.Println("v1 == v2:",reflect.DeepEqual(v1,v2))
//prints: v1 == v2: true
m1 := map[string]string{"one": "a","two": "b"}
m2 := map[string]string{"two": "b", "one": "a"}
fmt.Println("m1 == m2:",reflect.DeepEqual(m1, m2))
//prints: m1 == m2: true
s1 := []int{1, 2, 3}
s2 := []int{1, 2, 3}
fmt.Println("s1 == s2:",reflect.DeepEqual(s1, s2))
//prints: s1 == s2: true
}
|
接口作为参数,调用方法,解藕业务类型(实例对象)和控制逻辑(方法)
这种编程模式在 Go 的标准库有很多的示例,最著名的就是 io.Read
和 ioutil.ReadAll
的玩法,其中 io.Read
是一个接口,你需要实现他的一个 Read(p []byte) (n int, err error)
接口方法,只要满足这个规模,就可以被 ioutil.ReadAll
这个方法所使用
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
|
type Country struct {
Name string
}
type City struct {
Name string
}
type Stringable interface {
ToString() string
}
func (c Country) ToString() string {
return "Country = " + c.Name
}
func (c City) ToString() string{
return "City = " + c.Name
}
func PrintStr(p Stringable) {
fmt.Println(p.ToString())
}
d1 := Country {"USA"}
d2 := City{"Los Angeles"}
PrintStr(d1)
PrintStr(d2)
|
var _ Shape = (*Square)(
nil)
声明一个 _
变量(没人用),其会把一个 nil
的空指针,从 Square
转成 Shape
。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
type Shape interface {
Sides() int
Area() int
}
type Square struct {
len int
}
func (s* Square) Sides() int {
return 4
}
func main() {
s := Square{len: 5}
fmt.Printf("%d\n",s.Sides())
}
|
在 Go 语言中,你一定要使用 time.Time
和 time.Duration
两个类型:
- 在命令行上,
flag
通过 time.ParseDuration
支持了 time.Duration
- Json 中的
encoding/json
中也可以把 time.Time
编码成 RFC 3339 的格式
- 数据库使用的
database/sql
也支持把 DATATIME
或 TIMESTAMP
类型转成 time.Time
- YAML 你可以使用
gopkg.in/yaml.v2
也支持 time.Time
、time.Duration
和 RFC 3339 格式
如果你要和第三方交互,实在没有办法,也请使用 RFC 3339 的格式。
最后,如果你要做全球化跨时区的应用,你一定要把所有服务器和时间全部使用 UTC 时间
下面是一个在编程方面和性能相关的提示。
- 如果需要把数字转字符串,使用
strconv.Itoa()
会比 fmt.Sprintf()
要快一倍左右
- 尽可能地避免把
String
转成 []Byte
。这个转换会导致性能下降。
- 如果在
for-loop
里对某个 slice
使用 append()
请先把 slice
的容量很扩充到位,这样可以避免内存重新分享以及系统自动按 2 的 N 次方幂进行扩展但又用不到,从而浪费内存。
- 使用
StringBuffer
或是 StringBuild
来拼接字符串,会比使用 +
或 +=
性能高三到四个数量级。
- 尽可能的使用并发的
go routine
,然后使用 sync.WaitGroup
来同步分片操作
- 避免在热代码中进行内存分配,这样会导致
gc
很忙。尽可能的使用 sync.Pool
来重用对象。
- 使用
lock-free
的操作,避免使用 mutex
,尽可能使用 sync/Atomic
包。
- 使用 I/O 缓冲,I/O 是个非常非常慢的操作,使用
bufio.NewWrite()
和 bufio.NewReader()
可以带来更高的性能。
- 对于在 for-loop 里的固定的正则表达式,一定要使用
regexp.Compile()
编译正则表达式。性能会得升两个数量级。
- 如果你需要更高性能的协议,你要考虑使用 protobuf 或 msgp 而不是 JSON,因为 JSON 的序列化和反序列化里使用了反射。
- 你在使用 map 的时候,使用整型的 key 会比字符串的要快,因为整型比较比字符串比较要快。
函数式编程进行错误处理
- 外部传入
err
进行判定执行
1
2
3
4
5
6
|
doFunc := func(err error,f func()error){
if err != nil {
return err
}
err = f()
}
|
- 增添结构体存放 err
1
2
3
4
5
6
7
8
9
10
|
type Reader struct {
r io.Reader
err error
}
func (r *Reader) read(data interface{}) {
if r.err == nil {
r.err = binary.Read(r.r, binary.BigEndian, data)
}
}
|
我们需要包装一下错误,而不是干巴巴地把err
给返回到上层,我们需要把一些执行的上下文加入。
- 可以将错误和信息封装之后返回
1
2
3
4
5
6
7
8
|
type authorizationError struct {
operation string
err error // original error
}
func (e *authorizationError) Error() string {
return fmt.Sprintf("authorization failed during %s: %v", e.operation, e.err)
}
|
更好的方式是通过一种标准的访问方法,这样,我们最好使用一个接口,比如 causer
接口中实现 Cause()
方法来暴露原始错误,以供进一步检查:
1
2
3
4
5
6
|
type causer interface {
Cause() error
}
func (e *authorizationError) Cause() error {
return e.err
}
|
- 第三方库
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import "github.com/pkg/errors"
//错误包装
if err != nil {
return errors.Wrap(err, "read failed")
}
// Cause 接口
switch err := errors.Cause(err).(type) {
case *MyError:
// handle specifically
default:
// unknown error
}
|
可配置的模块化加载方法
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
|
// 业务对象
type Server struct {
Addr string
Port int
Conf *Config
}
// 配置方法类型
type Option func(*Server)
// 配置方法
func Protocol(p string) Option {
return func(s *Server) {
s.Protocol = p
}
}
func Timeout(timeout time.Duration) Option {
return func(s *Server) {
s.Timeout = timeout
}
}
func MaxConns(maxconns int) Option {
return func(s *Server) {
s.MaxConns = maxconns
}
}
func TLS(tls *tls.Config) Option {
return func(s *Server) {
s.TLS = tls
}
}
|
初始化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
func NewServer(addr string, port int, options ...func(*Server)) (*Server, error) {
srv := Server{
Addr: addr,
Port: port,
Protocol: "tcp",
Timeout: 30 * time.Second,
MaxConns: 1000,
TLS: nil,
}
for _, option := range options {
option(&srv)
}
//...
return &srv, nil
}
s1, _ := NewServer("localhost", 1024)
s2, _ := NewServer("localhost", 2048, Protocol("udp"))
s3, _ := NewServer("0.0.0.0", 8080, Timeout(300*time.Second), MaxConns(1000))
|
- 直觉式的编程
- 高度的可配置化
- 很容易维护和扩展
- 自文档
- 对于新来的人很容易上手
- 没有什么令人困惑的事(是 nil 还是空)
1
2
3
4
5
6
7
|
func MapStrToStr(arr []string, fn func(s string) string) []string {
var newArray = []string{}
for _, it := range arr {
newArray = append(newArray, fn(it))
}
return newArray
}
|
1
2
3
4
5
6
7
|
func Reduce(arr []string, fn func(s string) int) int {
sum := 0
for _, it := range arr {
sum += fn(it)
}
return sum
}
|
1
2
3
4
5
6
7
8
9
|
func Filter(arr []int, fn func(n int) bool) []int {
var newArray = []int{}
for _, it := range arr {
if fn(it) {
newArray = append(newArray, it)
}
}
return newArray
}
|