Golang Code Review

输入校验

\

validator

GitHub - go-playground/validator: :100:Go Struct and Field validation, including Cross Field, Cross Struct, Map, Slice and Array diving

常见使用

// bad code

if len(ids) == 0 {

    return nil, errors.New("ids is empty")

}

if objectType != "question" && objectType != "answer" && objectType != "article" && objectType != "zvideo" && objectType != "pin" {

    return nil,  errors.New("content objectType is invalid")

}

// except

type ContentMaaSSchema struct {

    Ids        []int64 `json:"ids" bind:"required"`

    ObjectType string  `json:"object_type" bind:"required,ascii,one-of=question answer article zvideo pin"`

}

var input ContentMaaSSchema

if err := defaultValidator.ValidateStruct(input); err != nil {

    HandleError(w, err)

    return

}

操作符说明

标记

标记说明

,多操作符分割
|或操作
-跳过字段验证

常用标记

 点击此处展开...

标记

标记说明

required必填Field 或 Struct validate:"required"
omitempty空时忽略Field 或 Struct validate:"omitempty"
len长度Field validate:"len=0"
eq等于Field validate:"eq=0"
gt大于Field validate:"gt=0"
gte大于等于Field validate:"gte=0"
lt小于Field validate:"lt=0"
lte小于等于Field validate:"lte=0"
eqfield同一结构体字段相等Field validate:"eqfield=Field2"
nefield同一结构体字段不相等Field validate:"nefield=Field2"
gtfield大于同一结构体字段Field validate:"gtfield=Field2"
gtefield大于等于同一结构体字段Field validate:"gtefield=Field2"
ltfield小于同一结构体字段Field validate:"ltfield=Field2"
ltefield小于等于同一结构体字段Field validate:"ltefield=Field2"
eqcsfield跨不同结构体字段相等Struct1.Field validate:"eqcsfield=Struct2.Field2"
necsfield跨不同结构体字段不相等Struct1.Field validate:"necsfield=Struct2.Field2"
gtcsfield大于跨不同结构体字段Struct1.Field validate:"gtcsfield=Struct2.Field2"
gtecsfield大于等于跨不同结构体字段Struct1.Field validate:"gtecsfield=Struct2.Field2"
ltcsfield小于跨不同结构体字段Struct1.Field validate:"ltcsfield=Struct2.Field2"
ltecsfield小于等于跨不同结构体字段Struct1.Field validate:"ltecsfield=Struct2.Field2"
min最大值Field validate:"min=1"
max最小值Field validate:"max=2"
structonly仅验证结构体,不验证任何结构体字段Struct validate:"structonly"
nostructlevel不运行任何结构级别的验证Struct validate:"nostructlevel"
dive向下延伸验证,多层向下需要多个dive标记[][]string validate:"gt=0,dive,len=1,dive,required"
dive Keys & EndKeys与dive同时使用,用于对map对象的键的和值的验证,keys为键,endkeys为值map[string]string validate:"gt=0,dive,keys,eq=1\|eq=2,endkeys,required"
required_with其他字段其中一个不为空且当前字段不为空Field validate:"required_with=Field1 Field2"
required_with_all其他所有字段不为空且当前字段不为空Field validate:"required_with_all=Field1 Field2"
required_without其他字段其中一个为空且当前字段不为空Field `validate:“required_without=Field1 Field2”
required_without_all其他所有字段为空且当前字段不为空Field validate:"required_without_all=Field1 Field2"
isdefault是默认值Field validate:"isdefault=0"
oneof其中之一Field validate:"oneof=5 7 9"
containsfield字段包含另一个字段Field validate:"containsfield=Field2"
excludesfield字段不包含另一个字段Field validate:"excludesfield=Field2"
unique是否唯一,通常用于切片或结构体Field validate:"unique"
alphanum字符串值是否只包含 ASCII 字母数字字符Field validate:"alphanum"
alphaunicode字符串值是否只包含 unicode 字符Field validate:"alphaunicode"
alphanumunicode字符串值是否只包含 unicode 字母数字字符Field validate:"alphanumunicode"
numeric字符串值是否包含基本的数值Field validate:"numeric"
hexadecimal字符串值是否包含有效的十六进制Field validate:"hexadecimal"
hexcolor字符串值是否包含有效的十六进制颜色Field validate:"hexcolor"
lowercase符串值是否只包含小写字符Field validate:"lowercase"
uppercase符串值是否只包含大写字符Field validate:"uppercase"
email字符串值包含一个有效的电子邮件Field validate:"email"
json字符串值是否为有效的 JSONField validate:"json"
file符串值是否包含有效的文件路径,以及该文件是否存在于计算机上Field validate:"file"
url符串值是否包含有效的 urlField validate:"url"
uri符串值是否包含有效的 uriField validate:"uri"
base64字符串值是否包含有效的 base64值Field validate:"base64"
contains字符串值包含子字符串值Field validate:"contains=@"
containsany字符串值包含子字符串值中的任何字符Field validate:"containsany=abc"
containsrune字符串值包含提供的特殊符号值Field validate:"containsrune=☢"
excludes字符串值不包含子字符串值Field validate:"excludes=@"
excludesall字符串值不包含任何子字符串值Field validate:"excludesall=abc"
excludesrune字符串值不包含提供的特殊符号值Field validate:"containsrune=☢"
startswith字符串以提供的字符串值开始Field validate:"startswith=abc"
endswith字符串以提供的字符串值结束Field validate:"endswith=abc"
ip字符串值是否包含有效的 IP 地址Field validate:"ip"
ipv4字符串值是否包含有效的 ipv4地址Field validate:"ipv4"
datetime字符串值是否包含有效的 日期Field validate:"datetime"

并发场景

\

Goroutine

1、sync.WaitGroup

defer func() {

    if re := recover(); re != nil {

        log.Errorf(s.ctx, "panic panic, err: %+v", re)

    }

    wg.Done() // ***

}()

resChan := make(chan interface{}, 3)

wg.Add(3)

go queryZRender(&resChan, objectIDs)

go queryTagCore(&resChan, objectIDs)

go queryZIndex(&resChan, objectIDs, indexesTypeContent, timelinessPT)

wg.Wait()

close(resChan)

2、future

// 设计

package future

import (
	"context"
	"errors"
	"fmt"

	pkgErrors "github.com/pkg/errors"
)

type Future struct {
	ch  chan struct{}
	fn  Func
	err error
}

type Func func() error

func New(fn Func) *Future {
	f := &Future{
		ch: make(chan struct{}),
		fn: fn,
	}
	f.start()
	return f
}

func (f *Future) start() {
	go func() {
		defer func() {
			if rval := recover(); rval != nil {
				if err, ok := rval.(error); ok {
					f.err = pkgErrors.WithStack(err)
				} else {
					rvalStr := fmt.Sprint(rval)
					f.err = pkgErrors.WithStack(errors.New(rvalStr))
				}
			}

			close(f.ch)
		}()

		// 执行结果
		f.err = f.fn()
		return
	}()
}

func (f *Future) Get(ctx context.Context) error {
	select {
	case <-ctx.Done():
		return ctx.Err()
	case <-f.ch:
		return f.err
	}
}

func (f *Future) Done() bool {
	select {
	case <-f.ch:
		return true
	default:
		return false
	}
}

// 使用

futures := make([]*future.Future, 0)

futures = append(futures, future.New(func() error {

    claims, e := c.creatorClaimDao.BatchGet(ctx, memberIDs)

    for _, claim := range claims {

        claimMap[claim.MemberID] = claim

    }

    return e

}))

futures = append(futures, future.New(func() error {

    users, _ := c.userDao.BatchGet(ctx, userIDs)

    for _, user := range users {

        userMap[user.UserID] = user

    }

    return err

}))

futures = append(futures, future.New(func() error {

    memberMap = c.memberRpc.MBatchGetMemberByID(ctx, memberIDs)

    return nil

}))

for _, fu := range futures {

    e := fu.Get(ctx)

    if e != nil {

        log.Error(ctx, e)

    }

}

一些常见并发编程错误

golang中常见的并发bug

闭包使用

for 循环里面使用 Goroutine

for obj := range objs {

    go func() {

        do(obj)

    }()

}

for obj := range objs {

    go func(obj ObjType) {

        do(obj)

    }(obj)

}

第一种是直接在闭包里面使用 obj,这个时候其实 obj 是一个引用,goroutine 的执行时间是不确定的,可能等你进行第 n 遍循环的时候,n - x 轮的 go 才开始执行,这个时候获取到的 obj 的值,已经不再是你希望的那个 obj 了

Gorm

  • gorm.io/gorm
    • GORM 中文文档
    • 代码模块化
    • Context,批量插入,预编译模式,DryRun 模式,Join 预加载,Find To Map,Create From Map,FindInBatches 支持
    • 支持嵌套事务,SavePoint,Rollback To SavePoint
    • 全新的 Hook API:带插件的统一接口
    • 统一命名策略:表名、字段名、连接表名、外键、检查器、索引名称规则
    • 更好的自定义类型支持(例如: JSON)
  • database/sql 在处理字段类型转换的时候抛了异常,解决办法:
    • 基于SQL库的查询需要注意结果字段为NULL的情况,对应结构体字段需要声明为指针类型
  • gorm.io/gorm de 处理办法:
  •  点击此处展开...

    // assign stmt.ReflectValue

    if stmt.Dest != nil {

        stmt.ReflectValue = reflect.ValueOf(stmt.Dest)

        for stmt.ReflectValue.Kind() == reflect.Ptr {

            if stmt.ReflectValue.IsNil() && stmt.ReflectValue.CanAddr() {

                stmt.ReflectValue.Set(reflect.New(stmt.ReflectValue.Type().Elem()))

            }

            stmt.ReflectValue = stmt.ReflectValue.Elem()

        }

        if !stmt.ReflectValue.IsValid() {

            db.AddError(ErrInvalidValue)

        }

    }

格式转换

格式化规范

  • gofmt
    • 代码风格检查:保存时自动格式化代码
  • goimports
    • 保存时自动导入处理包
    • 导入多种类型的包,建议以「标准库」>「依赖库」>「本地包」的顺序排序,使用 goimports -w -local git.in.xxx.com/user/project . 会将 import 自动排版

       点击此处展开...

      import (

         // builtin packages

         "context"

        

         // other packages

         "git.in.zhihu.com/go/box/mysql"

         "git.in.zhihu.com/zhihu/demo/pkg/dao"

      )

  • GolangCI-Lint

    • 静态代码检查:集成常用的代码检查工具,并提供统一的配置入口和输出格式

       点击此处展开...

      Makefile

      lint:

         $(ENV) $(GOPATH)/bin/golangci-lint run --deadline=5m ./pkg/...

         $(ENV) $(GOPATH)/bin/golangci-lint run --deadline=5m ./cmd/...

        

      test: lint

         go test -coverprofile=coverage.out ./pkg/...

      joker.yml

      test:

        unittest:

          - make test

    • godoc:文档注释

其他

  • 错误包装:和第三方(RPC、MySQL...)交互返回错误时建议通过 "pkg/errors".Wrap 或者 "pkg/errors".Wrapf 来添加上下文信息,而不是将错误直接透传至上层

  • 禁用 panic:在生产环境中运行的代码必须避免出现 panic,panic/recover 不是错误处理策略
  • 指定容器容量:向make()提供容量提示会在初始化时尝试调整map的大小,这将减少在将元素添加到map时为map重新分配内存。
    • 与maps不同,slice capacity不是一个提示:编译器将为提供给make()的slice的容量分配足够的内存
  • 建议增加在函数声明中如果需要传入 context,context 需要作为第一个参数传入
  • nil 是一个有效的 slice
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值