Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / All-Topics

Is It Worth Persevering with Golang?

4.33/5 (4 votes)
2 Jun 2016CPOL6 min read 9.5K  
After perhaps 10-20 hours of learning, coding and messing around, I'm wondering - is it worth persevering with Golang?

Is it worth persevering with Golang?

I recently decided to try out the Go Programming Language, by building a little project called google-it which lets me run a Google search from a terminal:

Is it worth persevering with Golang?

The idea behind the project is simple - avoid jumping into a browser if you need to quickly look up something which you can find in the first line of a Google search result. The idea is to try and stay in the zone. For example, forgotten how to split panes in tmux?

google-it "split pane tmux"  

Would probably show enough information to get going without leaving the terminal.

Anyway, the project itself is not that useful1 but it seemed like an ideal project to use as a learning exercise for a new language. After perhaps 10-20 hours of learning, coding and messing around, I'm wondering - is it worth persevering with Golang?

Why Go2?

The decision to choose Go for this learning exercise was fairly arbitrary. For the last few years, I've been using mainly interpreted languages or languages which use a platform (.NET, Node.js, Java, etc.) and the idea of going back to something which compiles into in good ol' binaries seemed appealing. I'd also heard a lot of good things about Go in general, mostly relating to simplicity, ease of use and concurrency.

Why Persevere?

That's where I'm looking for guidance. I've collected some of my observations so far, and my overall experience with the language is uninspiring. Anyone who can comment on what makes Go great, or whether my ambivalence is justified will help me decide whether to build my next mess-around project in Go or move on to something else.

Frustrations So Far

Before I upset anyone, this is all just the opinion of a total Go noob with maybe 15 hours of coding time in Go. But I've been developing using a few different languages and platforms for while.

Folder Structure Is Way Too Opinionated

Setup itself is easy, at least on Unix or a Mac. But like many coders, I'm anal-retentive about how I like to organise things:

~
+---repositories
    +---github.com
        +---dwmkerr
            +---project1
            +---etc
        +---organisation1
            +---etc 
    +---bitbucket.com
        +---etc

This is how I structure my projects on all my machines, and it works for me.

Go forces me to put all of my Go projects in the $GOPATH, so now I have:

+---repositories
    +---github.com
        +---etc
+---go
    +---src
        +---github.com
            +---dwmkerr
                +---goproject1

Which unnecessarily spreads out my projects. Other thoughts:

  1. My src folder is increasingly cluttered with dependent modules, making it harder to find my own work.
  2. Even within the project folder, I have little flexibility. I'd like to have a src folder to keep my code in, with just the README.md at the root (leaving space for a docs folder and others if necessary) - this cannot be done, so my root folder is cluttered.
  3. Again, in the project folder itself, I cannot use sub-folders for code. Some might argue if you need subfolders you have too much code in one project.

All in all, it feels like there are a lot of constraints for structure and organisation, with little benefit.

The Idiomatic Approach to Error Handling is Flawed

This is likely to prove contentious.

My code contains sections like this:

func LoadSettings() (Settings, error) {

  var s Settings

  exists, err := exists(GetSettingsPath())
  if err != nil {
    return s, err
  }
  if !exists {
    return CreateDefaultSettings(), nil
  }

  raw, err := ioutil.ReadFile(GetSettingsPath())
  if err != nil {
    return s, err
  }

  json.Unmarshal(raw, &s)
  return s, err
}

I see smells:

  1. The s structure is created even though I may not need it.
  2. Even worse, it is returned uninitialised in error conditions.
  3. Repetitive code for dealing with error conditions for calls.

Now I could avoid the first smell by returning a pointer to the structure, but that incurs unnecessary complexity and heap allocations. Here, I feel the language is forcing me to do something awful (return a structure I know is invalid) and expect the caller to deal with it.

Even worse - the calling code now does this:

settings, err := LoadSettings()  
if err != nil {  
    color.Red("Error loading settings: ", err)
    os.Exit(1)
}

I've seen this in many places - nested calls passing the same error around, with little extra context, and eventually terminating.

This is what exceptions are for.

Native exceptions in languages handle this for you, giving stack information and killing the process by default.

The 'pass the error on to the caller approach' may not be the right way to go, but the Go blog suggests exactly this:

And to me it stinks a bit. If this is the truly desired idomatic approach, then why not support it natively by the language? Here's the same pseudo-code in F#:

let loadSettings =  
  let path = getSettingsPath()
  match exists path with
  | true -> path |> readFile |> readSettings
  | _ -> createDefaultSettings

match loadSettings with  
| Some settings -> // ..whatever
| None -> // ..deal with errors

If loadSettings can't return settings, it doesn't return settings. If the caller doesn't handle the 'no settings' scenario explicitly, the compiler will complain that there's a case missing. In this case, we have an approach which will warn the coder if they miss something.

Inconsistent Syntax

A small one, but when I'm defining a structure I can do this:

type Link struct {  
    Id string
    Uri string
}

but when I'm returning a structure, I need commas:

return Link{  
    Id:  strconv.Itoa(linkNumber),
    Uri: item.Link,
}

I can see the benefit of allowing a comma on the last line, to support quick refactoring, but forcing it seems odd. Why commas for some constructs and not others?

Also, some more 'unusual' syntax (depending on your background) is present, I assume to save space:

something := createAndAssign()

// rather than
var something SomeType  
something = assign() 

But some space saving constructs such as ternary operators are missing:

// easy- c++, c#, java style
something := condition ? case1() : case2()

// easy- python style
something := case1() if condition else case2() // python

// hard - go style
var something SomeType  
if condition {  
    something = case1()
} else {
    something = case2()
} 

Difficult Debugging

For C, C++, .NET, Java and many other languages, debugging is pretty straightforward. For Node.js, you can just use the excellent Chrome debugging tools. For Go, it seems like it's much harder.

In my limited time using the language, I avoided gdb because it looked like a lot of work:

I did see some projects like godebug which may ease the process, but I was initially surprised by the effort needed to get into debugging.

Delights So Far

It's also worth talking about what I've liked or loved about Go so far.

Simple Tooling

A project can be nothing more than a single file, go knows how to build and install it. Compare that to Java, where you have a lot of 'project' related stuff - Gradle stuff, Ant stuff, Maven stuff, XML project files stuff and it feels much cleaner.

The tooling is intuitive, fast and works well if you are happy living in a terminal.

Testing as a First Class Citizen

Testing is built in, which is great. Knowing that you can run go test on a project and have a standard way of executing tests is really quite nice. I love npm test for Node.js projects as it has helped standardise testing as a practice (checkout npm install then npm test).

However, I did have to rely on a library, goconvey, to allow me to write tests in the more BBD structured style which I prefer:

JavaScript
func TestSpec(t *testing.T) {

    Convey("The param loader", t, func() {

        Convey("Should handle no params", func() {
            params, err := ParseParams([]string{})
            So(params.ShowHelp.Present, ShouldEqual, false)
            So(params.Results.Present, ShouldEqual, false)
            So(params.Open.Present, ShouldEqual, false)
            So(err, ShouldEqual, nil)
        })

But that's a totally personal thing and I'm sure many others will prefer more 'vanilla' tests.

Great Documentation

I've found everything I've needed so far on Go's own documentation. The documentation is clean, accessible and seems fairly complete from my limited interactions with it.

What Am I Missing?

There are some things I know I haven't had a chance to look at which may really be demonstrating the best parts of Go:

  1. Concurrency patterns
  2. Performance
  3. Writing web servers
  4. Godoc

Should I Continue?

At this stage, I'm leaning towards moving on and trying something different, hoping that I'll come back to Go later. Should I persevere with Go for my next project, would Go enthusiasts suggest so and what sort of project hits the 'sweet spot' where Go is a really effective choice?

Any comments are welcome!

Can You Help Me Get Better?

Any pull requests to my project or comments which show where I've gone wrong and what I could do to improve my experience and code would be welcome at:

Thanks!

Footnotes

  1. Mainly because you have to sign up for the Google Cloud Platform to get an API key so you can perform searches, as Google has deprecated the free and easy search API. ?

  2. https://www.youtube.com/watch?v=DvijZuvEiQo ?

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)