第一次接触 Golang 的时候,我就对其 Package 感到困惑。在很多语言中,都有类似命名空间(Namespace)的概念,开发者可以嵌套使用命名空间,让项目层次分明。 例如可以分为 Controller 层,Controller 层下面又可以分出 HttpController 和 RpcController,HttpController 下面又可分出 UserController,PostController 等类,类中又可以实现各种方法。
但是在 Golang 中,包似乎是平级的。尽管文件夹形式上可以将包放到父目录和子目录,但是实际上这些包还是平级的。只要你引入了这个包 mypkg,就可以直接使用 mypkg.name
的形式访问这个包下的其它公有符号。例如 net
包和 net/http
包。
为了解决这样的疑惑,探寻出合适的编程惯例,我阅读了很多文章。下面两篇是最推荐的:
其思想的核心是:越靠近根部的包,其依赖越少。
假设根部 Package 是 myapp,则属于 myapp 包的类型,都是不涉及其它包的业务类型,例如模型定义,模型的接口定义等。
返回指针还是值类型?
go - Pointers vs. values in parameters and return values - Stack Overflow
通常情况下,除非是你返回的东西需要修改,否则没必要用指针。
“首先,也是最重要的,该方法是否需要修改接收器?如果需要,接收器必须是一个指针。”
“第二是考虑效率。如果接收器很大,例如一个大结构,使用指针接收器会便宜得多。”
“接下来是一致性。如果类型的某些方法必须有指针接收者,其余的也应该如此,所以无论类型如何使用,方法集都是一致的”
Repo 层
Service 层
API 层
获取参数有两种方式。
传统方式:
message := c.PostForm("message")
nick := c.DefaultPostForm ("nick", "anonymous") // 此方法可以设置默认值
规范方式:
func likes(c *bm.Context) {
var args struct {
Sid int64 `form:"sid" validate:"min=1,required"`
Lids []int64 `form:"lids,split" validate:"min=1,max=50,dive,min=1"`
}
if err := c.Bind(&args); err != nil {
return
}
c.JSON(actSrv.Likes(c, args.Sid, args.Lids))
}
统一错误返回
我们还是学习 B 站,它重写了 JSON 方法:
// JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json".
func (c *Context) JSON(data interface{}, err error) {
code := http.StatusOK
c.Error = err
bcode := ecode.Cause(err)
// TODO app allow 5xx?
/*
if bcode.Code() == -500 {
code = http.StatusServiceUnavailable
}
*/
writeStatusCode(c.Writer, bcode.Code())
c.Render(code, render.JSON{
Code: bcode.Code(),
Message: bcode. (),
Data: data,
})
}
atomic.Value
是 sync/atomic
包中的类型,可以用于原子地读写(Load/Store)任何值,通过指令集的原子操作实现,所以性能比互斥锁(Mutex)好很多。但是不要用来存引用类型,因为不能保证引用类型并发安全。