通过查看GitHub上的很多Go代码,我注意到Go编码器喜欢短变量声明(:=
)并且经常使用它。这是一个例子Coding Style。但是这种用法似乎经常用于创建结构不良的代码:非常长的函数将大量功能捆绑在一起,因为Short variable declarations may appear only inside functions.如果你想设置一个封装类似于类的东西的包,其中有几个简短的成员,模块化函数运行,如同良好的结构化编程和OOP实践要求,您不能真正有效地为成员变量使用短变量声明。每当我看到任何长度超过10或15行的函数时,我都会感到不安 - 我知道这个设计可能不对。
就个人而言,我不是短变量声明的忠实粉丝,除了本地初始化循环计数器等。除了上面提到的问题,我还想清楚地看到我正在使用的类型。特别是在查看新代码时,短变量声明假定读者知道初始化变量的函数正在返回什么,或者迫使他们去查找或从上下文中推断出它。因此,该代码变得不那么可读,并且需要读者停止,并且可能在某处搜索其含义,而var
声明可能会立即清楚。
(我想有一种方法可以编写更好的代码并且仍然使用简短的变量声明来避免完全使用包全局成员并参数化所有函数 - 不一定是坏事 - 但这可能会产生比你节省更多的工作使用短变量声明。)
因此,我一直选择在我的包中使用这种设计,类似于传统OOP语言(如Delphi和C ++)中的声明和初始化工作方式:
package myPackage
import (
"importA"
"importB"
)
var m_member1 *importA.T
var m_member2 *importB.T
func init() {
m_member1 = new(importA.T)
m_member2 = new(importB.T)
}
然后我有明确的打包,初始化和封装的包成员可以在包中使用。是的,这确实违反了仅在必要时初始化的良好做法,但我不必在init()中执行此操作 - 可以根据需要在第一次使用该成员时执行此操作,尽管有其他潜在的并发症。 (尽管如此,因为constructor
中的类成员的初始化已经很长时间以来一直很常见,所以无论如何我都没有太多问题。)
这是Go中的非惯用“坏”代码吗?是否充分利用短变量声明及其IMO负面后果被视为好事?坦率地说,我看不出它是怎么回事。我倾向于认为,对于喜欢简短语法的程序员而言,可能很少使用短变量声明,结果是很多看起来很臃肿的意大利面样式代码。我错过了什么吗?
编辑:由于上述问题引起了很大的混淆,我将尝试用一个简单的例子来说明(这可能会也可能不会编译 - 快速编写只是为了说明)
如果我写:
package main
import
(
"packageA"
"packageB"
)
func DoStuff(){
a:=PackageA.T
a.Dostuff()
}
然后继续写下很容易:
func doStuff(){
a:=PackageA.T
b:=PackageB.T
Dostuff(a)
DoMorestuff(b)
DoEvenMorestuff(a,b)
DoLotsofstuff(a,b)
.....
}
func main(){
DoStuff()
}
IMO捆绑,臃肿,结构不良的代码。
_ __ _ __ _ __ _ __ _ 的__ _ __ _ ___
但是当我写作
package main
import
( "packageA"
"packageB"
)
var a packageA.T
var b packageB.T
init(){
a=new(packageA.T)
b=new(packageB.T)
}
Then I can write:
func dostuff(){
a.Write()
}
func doMorestuff(){
b.Write()
}
func doEvenMorestuff(){
a.Read(b)
}
func doLotsofstuff(){
a.ReadWrite(a,b)
b.WriteRead(b,a)
}
func main(){
dostuff()
doMorestuff()
doEvenMorestuff()
doLotsofstuff()
}
模块化的管道样式设计,无法使用短变量声明表单实现。使用简短形式可以做到的最好的是嵌套的参数化函数,这些函数通常也不是一个很好的设计选择。
有些人抱怨这相当于全局变量,但是在一个设计良好,封装的包中具有最小的公共接口,这不仅仅是声明函数本地变量的问题。包应该是原子的。成员变量是“永远”的OOP设计的可接受组件,如果使用得当,遵循OOP和结构化编程的规则,它们不是全局变量,而是封装它们的包或模块或类的本地变量。
当然,没有任何语言的功能无法使用或滥用。我的问题很简单,短变量声明似乎已经成熟,可以滥用并强制某些不太理想的设计决策,除非非常谨慎地使用。我问是否有办法使用这种形式来解决我遇到的问题,并提供易用性而没有缺点。
编辑2:
也许这是妥协:
package main
import
(
"packageA"
"packageB"
)
func dostuff(a PackageA.T){
a.Write()
}
func doMorestuff( b PackageB.T ){
b.Write()
}
func doEvenMorestuff(a a PackageA.T, b PackageB.T ){
a.Read(b)
}
func doLotsofstuff(a a PackageA.T, b PackageB.T ){
a.ReadWrite(a,b)
b.WriteRead(b,a)
}
func doStackofStuff(){
a:=PackageA.T
b:=PackageB.T
dostuff(a)
doMorestuff(b)
doEvenMorestuff(a,b)
doLotsofstuff(a,b)
}
func main(){
doStackofStuff()
}
仍然捆绑在main()
,但这不是真正的抱怨 - doStackofStuff()
是我的界面通话。在“真实代码”中,我会为所有代码编写一个单独的包,只有DoStackofStuff()
是公共的,并且可以从main()
调用 - 其余的将被封装。该实现在doStackofStuff()
中被分解,但使用了没有嵌套的简短形式。
答案 0 :(得分:4)
答案其实很简单。短变量声明的唯一替代方法,例如a := 2
是长变量声明,例如var a int = 2
。
他们中的任何一方是否会宣传意大利面条代码或使功能本身更长?否。
答案 1 :(得分:3)
我认为你在这里混合了一些没有关联的问题:
如果您需要在Go中模拟类和结构,请不要使用模块。使用结构。构建"构造函数"对他们来说就是这样。即使它与C ++或Java类没有100%完全相同,我甚至都称之为仿真。我的意思是,为什么不做一些像
这样的事情type Foo struct {
Bar string
Baz int
}
func NewFoo(bar string, baz int) *Foo {
return &Foo{
bar,
baz,
}
}
//and if you want static state - just do this
var DefaultFoo *Foo
func init() {
DefaultFoo = NewFoo("foo", 1)
}
我不完全明白为什么在函数内部:=
会创建意大利面条代码。你能说清楚你的观点吗?如果您不小心,它可以做的最大的伤害是范围冲突 - 例如:
var x int = 3
func main() {
if x == 3 {
x := 5
fmt.Println(x) // will print 5
}
fmt.Println(x) //will print 3
}
回到你的例子 - 从不同的模块导入类型是不错的设计(例如,在模块的init()函数中启动静态http客户端)。但是你必须确保你真的没有在这两个包之间混淆责任。