loading...
Cover image for My reflections on Golang

My reflections on Golang

deepu105 profile image Deepu K Sasidharan Updated on ・12 min read

Originally published at deepu.tech.

Please follow me on Twitter for updates.


Do I like Go? Yes. Would I use it for every use case I have? Definitely not.

Don't get me wrong, I like Go for what it is but like every other programming language, it is always a love-hate relationship. No programming language is perfect and all of them have their own merits and use cases. I hate it when I see people overusing something and I see that pattern with Go these days. To be fair, I have done my fair share of overusing in my career as well (mostly with JavaScript) and I can see why people do it. This is not gonna be a blog bashing Go or praising Go, it is just what I think of it after using it for over 9 months. Before I start a rant on the good and bad of Go, here is some background.

After being in the tech industry for over 10 years, I would like to think of myself as a pragmatic programmer or at least as someone getting closer to that - that should be a programmer's Nirvana. I didn't even plan to be a programmer, if you ask the 18-year-old self of me, he would say that he wanted to be an astrophysicist or a robotics engineer(Yes building space robots were my dream). Like most teenage dreams, it didn't happen and I ended up in tech instead.

Though landing an IT Job was an accident, programming wasn't alien to me. I did learn some C/C++ when I was in high school to help my girlfriend with her project and did dabble in some PHP, JavaScript, HTML and Flash(ActionScript) during my early college years for personal projects and blogs. So when I got a real IT job without having an IT background, I did what many in that situation did, I started learning the language that I stumbled upon first based on the task I was given, which happened to be Java. Being a quick learner and having some idea of programming concepts from C/C++ Java wasn't that hard to learn and I was a pretty decent Java programmer in a few months. Then I was tasked with building some Web UI and I dived deep into the world of HTML, CSS, and JavaScript and honestly fell in love with JavaScript due to its flexibility and ease. I mastered JQuery and soon become the go-to guy for front end stuff in the office.

I was anything but pragmatic back then, I was preaching JavaScript to everyone and would vehemently debate anyone who thought JS was a bad language.

Fast forward to now and if I look back I have done projects in C/C++, PHP, JavaScript, TypeScript, HTML, CSS, Java, Groovy, Scala, Python and recently Go. I think this exposure probably helped me become more pragmatic as I have started to look at programming languages as tools and each of them has their own strengths and weaknesses. Well, there is more to this story but that's for another time, the point is to set a baseline for the below reflections so that I don't sound like someone just trying Go and going on a rant.


Go is the latest language I learned and worked with, I have worked on a CLI project built with Go for over 9 months now, building a powerful scaffolding engine with my team(Yes, pretty much like JHipster) that uses Go templates where you could create what we call blueprints at XebiaLabs. So yes I have done much more than a hello world app with Go.

Without wasting more time on unrelated things here is what I like about Go and what I don't like.

What I like about Go

Simplicity

I like the fact that Go is a simple language(Going through the entire language features on the tour page literally takes 15 minutes unless you do the exercises) and unlike Scala, Rust or even JavaScript Go doesn't have many ways of doing the same thing which is extremely valuable for people working in teams and companies wanting to write maintainable code where even a newly joined employee can read and understand the code without needing much help. I think this is one of the biggest reasons that is driving Go adoption. If you have worked on large scale projects you know how difficult it is when the code is unreadable and every new team member has to spend so much time trying to understand what a piece of code does. So I was really happy when I saw that Go doesn't have features that rely heavily on implicit and such. The language features and concepts are easy to grasp and you can start being productive in Go quite soon. The only concepts that might seem bit complex are the concurrency part and even that is simpler compared to other languages.

Language provided code style and vetting

This is such a time saver. IMO every language should just do this so that you don't waste time debating code style and setting up lint rules. Go provides opinionated formatting, linting & vet tool as part of the package and the Go compiler even enforces things like unused variables and stuff. Most of the IDE/Editor plugins also use these tools for formatting and linting and hence helps to keep consistent code style across Go projects which again adds to readability and maintenance.

Goroutines & Channels

This is one of the biggest strengths of Go. The native support for concurrency and parallelism. This makes Go an ideal candidate for applications that require heavy concurrent and/or parallel processing, networking and so on. Goroutines makes it so easy to start lightweight threads and channels provide a way to communicate between these threads acting like a message bus.

func main() {
    messages := make(chan string)
    collected := make([]string, 2)

    go func() { messages <- "ping" }()
    go func() { messages <- "pong" }()

    collected = append(collected, <-messages)
    collected = append(collected, <-messages)
    fmt.Println(collected) // [ pong ping ]
}

Closures & callbacks

If you have used JavaScript you would know how useful closures and callbacks are. Go like JavaScript treats functions as objects and hence can be assigned to variables, stored in maps, passed as function parameters and returned from functions. It also supports creating nested closures and anonymous functions which helps to encapsulate context. The behavior is pretty much similar to JavaScript. So you can apply some functional programming concepts in Go as well.

func main() {
    // an unnecessarily complicated example
    type fnType = func(a int, b int) int
    fnMap := map[string]fnType{
        "ADD": func(a int, b int) int {
            return a + b
        },
        "SUB": func(a int, b int) int {
            return a - b
        },
    }

    // this is a closure
    localFn := func(method string) fnType {
        return fnMap[method] // returns a function
    }

    printer := func(fn func(method string) fnType, method string) {
        fmt.Println(fn(method)(10, 5)) // callback
    }
    // function passed as parameter
    printer(localFn, "ADD")
    printer(localFn, "SUB")
}

Type assertion and switches

Go provides a nice way of asserting types and can be used with a switch statement which makes it easier to do reflection and such.

Multiple returns

This is quite a handy feature like in Python, we are used to deconstructing objects/arrays to achieve this in JavaScript and using Tuples and such in some languages. The returns can also be named which is nice for readability.

Tooling

As mentioned earlier Go provides standard tooling for formatting, linting and so on and the language design makes it easy to build tooling for Go and hence editors/IDE has nice features like test generation, code coverage and so on. For example, the VSCode integration for Go provides the below options which helps with consistency and less boilerplate to write by hand.

Doesn't need a runtime

Go doesn't need a runtime like JVM or NodeJS, Go applications can be compiled into an executable cross-platform binary using the standard Go tooling. This makes Go applications portable and platform-independent.

What I don't like about Go

Simplicity

This is where the love-hate relationship starts, Go is a simple language which is nice but at times it feels too simple & verbose and coming from Java/JavaScript ecosystem you are spoiled with some nice features & syntax sugars which IMO makes the code more expressive and helps to keep it DRY. The things that I miss the most are

  • Generics: This is currently being considered in the next major iteration of Go, but until then this just makes you repeat code unnecessarily. I have lost count of the number of times I had to repeat the same block of code for different types where Generics would have kept it nice and simple. This is also one reason you don't see libraries like Lodash for Go.
  • Standard error handling: This also seems to be coming in the next major iteration of Go but until it lands I can complain. Anyone writing Go will remember doing if err != nil uncountable times in your code. Removing those might cut the codebase in size by at least 20%
  • Default values: I would love to see this in Go, this is quite useful. Maybe I'm just spoiled by JS.

Too much boilerplate(not suitable for DRY)

Go being too simple means you would have to write a lot of code as the language doesn't offer constructs like map, reduce, and so on, and add the lack of generic on top means you would end up writing a lot of utility code and a lot of that will be repeated to accommodate different types. Imagine writing a map function in Go, you would have to write one for every combination of maps that can be used. These factors don't make it easy to do DRY programming in Go.

Dependency management

The dependency management in the Go ecosystem feels immature and too basic compared to other mainstream languages. Importing packages from Git is nice but it also makes it more fragile. What can go wrong when you are depending on a Git branch on your production application right! There is no way to use relative dependencies(Can't beat NPM link!).
These problems are similar to the issues with the dependency range in Node package managers. Glide seems to be a popular choice but still is not as mature as solutions in other languages. In the project, I work on we used Gradle along with Gogradle and though it works fine the developer experience is not as good as using Gradle/Maven for Java project or using NPM on a NodeJS project.

Source code in GOPATH

Go recommends you to create your Go projects under the GOPATH. Maybe it is just me, but I hate this as I would normally like to organize my code. For example, I have a ~/workspace/ folder where I organize my projects by the organization. If I follow the Go recommendation I have to put the project under /home/deepu/go/src along with all the library source code that is downloaded. If you don't follow this then most of the Go tooling just doesn't work. Currently, I have a specific Gradle task that copies over all the vendor libs to my local Gopath inside ~/workspace/XL/<project> to workaround this.

Confusing pointer behaviors

Go has pretty good pointer support and the default behavior is to pass an object by value. If you want to pass something by reference you have to mark it specifically. But this behavior is not very consistent as Maps and Slices by default are passed by reference and hence this could be a bit surprising to beginners.

Struct hell

This is more of a nitpick. Structs are what you would use to create data structures in Go. It might look like an object but they are not exactly objects. While structs are fine functionally, in many cases you will end up with structs that look like the ugly brother of JSON. In real-world projects, you always will end up creating complex structs, especially if the application is doing some generic json or yaml parsing and soon your code will start to look like this. This is not that big of a concern but it just hurts my eyes every time I debug something or write tests.

    func main() {
    type MyYamlDoc struct {
        foo []map[interface{}][]map[interface{}]interface{}
        bar interface{}
    }

    ohno := MyYamlDoc{
        []map[interface{}][]map[interface{}]interface{}{
            {
                "Foo": {
                    {"Bar": map[interface{}][]map[interface{}]interface{}{
                        "Foo": {
                            {"Bar": map[interface{}][]map[interface{}]interface{}{
                                "Foo": {
                                    {"Bar": map[interface{}][]map[interface{}]interface{}{
                                        "Foo": {
                                            {"Bar": map[interface{}][]map[interface{}]interface{}{}},
                                        },
                                    }},
                                },
                            }},
                        },
                    }},
                },
            },
            map[interface{}][]map[interface{}]interface{}{
                "Foo": {
                    {"Bar": map[interface{}][]map[interface{}]interface{}{}},
                },
            },
        },
        map[interface{}][]map[interface{}]interface{}{
            "Foo": {
                {"Bar": map[interface{}][]map[interface{}]interface{}{}},
            },
        },
    }
    fmt.Println(ohno)
}

Weird interface construct

The interface concept in Go is weird. These are the only implicit construct in Go. If you come from other languages that have interfaces then this will feel weird. The fact that they are implicit means its really easy to mess things up. Refactoring is messy unless you have a smart IDE, and you can accidentally implement someone's interface by just naming your method a certain way. While implicit interfaces certainly help with polymorphism and decoupling code I personally would still prefer interfaces that are explicit.

Another interface Gotcha is nil value checks, in Go, an interface is made up of two parts a type and a value, so an interface is nil only when both type and value are nil, this means you can't just simply do nil checks on interfaces. This is so confusing the Go has a specific FAQ for this. Below article explains this in more detail

Single GC algorithm

Go implements a concurrent tri-color mark-sweep collector as its garbage collector. This specific GC implementation is optimized for better pause times while ignoring program throughput, pause frequency and many other parameters that are considered during GC. Some people in the Go community claims this as the best ever GC. Having some Java background I would have to disagree as most JVM implementations provide multiple GC algorithms you can choose from which includes a concurrent mark-sweep collector as well and most of these are balanced to take care of many more parameters than just pause times. This articles analyses this in detail. So some use cases that produce a high amount of garbage might actually be slower in Go compared to another language due to frequent GC.

Developer experience

This is purely based on personal experience and hence will vary from others. Being a polyglot developer who has worked with many languages, the developer experience from Go is not the best I have experienced. The DX of the JavaScript ecosystem is the best I have experienced so far. It feels like there are things missing in the Go ecosystem. Dependency management and toolchains need improvement. A bit more sensible language features and some syntax sugar wouldn't hurt as well.

Conclusion

Having worked with many major languages I can't just use Go for every use case but I can see why people would use Go for every use-case out there if they haven't worked with other languages.

So where would I use Go?

  • I would definitely use Go when the use case requires a lot of parallel processing and/or concurrency(both are not the same thing but are closer to each other) as you can make use of Goroutines for this and is much simpler and efficient than managing threads like in a Java application or working around it in JavaScript using callback hell since JS is actually single-threaded. Here is a nice article explaining the advantage of Goroutines.
  • Simple microservices where boilerplate is not a concern
  • Networking applications or web servers, especially with async workloads, can greatly benefit from Go. But to be fair you can do these in Java, Python, JS, etc as well but Go in the end will provide better efficiency and would be easier to implement.
  • System programming. While Rust or C is a much better choice for this but if those are not in your arsenal then Go is the next best thing. With decent support for pointers and its standard library its easier for system programs than other mainstream languages. Many popular system tools like Docker, Kubernetes, etc are indeed written in Go.

Where I wouldn't use Go?

  • Complex web application: I would choose Java with a framework like Spring or Micronaut as its much more maintainable and battle-tested and you would focus more on business logic than writing boilerplate infrastructure code. One common argument against this stack is its memory footprint but it is possible to get lower memory footprint with Spring and frameworks like Micronaut and Quarkus actually promises that OOB.
  • After writing a high-level CLI tool in Go, I hate the experience, I kept thinking that doing it in JavaScript would have been 10 times more productive and a nicer experience. SO I would choose JavaScript or TypeScript running on NodeJS for CLI tool any day. Mainly due to the ecosystem and the sheer joy and speed of getting things done without spending all your time writing boilerplate code. But this wouldn't be applicable if the CLI in question a system tool or a networking tool, in those cases Go could be a good option.

I do hope Go evolves into a general-purpose language over time and many of these concerns are solved. In the meantime, I'll try to follow this mantra.

But then you can always choose to fasten a screw using a hammer.


If you like this article, please leave a like or a comment.

You can follow me on Twitter and LinkedIn.

Cover image credit: Image from Gophercises created by @joncalhoun , Marcus Olsson (@marcusolsson), and Jon Calhoun.

Posted on by:

deepu105 profile

Deepu K Sasidharan

@deepu105

JHipster co-lead, Polyglot dev, Cloud Native Advocate, Developer Advocate @Adyen, Author, Speaker, Software craftsman. Loves simple & beautiful code. bit.ly/JHIPSTER-BOOKS

Discussion

markdown guide
 

Enjoyed that - a relatively balanced piece. Just a few corrections and observations

Dependency management

Was 'fixed' last year - making projects like Glide et al a bit obsolete. People are either moving to modules or are still using dep. Although that said it's not often that I need many dependencies as the standard library is very comprehensive.

In real-world projects, you always will end up creating complex structs, especially if the application is doing some generic json or yaml parsing and soon your code will start to look like this.

I looked at your ... um... I want to call it a struct, but I think "side pyramid of hell" would be a better term. If you're using this in a project - real world or any other world - then you've done something seriously wrong or you're trying to consume data the structure of which you have no prior knowledge about.

A huge smell is all the map[interface{}]map[interface{}]interface{} chaos. If you're using interface{} as a type all the time then you're basically writing a very verbose dynamically typed language with no compile time checking. Try and refactor to named types. Useful tools exist for parsing JSON and XML into Go types which can then be massaged by loving human hands (ie. gojson.com).

The interface concept in Go is weird.

But it's the same as in TypeScript - structural sub-typing (on methods in Go). Could you explain what you dislike about Go interfaces as opposed to TypeScript?

and you can accidentally implement someone's interface by just naming your method a certain way.

Sure - but has anyone ever done this, ever implemented the io.Writer interface accidentally and then passed this offending type to a function that's expecting it?

Go being too simple means you would have to write a lot of code as the language doesn't offer constructs like map, reduce, and so on, and add the lack of generic on top means you would end up writing a lot of utility code and a lot of that will be repeated to accommodate different types. Imagine writing a map function in Go, you would have to write one for every combination of Map that can be used. These factors don't make it easy to do DRY programming in Go.

Sure, you can't implement map or reduce in Go. There are no generics. Fine. When was the last time you needed to implement map or reduce? When was the last time you needed a function to be polymorphic over more than three types? In reality, it rarely happens. You don't map over a generically iterable type, you just write a for loop and get on with your life. I've seen more good code spoiled by an attempt to dry it out through a bad abstraction than I have seen it improved by generification.

 

Enjoyed that - a relatively balanced piece. Just a few corrections and observations

Thank you. I agree with some of your points and differ with some, also some of my concerns were more personal nitpicks and might not apply to everyone. But I think its good that we have constructive criticism. As I said in the article I do like Go and think it has a purpose and It does some things really well, I appreciate that but the takeaway from the article should be "Using the right tools for the right Job"

Dependency management

Was 'fixed' last year - making projects like Glide et al a bit obsolete. People are either moving to modules or are still using dep. Although that said it's not often that I need many dependencies as the standard library is very comprehensive.

I'm aware of modules and dep. Dep is still experimental and modules do look promising, but if you look at many popular Go projects in GitHub, seems like they are still using Glide or another system and not modules(of course it's their bad). But my point was that dependency management in Go is immature and I still stand by that. For example, if you have used NPM, Yarn, Maven or Gradle, its far better and mature than Go modules even with its shortcomings. Also in my project, we are using Gradle with Gogradle and it works fine but the experience is not as enjoyable as others as I mentioned. Also, I still don't see anything like npm link in Go modules. You have no idea how valuable it is unless you have developed some mode projects with dependencies, I'm seriously missing those here. Let me know if there is something similar in the Go world.

Also, I don't agree with "it's not often that I need many dependencies" as a generic statement. May be projects you do are simple enough to avoid that or you are reinventing the wheel all the time using language features. In most real-world cases you would need many dependencies unless you are into reinventing the wheel for everything.

In real-world projects, you always will end up creating complex structs, especially if the application is doing some generic json or yaml parsing and soon your code will start to look like this.

I looked at your ... um... I want to call it a struct, but I think "side pyramid of hell" would be a better term. If you're using this in a project - real world or any other world - then you've done something seriously wrong or you're trying to consume data the structure of which you have no prior knowledge about.

Maybe you misunderstood me. No one in their right mind should design structs like these, it was an example to show what is possible though. My pain point was when using generic parsing libs which indeed produce structures which look worse then these and hence is pain during debugging, mocking and test case creation. Also, it was a nitpick. And when you do generic data processing sometimes you don't know the structure or sometimes the structure has to be kept generic, for example, a yaml where a field can take values of different types. Many times when you work on real-world projects you have no control of what data structure will come in.

A huge smell is all the map[interface{}]map[interface{}]interface{} chaos. If you're using interface{} as a type all the time then you're basically writing a very verbose dynamically typed language with no compile time checking. Try and refactor to named types. Useful tools exist for parsing JSON and XML into Go types which can then be massaged by loving human hands (ie. gojson.com).

Agree, no one should ever write structs with interface{} as much as possible, but sometimes it's inevitable. As I said an example is when you have Yaml or Json doc that can take values of different types. We had a use case where our Yaml fields needed to take string, numeric, boolean values as well as complex Yaml expressions or functions. the only way to parse them is to declare those as interfaces and then do a type switch in code to process them.

The interface concept in Go is weird.

But it's the same as in TypeScript - structural sub-typing (on methods in Go). Could you explain what you dislike about Go interfaces as opposed to TypeScript?

No, it is not the same. In Typescript, interfaces are implemented by intent as below unlike the implicit(which is what I don't like about it) way in Go and also interfaces in TS also represent type a bit similar to a struct IMO.

interface ClockInterface {
    currentTime: Date;
    setTime(d: Date): void;
}

class Clock implements ClockInterface {
    currentTime: Date = new Date();
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) { }
}

and you can accidentally implement someone's interface by just naming your method a certain way.

Sure - but has anyone ever done this, ever implemented the io.Writer interface accidentally and then passed this offending type to a function that's expecting it?

I don't think we can answer that unless people who do that come out and say so right? I at least always have to think twice before naming a method(Which might be a good thing in some cases :P). Anyway, my point was that it is possible and also its a nitpick.

Go being too simple means you would have to write a lot of code as the language doesn't offer constructs like map, reduce, and so on, and add the lack of generic on top means you would end up writing a lot of utility code and a lot of that will be repeated to accommodate different types. Imagine writing a map function in Go, you would have to write one for every combination of Map that can be used. These factors don't make it easy to do DRY programming in Go.

Sure, you can't implement map or reduce in Go. There are no generics. Fine. When was the last time you needed to implement map or reduce? When was the last time you needed a function to be polymorphic over more than three types? In reality, it rarely happens. You don't map over a generically iterable type, you just write a for loop and get on with your life. I've seen more good code spoiled by an attempt to dry it out through a bad abstraction than I have seen it improved by generification.

Of course, everything can be done using the simple features Go provides and that is exactly my point, you have to write a lot of code for everything which is anything but DRY(I also believe in responsible DRY rather than doing it for the sake of doing it) and feels very verbose and annoying.

If you are used to doing map/reduce etc in other languages it just feels verbose doing the same all the time, again and again, using for loops. It's not an enjoyable experience at least for me. I can provide many examples of when you need map/reduce etc and when you need polymorphism but my intention is not to prove that Go is bad these are just reflections of someone who would like to see Go get better and better.

Anyways thanks for the comment, I really enjoy such healthy discussions and debate.

Also what Ben pointed out below also matters.

 

No, it is not the same. In Typescript, interfaces are implemented by intent as below unlike the implicit(which is what I don't like about it) way in Go

I must object. Although you can use interfaces to enforce contracts, they can also be used as structural sub-typing on fields - see this from the Typescript docs:

interface LabeledValue {
    label: string;
}

function printLabel(labeledObj: LabeledValue) {
    console.log(labeledObj.label);
}

let myObj = {size: 10, label: "Size 10 Object"};
printLabel(myObj);

Notice we didn’t have to explicitly say that the object we pass to printLabel implements this interface like we might have to in other languages. Here, it’s only the shape that matters. If the object we pass to the function meets the requirements listed, then it’s allowed.

Oh Yes, you are right, I never thought of Duck typing coz I used to avoid it to the best of my abilities. Interestingly, it is considered as an issue by many and there is an open ticket in TS project to change this behavior as it can cause unexpected bugs.

My problems with Go is in enforcing contracts and not in typing but regardless I understand what you mean and to be fair the exact same implicit behavior can be achieved in Typescript as well which I hated.

class Vehicle {
    run(): void { console.log('Vehicle.run'); }
}

class Task {
    run(): void { console.log('Task.run'); }
}

function runTask(t: {  run(): void }) {
    t.run();
}

runTask(new Task());
runTask(new Vehicle());

But a difference, however, is that in TS its is not the only way and I have the choice to do things explicitly without relying on the implicit behavior. And I have always used the explicit way in TS projects as far as I could remember. And seems like there are ways to workaround but they are quite verbose

Anyways, unlike Go, TS is just a superset of JS which is completely dynamic(so in the end, all the types and interfaces, etc are only syntax sugars) and due to the weak typed nature of JS allowing structural typing is the only way to emulate how JS works so its more of a limitation than a feature IMO.

 

Actually, array manipulation functions were a need I encountered quite often, and having to duplicate the same functions, just slightly changing the type, makes a real mess that's just painful to test.

 

I feel that pain all the time with arrays and maps

 

When was the last time you needed a function to be polymorphic over more than three types?

Today. Yesterday. The day before. Can we have generics now?

 

Only if you ask Father Christmas nicely.

Or rsc.

 

If you are planning next Google then Golang is good for you but for most people Java and it's huge ecosystem is more than enough. Also Java community is not just eating peanuts. They are evolving Java a lot that will take Golang next 10 years at least to achieve.

 

I understand this point of view. It sounds honest and fair, but I disagree.

I do not disagree with your feelings, but with some of your judgments.

In real-world projects, you always will end up creating complex structs

I do some "real-world" project in Java, Ruby, TypeScript (nodeJs) and Go, and Golang structure wasn't particularly complex. There is no reason for your Go structure to be more complex than a POJO for instance. If you have to deal with complex Json, you may use json.Marshaler or json.Unmarshaler interface, like here.

you can accidentally implement someone's interface by just naming your method a certain way

Hmm. Yes, and then ? It means that you may use an instance of your type as an instance of "someone's interface" only if you want, by passing it as a parameter of a function that explicitly ask for "someone's interface" for exemple. I don't see a use case when you may have an unwanted edge case with this feature.

the content of Maps and Slices by default are passed by reference

Wrong. The content of maps and slices aren't passed by reference. Slice and Maps are references (default value is nil for them) see. It is something to know, that's true, but this is less confusing than Java primitive type behavior.

The dependency management in the Go ecosystem feels immature and too basic compared to other mainstream languages

You are right. Even with module, dependency management in Golang is much basic than what you can find with npm or maven.
But you miss something important here. Even with big size project, you don't need a lot of dependencies. Unit tests ? All included. Web server ? Maybe a (small) router for dynamic route, and one for secure token if you need, but no more. Database access ? Only the driver. Linter and code inspection ? All included... Such limited dependency manager is consistent with one of the language principle : use the standard library the most (here an example).

These factors don't make it easy to do DRY programming

DRY is not related to your complaints. The DRY principle is about bad business logic duplication. For instance, if you have in a bank system, two 90% identical functions to compute account balance, there is chance to have a DRY violation. Here, you are pointing out that Go do not have a lot of syntactic sugar (you are right) and have some idioms that are very C-like (and not so fun). I agree with that. But I think go approach is better for maintenance.

And almost one of your statements is out of date

Go recommends you to create your Go projects under the GOPATH

Go is not the perfect tool (there is no perfect tool). But many of your points are just consequences of your preferences, and writing that Spring fit better than Go for complex web app, is just wrong. I experienced the two (Complex web app in spring (boot) and in Golang).

The two solutions eventually matched businesses and technical requirements.

With Spring, we had a lot of solution to write less code, and thanks to some magics, a lot of "autoconfigure" stuff. But This convenience came at a price of a steeper learning curve (in particular for new comers) and a sometime-hard configuration (I am looking at you spring-security !) when dealing with fine tuning, or very specific use case.

With Go, some task (in particular writing integration tests !) where longer to do. They where a little more boilerplate to write at the beginning of the project before shipping the first feature. But at the end, the code was much more simple and easy to understand. No need to have a deep knowledge of a complex framework like spring here. Just "basic" knowledge on HTTP, Database and so on, the absence of a framework finally turned as an advantage, when dealing with complex use case. The simplicity allow us to be focus on our problem, and not on how the framework want us to deals with such problem.

NB : you forget one (very) good point for Golang : easy to deploy !

 

Thanks for the detailed response. Most of my opinion is based on the real-world experience of building a CLI app using Go for the past 9+ months. It's not a super complex app but of fairly medium complexity.

I do some "real-world" project in Java, Ruby, TypeScript (nodeJs) and Go, and Golang structure wasn't particularly complex. There is no reason for your Go structure to be more complex than a POJO for instance. If you have to deal with complex Json, you may use json.Marshaler or json.Unmarshaler interface, like here.

In the beginning, we had some structs that were quite painful on the eye when parsing YAML. We then changed our model to simplify it. I agree you can get complex structures in every language but in Go, it looks a bit more ugly than others IMO. This is purely a personal view, you might find it beautiful. Also, regardless of what parser you use, if you have to transform the data you will end up using structs which looks really complex unless you have control over the data format.

Hmm. Yes, and then ? It means that you may use an instance of your type as an instance of "someone's interface" only if you want, by passing it as a parameter of a function that explicitly ask for "someone's interface" for exemple. I don't see a use case when you may have an unwanted edge case with this feature.

I just stated a fact as I really don't like implicit stuff again a personal preference.

Wrong. The content of maps and slices aren't passed by reference. Slice and Maps are references (default value is nil for them) see. It is something to know, that's true, but this is less confusing than Java primitive type behavior.

Yes, I didn't form that sentence correctly. I'll correct. They are references. But is still confusing, the same way Java primitives are.

You are right. Even with module, dependency management in Golang is much basic than what you can find with npm or maven.
But you miss something important here. Even with big size project, you don't need a lot of dependencies. Unit tests ? All included. Web server ? Maybe a (small) router for dynamic route, and one for secure token if you need, but no more. Database access ? Only the driver. Linter and code inspection ? All included... Such limited dependency manager is consistent with one of the language principle : use the standard library the most (here an example).

I disagree. Yes, there is a lot of standard stuff provided but it's not enough unless you are building something simple. For my medium complex project, we have 31 direct dependencies(Including one for asserts, the default way of asserting in test sucks). Look at some of the popular OSS projects like kubectl etc, they have a lot of dependencies as well. For simple stuff, you can do that without any dependencies in any language if you ok to write verbose code same way you would do in Go. Also using libraries in other languages also is a choice to reduce boilerplate. I have used NPM, yarn, Maven, Gradle, Pip, Gem and so on and If I have to rate the user experience then Go is still at the last

DRY is not related to your complaints. The DRY principle is about bad business logic duplication. For instance, if you have in a bank system, two 90% identical functions to compute account balance, there is chance to have a DRY violation. Here, you are pointing out that Go do not have a lot of syntactic sugar (you are right) and have some idioms that are very C-like (and not so fun). I agree with that. But I think go approach is better for maintenance.

May be but still, I cringe every time I have to duplicate a function and just change a type. I don't know why having Generics is bad? it's 2019.

Yes, I completely agree that Go has the best learning curve among modern languages(It took me just a few hours to start writing code for my application). Even easier than JavaScript I would say. It may be great for newcomers but once you learn it it's a bit annoying. I expect a high-level language to make my work as easy as possible with as less boilerplate possible. I don't want to implement a file walker or web server every time, I just want to use a library and get the job done as fast as possible. SO as I said this is my personal opinion based on my personal preference and I still don't see me writing a complex web application in Go.

But it's ok if you disagree. That's why we have options so you can choose what you like and I can choose what I like :)

 

but in Go, it looks a bit more ugly than others IMO

This is weird. On many aspect Go may look different from language like Java or Javascript. But it is very similar on that point.

Is

type Car struct {
  Name        string
  PlateNumber string
}

Really uglier or different than :

public class car {

  private string name;
  private string plateNumber

  // java accessor boilerplate....

}

I just stated a fact as I really don't like implicit stuff again a personal preference.

Fair enough

we have 31 direct dependencies

For one "fairly medium complexity" app ? You should have special needs (a glue app ? integration with a lot of tools), or maybe, you keep habits from other language that doesn't fit much Go. Go isn't Javascript. Most of the time you don't need much dependencies. You maybe don't like how go standard lib will solve a problem, but that's another point.

Including one for asserts, the default way of asserting in test sucks

That is wrong and a little bit arrogant. You don't like it, that's ok, but it doesn't sucks at all. I think you don't understand it.

First of all, I know some smart test lib too like Rspec, Mockito, AssertJ, junit (of course !), chai, karma, jest, etc... There are great ! And when I start to use Golang, I found confusing not having all the syntactic sugar I was accustomed... I found using mock unconvenient and strange that no assertion where available.

Then I become to realize that assertions are not so superior to a simple if... Every developer is able to understand the meaning of a simple "if"... That's no the case with all assertion lib (rspec for example is powerful, but do has his own learning steep).

But mock where still unconvenient, and hard to deals with, 'till I realize what was wrong. I was wrong. It was so easy to inject mock with Rails or Spring, that I didn't see the lack of values of this tests and how poor were some design choices. The need for mocks was just the symptom of a bad design :(.

Go testing package is not a shinny one, but it could help to write better tests, better code.

OSS projects like kubectl etc, they have a lot of dependencies as well

Well, that always far less that what node could require for a simple web app. But yes, go mod was necessary because it was one of the biggest weakness of the language

don't know why having Generics is bad

I never say that Generics are bad, but unrelated to DRY. By the way, lack of generics support is a weakness of the language. It may be useful in some cases.

I don't want to implement a file walker or web server every time

oO ?

// content of index.js
const http = require('http')
const port = 3000

const requestHandler = (request, response) => {
  console.log(request.url)
  response.end('Hello Node.js Server!')
}

const server = http.createServer(requestHandler)

server.listen(port, (err) => {
  if (err) {
    return console.log('something bad happened', err)
  }

  console.log(`server is listening on ${port}`)
})
package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func (w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "Welcome to my website!")
    })

    http.ListenAndServe(":80", nil)
}

Yes, that's maybe easy as a reply, but http server is not where go has a lot of boilerplate ! With the error system yes (it's true it is verbose).

but once you learn it it's a bit annoying

You hit the point. Go is annoying, because it is very simple. No magic, no shinning thing. And that's exactly what I expect from a language, but this is a personal opinion.

SO as I said this is my personal opinion based on my personal preference and I still don't see me writing a complex web application in Go

But it's ok if you disagree. That's why we have options so you can choose what you like and I can choose what I like

Yes, and I respect your opinion. BUT you decide to expose theses though on a platform that allow comments and discussion, so (respectful) contradictory opinions are part of the game :)

This is weird. On many aspect Go may look different from language like Java or Javascript. But it is very similar on that point.

simpler structs does indeed look fine in Go, as per my original point, they look ugly IMO when there are complex stuff like maps of maps and stuff involved. Again its a personal preference. For the record I also find Java classes ugly :P

For one "fairly medium complexity" app ? You should have special needs (a glue app ? integration with a lot of tools), or maybe, you keep habits from other language that doesn't fit much Go. Go isn't Javascript. Most of the time you don't need much dependencies. You maybe don't like how go standard lib will solve a problem, but that's another point.

I don't agree here, it's not about keeping habits, I don't want to write 10 lines of boilerplate code if a library can do it in 2 line for me. I believe in community/oss and I hate reinventing the wheel. I'm not gonna go into why that approach is better as again its again opinionated.

That is wrong and a little bit arrogant. You don't like it, that's ok, but it doesn't sucks at all. I think you don't understand it.

Sorry if I sounded arrogant, that wasn't the intention. The default way in Go seems to using equals and when having structs and stuff its quite difficult to see what is the difference. Compare the default to github.com/stretchr/testify/assert and you will see what I mean. I want a nice output from my assertion failures.

First of all, I know some smart test lib too like Rspec, Mockito, AssertJ, junit (of course !), chai, karma, jest, etc... There are great ! And when I start to use Golang, I found confusing not having all the syntactic sugar I was accustomed... I found using mock unconvenient and strange that no assertion where available.

Then I become to realize that assertions are not so superior to a simple if... Every developer is able to understand the meaning of a simple "if"... That's no the case with all assertion lib (rspec for example is powerful, but do has his own learning steep).

But mock where still unconvenient, and hard to deals with, 'till I realize what was wrong. I was wrong. It was so easy to inject mock with Rails or Spring, that I didn't see the lack of values of this tests and how poor were some design choices. The need for mocks was just the symptom of a bad design :(.

Go testing package is not a shinny one, but it could help to write better tests, better code.

That's your personal preference. In general, as I stated, again and again, I expect a high-level language to make my work easier. I just don't want to bother about writing if..else for these and wasting my time trying to figure out diff from an assertion failure. Having said that so far I only needed to use an assert lib and not a full-fledged testing framework in Go, whi8ch is quite nice.

Well, that always far less that what node could require for a simple web app. But yes, go mod was necessary because it was one of the biggest weakness of the language

Well, actually you can also do almost everything you can do in Go in Node without using dependencies. Node also has standard libs for almost everything. the reason people end up using so many dependencies is coz they are so super easy to use and reduces boilerplate.

I never say that Generics are bad, but unrelated to DRY. By the way, lack of generics support is a weakness of the language. It may be useful in some cases.

I don't see how it's useful

Yes, that's maybe easy as a reply, but http server is not where go has a lot of boilerplate ! With the error system yes (it's true it is verbose).

fair enough that was a bad example from my side, but I think you get my point

You hit the point. Go is annoying, because it is very simple. No magic, no shinning thing. And that's exactly what I expect from a language, but this is a personal opinion.

Absolutely, and that's my point, I personally prefer a middle ground. I don't like languages bloated with features as well. But Go is too simple for my taste. Maybe once Go has generics I would like it more.

Yes, and I respect your opinion. BUT you decide to expose theses though on a platform that allow comments and discussion, so (respectful) contradictory opinions are part of the game :)

Absolutely, that was the purpose so I can learn other views and may even change my mind on certain points. Thanks for your inputs they are indeed valuable. Another point I was trying to drive home was "using the right tools for the right job"

I don't see how it's useful

I wrote "It may be useful in some cases", "it" referring to Generics. So I guess we agree on this point.

Compare the default to github.com/stretchr/testify/assert and you will see what I mean

I know this lib (using it in the past), and I exactly know what you mean. But I still disagree on this point. Assertion reduce test code verbosity, but doesn't really improve readability. here is an example from one of my pet project. I do not think the usage of assertion will bring a lot of value here.

But the most important is that it is not considered idiomatic to do so in Go. Some articles on the subject here and here.

That's precisely the reason why I still use assertion in Javascript, Java or Ruby, event if I don't find them useful as much as I used to.

Thanks for your inputs they are indeed valuable

Thank you too :)

using the right tools for the right job

I definitely agree with that sentence. And my reaction is mainly drive by the will to show that Go may be a good choice for a web application. It is very opinionated and may look ugly, but is eventually very effective and productive in that use case.

With it's GC and embedded Runtime, Go is more closed to many web-backend plateform than it looks like on the first sight :).

I was talking about the readability of the error messages printed during assert failures. The default way is not very readable whereas this lib provides a nice view with a diff, which IMO is really useful to spot issues.

I would still be open to building web backends for microservices using Go, but for full-fledged real-world web apps how would you handle stuff like security, filters, etc, etc(say stuff provided by spring framework). I have to agree maybe I might be more open if I see a real example of that.

For example, I was thinking about doing a version of JHipster app in Go but then wasn't sure it's worth the effort

There is two possibilities here.

The first is to used the (very good) lib gorilla which provide a full featured http router with many security options (secured cookies, sessions, CSRF protection, etc...). Not the option I prefers, but a very convenient and common one. If you wish to support Go with JHispster, this is the way to follow I guess.

The second is to use the basic http router and to choose the lib you want to use (JsonWebToken, CSRF, etc ...) for security.
The http package is build in a way that make trivial to create filters (by chaining http.Handler or http.HandlerFunc).
I prefers using that way because I prefer to add explicitly security layers.

For SQL injection, the sql package provide automatically a protection against it, as long as you use parameterized requests with Exec() or Query().

You may notice that I am not against the use of dependencies :). Cryptographie and security are domains where I want to rely on a maintained, specialized libs.

 

Your What I don't like about Go section resonates with some of my experience. I like things about it in theory but in practice I do feel like I don't enjoy the verbosity, boilerplate and generally minimal tooling.

 

Exactly, thats well said I also had the same feeling. It was not as enjoyable as working in some other languages.

 

I agree about the dependency management but it's been quite a year since go mod birth. Today you can put your code wherever you want (outside of GOPATH) and have basic dependency management features like adding a new dependency, update an existing dependency, etc.

I agree that there is a lot of boilerplate code but it's not a problem for me as it helps to understand what the code does and IDEs have snippet features to quickly write this kind of code.

I develop in Go since 9 months also and what was confusing for me is the lack of conventional project structure. I started with my good old MVC layer structure and ended up with a more pragmatic structure inspired from golang-standards/project-layout.

Another pain in the ass is the lack of reflection in unit tests mocking libraries. Unlike mockito or phpunit, you have to use tools like testify or mockgen to generate mocks that you can use in your UT. It's a lot of extra work compared to PHP and Java.

But in the end, I still love Go :)

 

I thought putting projects in Go path is still the official recommendation. At least the website says so golang.org/doc/code.html

Could you point me to doc or an article explaining how to do it outside of GOPATH? I'm defineley interested sop that I can get rid of my current workarounds.

 

It must be in the official go mod doc on the wiki. I'll put the link when I'll be back to my desktop.

you can put code in anywhere once using go module
github.com/golang/go/wiki/Modules

 

Awesome post!. It has actual examples and well written.

All the wrong things seems kinda fixed on nim-lang.org

  • Generics: Nim has Generics since long time ago.
  • Error handling: Error handling, exceptions, custom exceptions, etc.
  • Default values: Yep, it can do it.
  • Not DRY: Hygienic Templates/Macros do DRY, Templates are simple.
  • Package Manager: Builtin package manager, similar to Cargo, scriptable.
  • Source code in GOPATH: Source code anywhere.
  • Confusing pointer: Nim supports Pointers out-of-the-box.
  • Struct hell: Nim supports literal JSON on code, no Struct, no String.
  • Weird interface: Uses Types, are like Objects or Classes.
  • Single GC algorithm: 6 GC algorithms, Go GC, RealTime GC, or no GC.
  • Developer experience: Syntax sugar is encouraged, sugary libs, DRY.

It also has a Rust-like memory management, without BorrowChecker (Experimental).
πŸ‘‘

 

Nim does look like an interesting alternative to both Go and Rust. I've been holding off on seriously considering it until they release Nim version 1.0 (due to stability concerns), but it looks like they are nearing that point with a release candidate.

 

Nim looks interesting, but I'm looking for a language that fixes issues I have with Go, for that I think there are many options out there already. Go has a purpose and It does some things really well, we need to appreciate that.

 
 

I never used Go before... it's on my list to try.

But really I just see Go as another hype just like React!

Really, lemme just write what I don't like about Go already:

  1. The syntax is funky (comparing to Python).

  2. Co-routine pattern for asynchronous programming never ticked for me... It's just weird. Aside from that, I think the only programming language that got async right is C#.

  3. I see people saying it provides great features like references (and that's what C# and Java provided long ago).

  4. Its frameworks/libraries are not mature... Cuz hey, it's a relatively new lang, and yeah no StackOverflow discussions when you get stuck!

From all those, I just ask myself again and again: what does Go bring new into the table? Not simplicity nor new features, so what?

TBH, I just hate to see people use a specific tech just because a big X company built it... Again, like React and Go 🀭

When people tell me it's for building high performance websites. Well, like: Google?!
While the 99.99 percent of websites they're building is never like Google needs πŸ™„

The only valid use case that I see is just building tools like Docker and Kuber in an easier lang than C/C++.

 

I wouldn't be that harsh. As I said I like Go for what it is, it's just that I think it could be a bit better. We can find issues in almost every programming language. The real takeaway from the article should be that we use the right tools for the right job.

 

Google has a serious case of β€œNot Invented Here” syndrome. Sometimes that works out, sometimes it doesn’t. I’ve not written any Go, but I’ve read enough of the Terraform codebase and of several k8s controllers to not be impressed. If they add generics I might take it for a spin, but I can’t yet see a usecase for Go where I wouldn’t just use Rust, Elixir, or F# instead.

 

Yeah exactly... I really can't understand what people think when they use the tools you mentioned just "cuz it's Google tool" 🀯

 

This is a great article, beyond the Go hipsterish trend.
I agree with all you said, both good and bad. I personally moved onto Rust because in my sense they found nice ways to cover the bad points of Golang however it comes with complexity and steep learning curve.
I feel like learning Go was actually a nice ladder to move onto Rust :D

Anyway spot on, great article, looking forward on your next ;)

 

As I said in the conclusion, we cannot brush aside Go like that. Go is still a great choice for many use cases like networking apps, concurrent apps, web servers and so on.

 

Well I agree on this too but as it happens I use Rust especially for those use-cases too ;)

 

I would only use Go as an alternative to C or in some cases C++, as it is easier to deal with than those languages and can compete with their speed.
Otherwise I would never consider it.

My main reason is that its simplicity often forces over complicated solutions that can be pretty difficult to understand in reasonable time.

 

I would choose Rust as replacement for such cases as its much more memory safe and equally performant

 

Following are just my reflections over 9 years, please do not get me wrong.

In India, most students start with Java in their college, its taught to engineering students.
They land jobs from college fairs, TCS, Infosys... they train them. Some unlucky ones get to work on non-programming projects, and some lucky ones actually get programming projects.

When they start working they praise Java.. spring (the mess of magic world)
Then they start bitching about Javascript, how javascript doesn't have a private variable (when they don't know JS has its way via Module pattern)

and in the same way, they start writing every other language with Java in their thinking process.

I started learning GO because of one of my colleague (hardcore Scala guy) he told me not to learn Java or Scala, but go for GO. And I'm happy with that decision.

 

I think you are over generalising based on your experience. I don't see what nationality or experience has anything to do with my views here. And NO, I didn't have the background stereotype you mentioned above. I started with C/C++, then learned PHP, JavaScript and then Java. And Generics weren't available in Java always. If you do a real project in Go, you would understand why I'm looking for Generics. Any strictly typed language with generics will be a PITA to work with and that is the reason Go is trying to add generics support in Go v2

 

The pain of working with Java is immense.

The need of an IDE,
so tightly coupled community with Spring,
the configs you need to set up to get a basic test running,
the code's feedback has to wait until java finishes its build for 30mins+,
old Java, cannot run on newer versions of Java

Hence I would prefer simplicity over complexity. i.e. GO

Well. I don't know what you are implying. I didn't say I prefer Java for everything and I said I prefer it for usecases it is best suited for. Also seems like you have lot of false assumptions about it. Java is not a simple language but its much simpler than Scala. In terms of language complexity Java is slightly more complex than JavaScript and way more complex than Go.

 

Hello Deepu, well i dont agree with you. You can do every thing with go. From server to batch to cli to moon landing ;) etc. It is ligtweight, fast et efficient. I am fed up of the same thing again and again : generics, error bla bla...
It is simple just like a mac and you spend less time to thinking on what keyword to use but more on the business code.

People are trying to transpose what they have learned from previous language to go, it is none sense. Go has it Own paradigm.

One thing for sure, it is ok to copy paste in go in some extense. This is what i found And it is fine, i am not shocked when i am doing reviews and see some duplication for sake of simplicity instead of a complex code for kind of generics, go is not a language for framework makers but it is for developers !!!!!

To be honest with you, i habe reborned after i started programming in go, i was so disappointed of other languages and now i enjoy more than ever coding . I have 20 years of experience in programming.

Cheers

 

Well, if it works great for you, then great and I'm happy for you.

I didn't say that you can't do things with Go, well you can also do everything with assembly or any other language as well right. One of the reasons for using high level languages is that you don't have to do everything yourself. I don't want to write boilerplate code and that is why I expect some language features. No language is perfect and so is Go, my post is about using what makes you most productive for the given use-case and not about proving how smart one is.

 

Thanks for the article, Deepu, I enjoyed it and find myself agreeing with a lot of your thoughts. At the same time, most of the language issues you found problematic didn't bother me much -- then again, I loved working in C/Win32API back in the day so I may have issues with causing myself pain. ;)

The one thing that did make me bonkers was GOPATH, and I agree with you completely. I haven't been able to work with Go for a while, but I swear I saw a utility/feature in IntelliJ Go products/plugins that would change your GOPATH to the current project directory when you opened it or something similar so that you could work as you describe.

Then again, maybe it was a dream. It's an awful nice one.

 

Yes a colleague of mine told me there was such a plugin for IntelliJ

 

I disagree with the point of using Spring for complex web application especially if you want to move into micro services architecture. It only adds complex configurations due to the poor documentation and Java isn't the best language for building web apps in my opinion.
Did you try frameworks like moleculerJS or Go micro?

 

Well, Java is used for building a lot of web applications(quite successfully) and IMO has the best ecosystem to do so. Also not everyone should be doing microservices since they are trendy, there is a lot issues in those which is a whole different topic. And Spring is perfectly suitable if you want to do microservices.

I haven't tried the frameworks you mentioned, will do when I find the time and will update the post if it changes my opinion.

 

Good article bro, just wait for your next reflection on V -> github.com/vlang/v