go 类型别名

1. 背景

类型别名(type aliases)原本是要在 Go 1.8 发布时推出的。但是由于一些争议和实现上的问题,Go 团队把它推迟到了 Go 1.9。

2.目的

这一特性其实是为开发者们的代码库重构准备的。代码重构是对代码的重新组织,以及这种重组与代码包之间的关系的重新思考和修改过程。代码重构的原因可能是代码的拆分、命名优化或者依赖关系清理,等等。但是不论原因是什么,我们的目的都是让代码变得更清楚、更易于使用,以及更容易扩展。

也许你有过这样的经历:在进行(范围比较广的)代码重构的过程中,原有代码已经改变。甚至,当你完成重构并想把代码合并回去的时候,原有代码已经面目全非。合并代码有时候比重构代码更加困难。

 

这就引出了一个比较先进的重构方法——渐进式代码重构。也就是说,同时计划重构的终极目标和阶段性目标。在达到完备状态之前,设置几个易实施并且可用的中间状态。纵观 Go 语言的代码更新和版本升级过程,我们不难发现 Go 团队对这种方法的合理运用。

然而,在渐进式代码重构的过程中我们也可能会遇到问题。例如,当你想把一个允许包外代码访问的类型迁移到另外一个包中时,该怎么做?简单来说,应该分为三步:

1)在目的包中声明一个名称和功能都相同的新类型。

2)检查所有可能引用原类型的地方,并把那些引用都指向新类型。

3)删除原包中的那个类型。如果这个类型只被由你掌控的程序中引用,那么这种方式当然没问题。

但是,如果该类型所在的是一个已被广泛依赖的底层代码包,那我劝你还是不要执行第 3 步了。

除非你冒着被口水淹死的风险发布程序不兼容声明。可是不执行第 3 步就等于任由废弃代码在程序中蔓延。

这也是很恶心的一件事。

 

讲了这么多,我要说的重点是:Go 的类型别名就是为我们解决这类两难的问题的。在类型别名真正问世之前,Go 团队自己在做上述重构时都不得不用上一些特殊的、脆弱的手段。

 

3.eg

 

若使用类型别名的话,应该怎么做?

如果我们要把 oldpkg.OldType 类型迁移到 newpkg 代码包中并将其改名为 NewType,那么最少用 2 步就可以完成:

1)声明 newpkg.NewType 类型。

2)把 oldpkg.OldType 类型的声明改为:

 

package oldpkg
type OldType = newpkg.NewType

 

新的 oldpkg.OldType 类型声明可以使它与 newpkg.NewType 完全等价,并可以实现互换。也就是说,如果一个函数有一个 oldpkg.OldType 类型的参数声明,那么该函数就可以接受一个 newpkg.NewType 类型的参数值。

 

当然,为了不留废弃代码你仍然需要在某个时间删除掉 oldpkg.OldType。

但是类型别名给了你和其他使用 oldpkg.OldType 的开发者一个可以游刃有余的必要条件。

你们为重构代码而设置的中间状态都可以轻松达成。

举个例子,你在按照上述 2 步迁移完类型之后,可以马上把自己写的某个函数的声明由

 

package handler
import oldpkg
func HandleXXX(obj oldpkg.OldType){}

  改为

package handler
import newpkg
func HandleXXX(obj newpkg.NewType){}

 

tip 思考:如何让 oldpkg.OldType 与 newpkg.NewType 相同

 

然而,在调用该函数的(其他人写的)程序那里,却可以不用做任何修改(虽然最后可能要修改)。

代码 handler.HandleXXX(oldTypeVar) 仍然有效。其中的 oldTypeVar 是 oldpkg.OldType 类型的值。这就相当于可以让各方按照自己的节奏重构代码了。

 

再后面的情景可能是:你在你的代码包还是 1.0 版本的时候就发布声明说 oldpkg.OldType 类型即将在 2.0 版本中删除。而当你在开发 2.0 版本时,心安理得地删掉了 oldpkg.OldType。

 

实际上,类型别名只是 Go 团队为了让我们顺利实施大规模 Go 软件工程的举措之一。

他们一直在致力于帮助开发者们高效地编写 Go 代码和利用 Go 代码包,并避免因不经意的重复造轮子而导致的代码膨胀。

如果你真正用过 Go 语言,那么也一定能体会到它在代码依赖方面展现出的规范性和严谨性。

 

posted @ 2023-03-18 14:03  易先讯  阅读(48)  评论(0编辑  收藏  举报