Go 中为什么建议使用 NewXxx 这样的构造函数

分享于2021年11月17日 go 使用 建议
Go 语言并非完全的面向对象语言,只是有部分面向对象特性。其中,没有实际意义的构造函数,但对类似构造函数有自己的一些约定成俗的规则。本文讲解为什么在 Go 中,建议你尽量使用专门的构造函数。01 Go 和 Struct 初始化的背景Go 有一个特殊的特性。它不同于其他语言,Go 的构造函数也是独一无二的。我们通过比较其他语言如何构造对象来建立基准。PHPx = $x;        $this->y = $y;    }}$p1 = new Point(4, 5);Java 重载(多个构造函数)public class MyClass {    private int number = 0;    public MyClass() {    }    public MyClass(int theNumber) {        this.number = theNumber;    }}Soliditypragma solidity ^0.5.0;contract Base { uint data; constructor(uint _data) public { data = _data; }}Go 呢?Go 语言不强制构造函数(没有语法层面的构造函数)。它经常通过 “复合字面量” 来实例化结构体。复合字面量为结构体、数组、切片和 map 构造值,并在每次计算(evaluated)它们时创建一个新值。它们由字面值类型后跟花括号绑定的元素列表组成。相应的键可以可选地位于每个元素之前。复合字面量示例:type Point3D struct { x, y, z float64 }type Line struct { p, q Point3D }可以这么初始化:origin := Point3D{}                            // zero value for Point3Dline := Line{origin, Point3D{y: -4, z: 12.3}}  // zero value for line.q.x“作为一种限制情况,如果复合字面量根本不包含任何字段,它会为该类型创建一个零值。表达式new(Point3D)和&Point3D{}是等效的。”你可以将其记住为,如果未提供参数,则各字段使用其类型的默认值。Type + Curly Brackets.Go 的设计考虑了简单性和无样板代码,复合字面量正是如此,但它们**不必要地难以维护。为什么?让我用复合字面量解释我的日常问题以及我喜欢如何解决它们。02 创建构造函数保持代码的可维护性我的构造函数规则:当创建一个新的结构体,比如 Transaction(Tx),我马上创建一个专用的构造器函数:NewTx()。type Tx struct { From     common.Address `json:\"from\"` To       common.Address `json:\"to\"` Gas      uint           `json:\"gas\"` GasPrice uint           `json:\"gasPrice\"` Value    uint           `json:\"value\"` Nonce    uint           `json:\"nonce\"` Data     string         `json:\"data\"` Time     uint64         `json:\"time\"`}func NewBaseTx(from, to common.Address, value uint, nonce uint, data string) Tx { return Tx{from, to, TxGas, TxGasPriceDefault, value, nonce, data, uint64(time.Now().Unix())}}这么做,我认为有 4 点原因,相信对你会有帮助。原因 1 - 有些没有合适的默认值没有构造函数假设我没有创建专用的构造函数 NewBaseTx()。如果我必须向 Tx 结构添加另一个属性MaxGasPrice,我将不得不修改数十/数百个文件(取决于代码库大小/实现)。我这里没有夸大其词。这是一个非常现实的估计,因为我已经在各种项目中遇到过几次这样的情况。too few constructor values字段:值构造函数但是,如果我将构造函数元素标记为“显式 字段:值 对,初始化程序可以按任何顺序出现,缺少的当作零值”,重构可能很快以生产错误告终,因为编译器不会指出我忘记传递新值的所有地方;所以这是更糟糕的方法。MaxGasPrice默认情况下会意外地为 0,从而使结构变为无效状态。甚至不要尝试使用可选的 setter 来修复它。field:value zero通常,我在field:value构造函数中看不到任何值,因为任何好的 IDE 都会像我之前的屏幕截图一样以图形方式向你显示名称。你有什么经验?同意还是您有不同的看法?专用构造函数幸运的是,我可以避免所有这些混乱,并为我的队友避免大量 PR,因为我可以控制结构的创建。我把这个职责封装成一个单一的功能。我只需要改变一个地方。constructor values每个结构都值得拥有它的构造函数!你未来的自己会在下一次重构会议上感谢你 :)客观地说,有些结构体的默认值很特别,比如我经常使用的 sync.RWMutex。type Mutex struct { state int32 sema  uint32}type RWMutex struct { w           Mutex  // held if there are pending writers writerSem   uint32 // semaphore for writers to wait for completing readers readerSem   uint32 // semaphore for readers to wait for completing writers readerCount int32  // number of pending readers readerWait  int32  // number of departing readers}rwm := sync.RWMutex{}rwm.Lock()十分优雅。但我写 Go 已经 4 年了,我无法确定我单独使用默认值设计的单个结构。但是,我可以在过去 6 个月内链接到 3 个 PR,这些 PR 由于缺少专用构造函数而导致生产错误。提供了错误的默认值,因为 Go 编译器无法挑到它;从编译器的角度来看,该值不会丢失;它认为你没有通过它,因为你希望编译器使用默认值。原因 2 - 你的代码可能每周更改一次,而 RWMutex 不会Mutex 源源的最后一次提交是在 2019 年 11 月。现在是 2021 年 11 月。last commit mutex不知道你是什么情况,但我项目中的最新提交是 3 小时前~。如果你在项目中作为一个团队来优化灵活性和重构,那将是最好的。专用的构造函数可以帮助解决这个问题。原因 3 - 导出的结构体和公共库公开的结构体和库对变化更加敏感。上个月,由于缺少构造函数,我的代码出现了一个 bug。。。一个Config结构体是用 field:value 语法 初始化的,我添加了一个不会有“合理默认值”的新字段:Config{ IsObserverEnabled: func() bool {  return false }, IsFilteringEnabled: func() bool {  return false },}我审查了整个代码库,调整了所有用法,更新了测试,然后发布新版本。似乎一起都很正常。但当我将库导入到 3 个使用它的微服务时,我忽略了一个用法,并且错误的默认值潜入并弄乱了服务部署。如果只有一个专用的NewConfig()构造函数,编译器会在导入库后抛出错误。我也不必更改 3 个微服务,只需更改NewConfig()库中的一个函数即可!原因 4 - 打破单一职责原则 (SRP)似乎是因为缺少样板,当开发人员创建专用构造函数时,他们会用不属于那里的额外逻辑来劫持它,比如 Goroutines, disk 操作, DB 链接等。func NewDatabase(cfg Config) (*DB, error) { ctx, cancel := context.WithCancel(context.Background()) db := &DB{  // stuff } go db.connect(ctx) return db}污染的构造函数使得维护、测试和使用变得具有挑战性。A constructor should only create a new instance of the object就是这样。几十年来一直如此,这一原则对我们很有帮助。当我在某个库上调用 NewDatabase(cfg) 时,我最不希望看到的是在幕后的 goroutine 中启动数据库网络连接。你无法对其进行测试,甚至无法在数据库连接之前初始化结构体;这只是一种可怕的做法。此逻辑属于专用Connect()函数,而不是构造函数。两种不同的职责。以上就是我使用 Go 和构造函数的经验,希望对你有帮助!原文链接:https://web3.coach/golang-why-you-should-use-constructors推荐阅读mutex 出问题怎么办?强大的 goroutine 诊断工具福利我为大家整理了一份从入门到进阶的Go学习资料礼包,包含学习建议:入门看什么,进阶看什么。关注公众号 「polarisxu」,回复 ebook 获取;还可以回复「进群」,和数万 Gopher 交流学习。