大佬们, golang下一个包内文件和func太多,使用struct封装成PHP那种加构造函数的,又怕影响性能.

题目描述

golang下一个包内文件太多, func太多,用的时候不方便区分,
使用struct封装成PHP那种,再加加构造函数的,又怕影响性能.

题目来源及自己的思路

例如util包下有这么几个文件,以前的时候我会把不同func先归类到不同的文件,然后将文件单独放一个独立目录下作为一个包例如下面的math,common等都会单独放一个包里面.

像这样

util
├── math
    └── math.go
├── common
    └── common.go
├── cookie
    └── cookie.go
├── encrypt
    └── encrypt.go
├── fileutil
    └── fileutil.go
├── json
    └── json.go

但是这样感觉很麻烦,而且有的时候一个包下面难免会有多个文件,没法完全避免同一个包下很多个文件.

现在我直接把util的所有文件放一个目录下每个文件中使用struct封装起来,再搞一个构造函数,做分类.用的时候直接调用构造函数

但是这样感觉又会导致指针逃逸很影响GC性能. 尴尬啊

util
├── math.go
├── common.go
├── cookie.go
├── encrypt.go
├── fileutil
├── json.go

相关代码

例如下面的model, 因为数据库连接一般都放在同一个目录下,根据表名的不同,写到不同的文件.
如果不用struct封装起来,很多func会重名,用的时候很尴尬.封装起来的话,又怕影响性能.

package model

type UserModel struct {
}

func NewUserModel() *UserModel {
    return &UserModel{} //出现指针逃逸
}

func (receiver UserModel) AddNew(user entity.User) (int64, error) {

    if err := core.DB.Create(&user).Error; err != nil {
        return 0, err
    }
    return user.ID, nil
}

func (receiver UserModel) GetInfoByMobile(mobile string) (t entity.User, err error) {

    err = core.DB.Where("mobile=?", mobile).First(&t).Error
    return
}

你期待的结果是什么?实际看到的错误信息又是什么?

求大佬们指点,包下面有多个文件和方法时,应该怎么做.

阅读 2.6k
2 个回答

切忌盲目优化/过早优化,先分析瓶颈,再考虑解决。


对于工具函数一般就是聚合到一个包里,如果工具函数很多,再按用途分成多个子包,和你题中描述的一样:

util
├── math
    └── math.go
├── common
    └── common.go
├── cookie
    └── cookie.go
├── encrypt
    └── encrypt.go
├── fileutil
    └── fileutil.go
├── json
    └── json.go

但是你说:

但是这样感觉很麻烦,而且有的时候一个包下面难免会有多个文件,没法完全避免同一个包下很多个文件.

实话说我不能理解 很麻烦没法完全避免同一个包下很多个文件

首先关于麻烦,工具包下要不要再分子包应该是经过权衡、认为一个包里存放所有工具函数已经不可接受,才分包管理,这是在难维护用起来/写起来简单之间权衡。你说的麻烦可以再具体放到实际场景里去权衡,值不值得用这个“麻烦”去交换分包带来的好处。

其次没法避免很多个文件。go不是Java,也不是其他传统的“OOP”语言,从其他语言转go的话最好不要把其他语言的项目结构直接搬到go里,像是Java一个文件一个类,硬搬到go里一个struct一个go文件就有点迷惑了。

为什么要避免多个文件呢?数量真的很多,职责混乱,可以再分子包。数量很多但职责明确清晰,一个包里放多个go文件也不会有问题,都应该从实际出发。

项目目录结构组织上来说只要能满足几个点我觉得就完全ok。

  • 易维护。管理好复杂度,不要做多余的抽象和设计模式,拒绝过度设计。
  • 易使用。同类型的函数、结构有一定的惯用法,不要给使用者“惊喜”。同样,拒绝过度设计,不要为了统一而统一,做好权衡。
  • 可复用。想清楚是不是真的需要复用,需要在什么范围里复用,避免提取出来打算复用的代码没用到、不能用、不好用的情况。

具体到你的用户模型的场景里,我个人看法是:

  1. 消除不必要的抽象。如果只是因为重名而封装一个struct当类,那建议是在model下再分子包。
  2. 先pprof分析确定性能瓶颈再优化,不要自己猜。

关于优化,不是不建议优化,主要是:

一要相信编译器的优化能力;

二盲目优化的时间和脑力完全可以做更有价值的性能分析、调优或功能。web开发程序本身执行性能很少成为瓶颈,如果你们有做分析的话会发现绝大部分耗时都在数据查询和网络传输上。

像是你担心的Model结构逃逸,造成大量小对象gc的问题,go虽然有对象池可以用,但显然会增加你代码的复杂度和拖累可维护性。这又是一个tradeoff。考虑是不是Model结构的频繁创建导致的大量gc或者长gc?给Model加对象池能解决多少gc问题?后续项目维护中,新人能不能看懂用对象池的意图,会不会改坏,加上对象池之后接口用法是不是要变?


写太多了,不一定都对,可能有点掺杂了个人情绪发泄,见谅。

我自己总结下吧,省得抬杠:

  1. 别担心逃逸问题。
  2. 别因小失大,过度设计。你题中的util,我觉得分子包的模式就挺好了。而Model又要看你打算怎么做,是写成DAO还是怎么样,我没法给个绝对的说法。
  3. 不管意见怎么样,记住没有银弹。最终落地还是要根据你的实际场景权衡。

这你封装成struct造成的性能损耗根本不需要在意,你的系统根本还没到压榨语言性能到这个地步

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏