3 回答
TA贡献1886条经验 获得超2个赞
由于A是不变的,因此这非常适合函数,而不是字段。
type room struct {
L int
W int
}
func (r *room) area() int {
return r.L * r.W
}
TA贡献1829条经验 获得超13个赞
如果您想将 A 保留为字段,您可以选择在构造函数中执行计算。
type room struct {
L int
W int
A int
}
func newRoom(length, width, int) room {
return room{
L: length,
W: width,
A: length * width,
}
}
TA贡献1796条经验 获得超7个赞
如果您考虑一下您的目标,您会发现基本上您希望“不添加不必要的代码”实际上是不手动编写任何代码,而不是不执行任何代码:当然,如果类型定义
type room struct {
L int
W int
A int = room.L*room.H
}
在 Go 中是可能的,这意味着 Go 编译器会做出安排,而不是像这样的任何代码
var r room
r.L = 42
以隐式变异的方式编译r.A。
换句话说,编译器必须确保对程序中任何类型的变量的任何L一个或多个字段的任何修改也会执行计算并更新每个此类变量的字段。WroomA
这带来了几个问题:
如果你的公式比较棘手,比如,怎么办
A int = room.L/room.W
?首先,考虑到类型 0 值的随意 Go 规则
int
,无辜的声明var r room
会立即使程序崩溃,因为编译器插入的代码执行整数除以零以强制讨论不变量。其次,即使我们发明了一个不计算公式的可疑规则(在 Go 中,这也是初始化),问题仍然存在:在以下场景中会发生什么?
var r room r.L = 42
正如您所看到的,即使编译器不会使程序在第一行崩溃,它也必须在第二行进行安排。
当然,我们可以添加另一个有问题的规则来回避问题:要么以某种方式将每个字段“标记”为“显式设置”,要么要求用户为此类“武装”有“公式”的类型提供显式“构造函数”。这两种解决方案都有其各自的缺点:跟踪写入字段访问会带来性能成本(某些字段现在有一个隐藏标志,会占用空间,并且每次访问此类字段都会花费额外的 CPU 计数),而使用构造函数又成为基石原则之一Go 设计的核心理念:尽可能少地使用魔法。
该公式创建隐藏写入。
直到你开始为它所擅长的任务编写“更硬核”的 Go 程序(具有大量同时工作的 goroutine 的高度并发代码)时,这一点可能并不明显,但是当你这样做时,你不得不考虑共享状态及其实现方式突变,并因此影响这些突变同步以保持程序正确的方式。
因此,假设我们使用互斥锁来保护对
W
或 的访问;鉴于互斥操作是显式的(即程序员显式编码锁定/解锁操作),L
编译器如何确保 的 突变也受到保护?A
(这个问题与前一个问题有些相关。)如果“公式”做了“有趣的事情”——例如访问/改变外部状态,该怎么办?
这可以是任何事情,从访问全局变量到查询数据库,再到使用文件系统,再到通过 IPC 或网络协议进行交换。
这一切看起来可能非常天真,就像
A int = room.L * room.W * getCoefficient()
所有漂亮的细节都隐藏在该getCoefficient()
调用中一样。当然,我们可以再次通过对编译器施加任意限制来解决这个问题,只允许显式访问相同封闭类型的字段,并且只允许它们参与没有函数调用或某些“白名单”的简单表达式。它们的子集,例如
math.Abs
或其他。这显然降低了该功能的实用性,同时使语言变得更加复杂。如果“公式”具有非线性复杂性怎么办?
假设,该公式是
O(N³)
关于 的值W
。然后,将
W
值设置为 0 几乎会立即处理,但将其设置为 10000 会显着减慢程序速度,并且这两种结果都会形成看似不太不同的语句:r.W = 0
vsr.W = 10000
。这又违背了尽可能少用魔法的原则。
为什么我们只允许在结构类型上使用这样的东西,而不是在任意变量上——假设它们都在相同的词法范围内?
这看起来像是另一个任意限制。
另一个(据称)最明显的问题是,当程序员像这样时会发生什么
var r room
r.L = 2 // r.A is now 2×0=0
r.W = 5 // r.A is now 2×5=10
r.A = 42 // The invariant r.A = r.L×r.W is now broken
?
现在您可以看到,上述所有问题都可以通过简单地编写您需要的内容来解决,例如使用以下方法:
// use "unexported" fields
type room struct {
l int
w int
a int
}
func (r *room) SetL(v int) {
r.l = v
updateArea()
}
func (r *room) SetW(v int) {
r.w = v
updateArea()
}
func (r *room) GetA() int {
return r.a
}
func (r *room) updateArea() {
r.a = r.l * r.w
}
通过这种方法,您可能对上述所有问题都一清二楚。
请记住,程序是为人类阅读而编写的,然后才供机器执行;对于正确的软件工程来说,最重要的是尽可能保留代码,而代码的各个部分之间尽可能不存在任何魔法或复杂的隐藏依赖关系。请记住
软件工程是当你增加时间和其他程序员时编程所发生的事情。
- 3 回答
- 0 关注
- 118 浏览
添加回答
举报