Question

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's an example Coding Style. 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. 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't really use short variable declarations effectively for member variables. I tend to get uneasy whenever I see any function that's more than 10 or 15 lines long - I know something's probably not right with that design.

Personally, I'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'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 avardeclaration 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 (
    "importA"
    "importB"
)

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'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't have much of problem with this, regardless.)

Is this non-idiomatic, "bad" 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'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
(
"packageA"
"packageB"
)


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.

______________________

But when I write

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

}

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 "forever" 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'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

(    

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

}

Still bundled up in main() but that's not really a complaint - doStackofStuff() is my interface call. In "real code" 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.

Was it helpful?

Solution 2

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't use modules for them. Use structs. Build "constructors" for them. That's it. I'd hardly even call it emulation, even though it'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 &Foo{
            bar,
            baz,
        }
    }
    
    //and if you want static state - just do this
    var DefaultFoo *Foo
    
    func init() {
        DefaultFoo = NewFoo("foo", 1)
    }
    
  2. I don'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'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's not bad design to import types from a different module (e.g. have a static http client initiated in a module's init() function). But you have to make sure you are really not mixing up responsibility between the two packages.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top