[go: up one dir, main page]

DEV Community

Cover image for Language Review: Gophers Galore!
Ryan Burmeister-Morrison
Ryan Burmeister-Morrison

Posted on • Edited on

Language Review: Gophers Galore!

For a relatively young language, Go's doing pretty well. Other than having the best mascot, it has been used to create several very popular products, such as Docker, Kubernetes, and Ethereum's Geth.

Go's currently my favorite language for several reasons. Let's get to what's good, bad, and meh about Go.

Good: Simple Syntax

Go has only 25 keywords. For context, here's a graph of some of the most popular languages and the number of keywords they have.

# of Keywords

That's not a lot! And that's a good thing. There's not much magic in Go. That means that there's less to learn and more clarity by default. There's a post I love by Dave Cheney (a contributor to Golang itself) called "Clear is better than clever". It's a short post about the difference between readability in code and clarity, and the principles of effective Go code. It's well worth the 5 or 10 minutes it takes to read.

The lack of keywords makes it easy for new programmers to get started as well. I often hear about the ease of Python and its clarity. Take the following two snippets to compare them:

# add.py
def add(one, two):
    return one + two

if __name__ == '__main__':
    a = 3
    b = 4
    total = add(a, b)
    print(total)
Enter fullscreen mode Exit fullscreen mode
// add.go
func add(one, two int) int {
    return one + two
}

func main() {
    a := 3
    b := 4
    total := add(a, b)
    fmt.Println(total)
}
Enter fullscreen mode Exit fullscreen mode

To me, the Go code is more clear. That's not born out of familiarity; I've known Python far longer than I've known Go. For the most part, the code feels the same. But the addition of static typing in Go makes it very clear what my parameters must be in order to use add(), and what I should expect back. I can't say the same about Python.

Good: Static Typing and Type Inference

Not everybody likes static typing, and I get it. It can feel like a hassle sometimes. Some things are just so obvious as to what they are that it feels like overkill to have to specify types for every variable, and in a lot of cases, it is! Fortunately, Go has type inference. Look at the following snippet:

func main() {
    a := 1
    b := 4.6
    c := "Hello, world!"
}
Enter fullscreen mode Exit fullscreen mode

In that snippet, a is an int, b is a float64, and c is a string. I didn't have to specify the types, but they're defined. If I were to try to assign the value 3 to c, I'd get a type mismatch error. As a consequence, Go's typing system feels reasonable; the obvious stuff is taken care of for you, and the not-so-obvious stuff asks you to define the types. Surely that's not too much to ask to make your code easier to decode, right?

Good: Detailed Documentation

Go has some very good documentation written by the official team. You can find it all in one location, and it's split up nicely. If you want an interactive taste of the language without committing fully, take the tour. If you like what you saw in the tour, look at How to Write Go Code. If you really like Go, read the Effective Go document. It's all organized pretty well and well-written in my opinion.

Good: Easy Documentation Tools

Go puts a lot of emphasis on code documentation. If you use a tool like golint which reports problems with your code style, you'll get several warnings if you don't add comments to exported functions. It might be annoying at first, but Go makes it easy to document your code properly and can generate a nice HTML document that maps out your code. It certainly helps that Go is statically typed, as it takes care of type definitions for you. All you need to do is add a short comment above the function you want to document, like so:

// Add returns the result of adding one and two.
func Add(one, two int) int {
    return one + two
}
Enter fullscreen mode Exit fullscreen mode

Of course, that's a simple and impractical example, but it really helps for large projects. If you want to see an example of what the generate documentation might look like, check out a package from Go's standard library: https://golang.org/pkg/os/. Fair warning, it does get a little getting used to, but once you are, it's very nice.

Good: A Large and Useful Standard Library

I've written a lot of projects in Go, and in most of them, I only need one or two third-party dependencies max. Compare that to NodeJS, where you typically need 20-30 for regular-sized projects minimum. To me, that really speaks to the utility of Go's standard library and what's possible with it.

I've written many servers and microservices in Go using only its standard library. Here's an interactive basic server implementation:

There's a lot of power in that little bit of code that isn't nearly as easy with the standard libraries of any other language I know. That's just a taste of all that Go's standard library can do. You can check out all the packages it has here: https://golang.org/pkg/.

Good: Verbose Error Handling

Here's some code to open a file named temp.txt for writing in Go:

func main() {
    file, err := os.OpenFile("temp.txt", os.O_WRONLY|os.O_CREATE, 0755)
    if err != nil {
        fmt.Fprintln(os.Stderr, "There was an error opening your file")
        os.Exit(1)
    }
    defer file.Close()
}
Enter fullscreen mode Exit fullscreen mode

A lot of people I've known that use Go can't stand this level of verbosity. In Go, functions return errors like that all the time, and you have to manually check if the error is nil or not (to determine whether an error actually occurred). I get it; it does seem very verbose, particularly when compared to other languages.

I'd like to make a counter-argument, however. In other languages like Python and Java, if you're doing it right, your error handling will be just as verbose. Here's Python's equivalent code:

if __name__ == '__main__':
    try:
        file = open('temp.txt', 'w')
    except:
        eprint("There was an error opening your file")
        exit(1)
    finally:
        file.close()
Enter fullscreen mode Exit fullscreen mode

To me, Go is the winner again.

Meh: Package Management

Go's package management has traditionally been very messy. Originally, packages were pulled straight from places like GitHub and had no versions. Each time you needed to download new dependencies, they'd pull the newest commit from the repo's master branch by default, which had the possibility of breaking your codebase. To combat it, projects would vendor their dependencies, meaning they would be re-downloaded and placed in a special vendor/ folder in your project's root. This made the codebase large and hard to keep up with.

More recently, since Go v1.11, modules have been implemented. They still feel a bit wonky, but everything has versions, and the dependencies are managed automatically. It's also very easy to migrate old projects to the new module system, as it only needs to add one configuration file, go.mod. It still needs work, but it's improving for sure.

Bad: No Generics, Overloading, or Default Parameters

When trying to get into Go's advanced concepts, or when trying to build large projects, Go can feel really inflexible. To an extent, that's part of its design, but it really hurts when you need to create a custom data structure and you have to do a lot of extra work to get it working properly. Here's a (very) minimal stack implementation in Go:

type Stack struct {
    Position int
    Items []interface{}
}

func NewStack() Stack {
    return Stack{
        Position: 0,
        Items: []interface{}{},
    }
}

func (s *Stack) Push(item interface{}) {
    s.Position++
    s.Items = append(s.Items, item)
}

func (s *Stack) Pop() interface{} {
    s.Position--

    item := s.Items[len(s.Items)-1]
    s.Items = s.Items[:len(s.Items)-1]
    return item
}

func main() {
    stack := NewStack()
    stack.Push(3)
    stack.Push(5)

    fmt.Println(stack.Pop())
    fmt.Println(stack.Pop())
}
Enter fullscreen mode Exit fullscreen mode

This will work as expected, but for a language that prides itself in clear and readable code, all of the interface{} types (which match any type in Go) are hard to look at. The only other option is to create a separate Stack for each type, which is a problem because Go doesn't support overloading either! As a consequence, you'd have to name each one as StackString, StackInt, StackFloat64, and so on, and that's pretty darn WET.

In a lot of cases when you have interface{} declarations everywhere, you end up having to do a lot of manual type assertions to verify that you're working with the proper type. It's just plain frustrating when you have to deal with it.

Conclusion

Go really shines when working within a team. It is easy to learn while still enforcing well-defined standards that maintain clarity. Learning new languages can sometimes feel like a curse after learning Go due to its ease and utility. Most of the co-workers I work with daily have converted to Go as their primary language. I could go on and on about other things that I like about Go, such as its speed and portability, but I have to stop somewhere.

I hope it's obvious that the "Good", "Bad", and "Meh" labels are my opinion; I'm not trying to pass those off as fact. If you disagree with anything, let me know! I'd enjoy discussing it. And if you noticed that I said anything that is objectively wrong, post a comment! I'm open to having my mind changed.

If you're interested in learning Go, leave a commment! I have some resources that have worked well for both myself, co-workers, and people who have never programmed before.

Thanks for reading!

Top comments (1)

Collapse
 
tojacob profile image
Jacob Samuel G.

Your reviews are very fun to read. Thanks!