在Go语言中,短变量声明是否会导致代码结构不良呢?

huangapple go评论77阅读模式
英文:

Do short variable declarations lead to poorly structured code in Go?

问题

从我在GitHub上查看了很多Go代码后,我注意到Go编程人员喜欢使用短变量声明(:=)并经常使用它。这是一个例子Coding Style。但是,这种用法似乎过于频繁,会导致代码结构混乱:非常长的函数将大量功能捆绑在一起,因为短变量声明只能出现在函数内部。如果你想设置一个封装类似于具有多个短、模块化函数操作的成员的包,就像良好的结构化编程和面向对象编程实践所要求的那样,你实际上不能有效地使用短变量声明来定义成员变量。每当我看到超过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()中这样做-可以根据需要,在首次使用成员时进行初始化,尽管这可能会带来其他潜在的复杂性。(尽管如此,由于在“构造函数”中初始化类成员一直是常见做法,所以我对此并不太在意。)

这种设计在Go中是否是非典型的、“糟糕”的代码?短变量声明的广泛使用及其我认为的负面影响被认为是一件好事吗?坦率地说,我无法理解它为什么会是好事。我倾向于认为,也许程序员过于喜欢简洁的语法而过度使用短变量声明,结果是一堆臃肿的代码。我是否漏掉了什么?

编辑:由于之前的问题引起了很多困惑,我将尝试用一个简单的例子来说明(这个例子可能无法编译-只是为了快速说明)。

如果我写:

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()
}

我认为这是捆绑、臃肿、结构不良的代码。

但是,当我写:

package main
    
import
     
(    "packageA"
    "packageB"
)

    
var  a packageA.T
var  b packageB.T

init(){

    a=new(packageA.T)
    b=new(packageB.T)

}

然后我可以写:

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()`中被分解但使用了短形式而没有嵌套



<details>
<summary>英文:</summary>

From looking at a lot of  Go code on GitHub, I have noticed that Go coders love the short variable declaration (`:=`)and use it very often. Here&#39;s an example [**Coding Style**](http://mattn.github.io/go-gtk/). But this usage seems far too often to create poorly structured code: Very long functions that bundle lots of functionality together, because  [**Short variable declarations may appear only inside functions.**](http://golang.org/ref/spec#Short_variable_declarations)   If you want to set up a package that encapsulates something akin to a class with members that several short, modularized functions operate on, as good structured programming and OOP practices mandate, you can&#39;t really use  short variable declarations effectively for member variables. I tend to get uneasy whenever I see any function that&#39;s more than 10 or 15 lines long - I know something&#39;s probably not right with that design.
 
Personally, I&#39;m not a big fan of  short variable declarations except for local initializion of loop counters, etc. Aside from the above mentioned issue, I like to see clearly the type I&#39;m working with. Particularly when looking over new code, short variable declarations assume that the reader knows what the function initializing the variable is returning, or obliges them to go and find out, or deduce it from the context. So, that code becomes less readable and requires the reader to stop, and perhaps search somewhere for its meaning, whereas a`var`declaration might make things immediately clear. 

(I suppose one way write better code and still use short variable declarations would be to avoid the use of package global members entirely and parameterize all your functions - not necessarily a bad thing - but this probably creates more work than you will save using short variable declarations.)
 
As a result I have been opting to use this sort of design for my packages, similar to the way declaration and initialization work in traditional OOP languages such as Delphi and C++:
  

    package myPackage
    
    import (
    	&quot;importA&quot;
    	&quot;importB&quot;
    )
    
    var m_member1 *importA.T
    var m_member2 *importB.T
    
    func init() {
    
    	m_member1 = new(importA.T)
    	m_member2 = new(importB.T)
    
    }

Then I have clearly typed, initialized and encapsulated package members that are available for use in the package. Yes, this does violate the good practice of initializing only when necessary, but I don&#39;t have to do this in init() either - can do it on an as needed basis, when the member is used for the first time, although that has other potential complications.  (Be that as it may, since initialization of class members in a `constructor` has been common practice for a long time, I don&#39;t have much of problem with this, regardless.)

Is this non-idiomatic, &quot;bad&quot; code in Go? Is the abundant use of short variable declarations, with their IMO negative consequences, considered a good thing? Frankly I fail to see how it could be. I tend to think that perhaps short variable declarations are being used too much by programmers who just love the short syntax, and the result is a lot of bloated looking spaghetti style code. Am I missing something? 

____________________________________

**Edit: Since the question as stated caused a good deal of confusion, I&#39;ll try to illustrate with a simple example (this may or may not compile - wrote quickly just to illustrate)**

If I write:

    package main
    
    import
    (
    &quot;packageA&quot;
    &quot;packageB&quot;
    )
    
    
    func DoStuff(){
    
        a:=PackageA.T
        a.Dostuff()
        
        }
        

 Then it will be very easy to continue and write:
  
 

    func doStuff(){
    
        a:=PackageA.T
        b:=PackageB.T
        Dostuff(a)
        DoMorestuff(b)
        DoEvenMorestuff(a,b)
        DoLotsofstuff(a,b)
        
        .....
        
        
        }
    
    func main(){
      DoStuff()
    }
     
   
IMO bundled, bloated, poorly structured code.  
   

&gt; **____________________________________**

  

But when I write

    package main
        
    import
     
    (    &quot;packageA&quot;
        &quot;packageB&quot;
    )

        
    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()
      
    }

A modularized pipeline style design, which cannot be implemented with the short variable declaration form. The best that can be done using the short form is nested, parameterized functions, which are generally not a very good design choice either.

Some complained that this amounts to globals, but in a well designed, encapsulated package with a minimal public interface, that is no more of an issue than declaring variables local to a function. Packages should be atomic. Member variables have been an accepted component of OOP design &quot;forever&quot; and when used properly, following the rules of OOP and structured programming, they are not globals, but locals to the package or module or class which encapsulates them. 

Granted, there is no feature of any language that cannot be used, or abused. My question is simply that short variable declarations seem to be ripe for abuse and force certain design decisions that are less than desirable, unless used very discreetly. I&#39;m asking if there is a way to use the form that will circumvent the issues I have with them and afford me their ease of use without the drawbacks.

**Edit 2:**
_____________________________________________    

**Perhaps this is something of a compromise:**

    package main
    
    import
    
    (    
    
     &quot;packageA&quot;
     &quot;packageB&quot;
     
    )
    
    
      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()
        

 }
 
Still bundled up in `main()` but that&#39;s not really a complaint - `doStackofStuff()` is my interface call. In &quot;real code&quot; I would write a separate package for all of it, and only  `DoStackofStuff()` would be public and callable from `main()` - the rest would be encapsulated.  The implementation is broken up in `doStackofStuff()`, yet using the short form without nesting.
    
    












</details>


# 答案1
**得分**: 3

答案实际上非常简单短变量声明例如`a := 2`的唯一替代方式是长变量声明例如`var a int = 2`)。

它们中的任何一种都不会导致代码混乱或使函数本质上变得更长

<details>
<summary>英文:</summary>

The answer is actually very simple. The only alternative to short variable declaration e.g. `a := 2` is the long variable declaration e.g. `var a int = 2`.

Does either of them promote spaghetti code or make functions inherently longer? No.



</details>



# 答案2
**得分**: 2

我认为你在这里混淆了几个不相关的问题

1. 如果你需要在Go中模拟类和结构体 - 不要使用模块使用结构体为它们构建"构造函数"就这样我甚至很难称之为模拟尽管它与C++或Java的类并不完全相同我的意思是为什么不只是这样做

        type Foo struct {
			Bar string
			Baz int
		}

		func NewFoo(bar string, baz int) *Foo {
			return &Foo{
				bar,
				baz,
			}
		}

		// 如果你想要静态状态 - 只需这样做
		var DefaultFoo *Foo

		func init() {
			DefaultFoo = NewFoo("foo", 1)
		}


2. 我不完全明白为什么在函数内部使用`:=`会导致混乱的代码你能更清楚地表达你的观点吗它可能会造成的最大问题是作用域冲突如果你不小心的话 - 就像在这个例子中

        var x int = 3

        func main() {
	        if x == 3 {
		        x := 5
        		fmt.Println(x) // 将打印 5
        	}

	        fmt.Println(x) // 将打印 3

        }


3. 回到你的例子 - 从不同的模块导入类型并不是坏设计例如在模块的init()函数中初始化静态http客户端)。但你必须确保你真的没有混淆两个包之间的责任

<details>
<summary>英文:</summary>

I think you are mixing a few issues here that are not connected:

1. If you need to emulate classes and structs in Go - don&#39;t use modules for them. Use structs. Build &quot;constructors&quot; for them. That&#39;s it. I&#39;d hardly even call it emulation, even though it&#39;s not 100% identical to C++ or Java classes. I mean, why not just do something like


        type Foo struct {
			Bar string
			Baz int
		}

		func NewFoo(bar string, baz int) *Foo {
			return &amp;Foo{
				bar,
				baz,
			}
		}

		//and if you want static state - just do this
		var DefaultFoo *Foo

		func init() {
			DefaultFoo = NewFoo(&quot;foo&quot;, 1)
		}




2. I don&#39;t fully see why doing `:=` inside functions will create spaghetti code. Can you make your point clearer on that? The most harm it can do is scope collisions if you&#39;re not careful - like in this example:

        var x int = 3

        func main() {
	        if x == 3 {
		        x := 5
        		fmt.Println(x) // will print 5
        	}

	        fmt.Println(x) //will print 3

        }

3. going back to your example - it&#39;s not bad design to import types from a different module (e.g. have a static http client initiated in a module&#39;s init() function). But you have to make sure you are really not mixing up responsibility between the two packages.

</details>



huangapple
  • 本文由 发表于 2014年4月2日 19:10:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/22809550.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定