DEV Community

Valeria
Valeria

Posted on • Updated on • Originally published at valeriavg.dev

10 Reasons NOT to use Go for your next project

They say Go is the language of the web. Of course, with Google backing it up it sounds very objective! But is it as good as they say? I can think of cases where it wouldn't be a good fit:

1. You need your app to compile for at least an hour so you can get a break

Totally relatable. With Go compile speed you won't be able to even stand up from your chair, let alone grab a coffee! No, really, do they even compile it all?! I had TypeScript taking longer to check a small project than go run!

2. You love code puzzles

With Go, it's really hard to write frustrating code. No classes, no multiple inheritances, no overriding. Heck, there is only one way to do loops! It's almost impossible to create a great puzzling labyrinth of dependencies so that your team could enjoy a nice challenge every once in a while. Who would settle for a tool like that?!

3. You hate default values

In Go, every variable always has a value. An integer would be created with a 0, a string would be an empty string and so on. Why would you leave JavaScript with its variety of null, undefined and empty values for such a limited language?!

4. You don't like to handle errors

While Go programs can panic, the Go way of handling errors is to return them as a last return value and handle them explicitly or explicitly ignore them. Every! Time! Ugh! Where's the fun in it? So much boilerplate and so little debugging! Don't you feel the joy every time an exception is thrown somewhere inside of a dozen try{}catch{} wrappers?

5. You are a true patriot of your favourite language

Those devs these days have no loyalty, am I right? They hop from one mainstream train to another! I think they are just lazy! Back in the day, we were writing code in Notepad without any checks and we've done just fine. Nowadays some program does half of the programmer's job, no one even needs to remember the proper syntax or care about formatting. And Go is the worst: it won't even compile with an unused variable!

6. You like to watch spinners

Go is fast and so are the requests to API, written in Go. So fast that you'll barely see a spinner in the client application! Are we supposed to set a timeout now or what? How would the users enjoy our unique spinner designs if they can't see them?!

7. Your server is running on Windows'98

I don't know how you guys put up with it. Go won't even launch on my dedicated Windows'98 server! It was with me my whole career and now I'm supposed to abandon it?! For what? Your cloud servers have no personality! You won't even notice if it'd be replaced by another machine. And Go is all about the cloud!

8. Your hosting provider only supports PHP & MySQL

Who do you call a dinosaur?! This technology has withstood the test of time and no new fancy tech can say that. Go was released just the other day, in 2009, while PHP dates back to 1995!

9. Your application requires full control over memory

Jokes aside, everything comes at a price and Go is great for a lot of things, but Go was intentionally created "less memory safe". And as long as you run your application in a dedicated cloud container it will not become an issue, but if you're dealing with strict security requirements for a consumer application, you are probably better off with Rust or another system language.

10. You are writing embedded software or an OS

Go will only compile to a supported platform. You can't write a custom operational system in Go or run it on bare metal. Once again, Rust could back you up here since Go is simply not made for it.

I've run out of reasons not to write in Go. I love Rust, but Go is almost as fast yet much, much simpler to learn and write in. I love Node.js + TypeScript, but I love performance and runtime type-checks more.

For the next CLI or API, I'm going with Go. What about you?

Discussion (29)

Collapse
codenameone profile image
Shai Almog

Are you seriously trying to frame the lack of proper exception handling as a plus?

It's OK to advocate a language or platform. But this argument deters from the power of the other arguments.

Collapse
valeriavg profile image
Valeria Author

I'm sorry I'm not sure I'm following.
What is a proper exception handling in your opinion?

Go and Rust have a similar way of handling errors: (T,error) and Result<T, E>.
JavaScript, C++, Python and others do the try{}catch{}finally{} and try...except...finally, which causes the issues I mentioned in the post and aren't enforced in any way.

I mean sure it definitely fades in comparison to Windows'98 server argument, but otherwise looks legit :-)

Collapse
codenameone profile image
Shai Almog

So I'm a Java guy. But the Go approach isn't new or novel. We've used it in C, Objective-C and many other languages. There are reasons people came to the conclusion that exceptions are the way to Go.

The error response approach works great in a 5 line demo app. You open one or two files and yes you have an error right there and don't need to catch. That's OK for simple use case which again might be fine for Go as it's meant to replace C more than its meant to replace Java.

Exception handling shines when you have multiple, complex operations and 3rd party API calls. Handling the error for every call becomes a noisy affair of testing every call or wrapping everything you can. In Java we can just move that error handling up the stack. The user doesn't care that method X or Y failed. They care that that the operation as a whole failed which is why we have generic handling.

The developer gets a stack that pinpoints the error for our use case. Java also has checked exceptions which enforces error handling. I think people hate that feature because it was over used in some cases, but it's a wonderful feature that works well for most cases.

Thread Thread
valeriavg profile image
Valeria Author

Try/catch approach works great in a demo app. But in a big codebase it's easy to forget what command throws and what doesn't. Catching up the stack seems like a great idea till the point you decide that some errors need to be handled in a different way and you start wrapping calls in yet another try/catch. Eventually you end up with the system where you have no idea where exactly an exception would be caught or silenced. There is a reason people came up with explicit error handling. It gives you not only freedom to choose how to handle it (perform early return, accumulate all the errors in a batch or ignore it), but enforces handling it close to the source. This allows you to make changes to the code in isolation, regardless of where you you've decided to catch errors.

Thread Thread
codenameone profile image
Shai Almog

That's a nice reuse of my statements ;-)

Unfortunately this isn't correct.

Try catch came after explicitly returning an error code and response by reference. Java solved the problem of remembering what is thrown by adding checked exceptions. Pretty much every framework has generic fallback error handling so nothing is lost. Instrumentation, diagnostic and debuggers all support exception handling very well and make working with it super trivial.

Large code bases often added "fake" try/catch semantics to languages that had no business doing this e.g. C and C++ before it became part of the language. In C++ it always sucked because it needs a GC to work and yet it was added because the value is so obviously apparent.

Try/catch maps to hardware capabilities very well and can be handled very efficiently without adding multiple branch statements. Don't say "the optimizer will remove this"... It can't do that as well.

Thread Thread
dominikbraun profile image
Dominik Braun • Edited on

Handling the error for every call becomes a noisy affair of testing every call or wrapping everything you can. In Java we can just move that error handling up the stack. The user doesn't care that method X or Y failed. They care that that the operation as a whole failed which is why we have generic handling.

This is exactly what Go wants to avoid: Propagating an error up to a caller that has nothing to do with the actual error. Go wants you to handle errors immediately where they occur.

Other than that, your approach comes with a problem:

int myFunction() throws Exception {
    int a = calculateThis();
    int b = calculateThat(a);
    int c = calculateThose(a, b);

    return calculateResult(a, b, c);
}
Enter fullscreen mode Exit fullscreen mode

One of those functions throws an exception. Which one?

I've been a Java Developer myself and think that Exceptions are a good, but far from perfect approach. Rust does this better, and Go does it more verbose but also more explicit.

Thread Thread
codenameone profile image
Shai Almog

The one the stack trace points at. You would literally see the name of the problematic function and the line number in the stack. There's no chance that you will accidentally "forget" to log an error properly. No discipline required by the developer.

Thread Thread
valeriavg profile image
Valeria Author

As far as I know there are several types of exceptions in Java, we are referring to the ones that are not possible to check at compile time. Web applications deal with a lot of user input. Bad, malicious, unfiltered user input. That means that parsing a JSON string would fail, that some arrays would come with unexpected lengths and so forth. Go is made for web and there is no surprise it was designed this way, if you think about it. You don't want an HTTP server to hang the request, crash and restart, because someone sent some hacky request. You want to send a proper error response to the user. And often, when it comes to database issues, you also want to obfuscate the actual errors to avoid leaking your schema. With try/catch you need to either wrap every single call with it or create some horrendous upper level wrapper.

Thread Thread
codenameone profile image
Shai Almog

The most popular framework for Java developers (by far) is spring. It lets you define exception handlers and comes with default ones that would show the right response error if an exception makes it all the way up.

In Go you will either crash or completely miss the error (in case you didn't check) at which point the failure can cascade to something worse.

Try/catch is the exact opposite of wrapping every single call. I'm assuming you saw horrible code from people who just don't know how to use exception handling. E.g. In Spring boot I can define the exact exception types at the top level and then just throw whenever I want in the code. This will translate to the right error code to the user with all the details embedded into the response seamlessly. You can write any code you want and don't need to "deal" with errors for the most part. This is easy, efficient, maintainable, secure... Literally no downsides when compared to your approach.

My guess is that you saw some horrible try/catch nesting some novices do sometimes especially for older Java code where close() used to throw an IOException and we didn't have try with resources. The syntax is MUCH better now and the powerful capabilities you can layer on top of this (tooling, tracking etc.) is at a completely different level.

Thread Thread
valeriavg profile image
Valeria Author

You are missing the point. You CAN'T miss an error in Go. You are forced to do something about it. If you chose to ignore it, you do it at your own risk this way:

result, _ := doSomething()
Enter fullscreen mode Exit fullscreen mode

You need to handle the error explicitly. Go won't compile if you do this:

func doSometing() (int, error){
 return 42, nil
}

func main(){
// won't compile, you need to handle the second parameter
 result:= doSomething()
}
Enter fullscreen mode Exit fullscreen mode

And you are forced to write your functions this way, because the whole standard library is written like this.
You don't need a framework for it, you don't need discipline, you don't need to remember and thus you can concentrate or just writing the code.

And if you DO want try/catch "exceptions" you can recover from a panic:

// https://gobyexample.com/recover
package main

import "fmt"

func mayPanic() {
    panic("a problem")
}

func main() {

    defer func() {
        if r := recover(); r != nil {

            fmt.Println("Recovered. Error:\n", r)
        }
    }()

    mayPanic()

    fmt.Println("After mayPanic()")
}
Enter fullscreen mode Exit fullscreen mode

That's the whole point. Go produces fast apps, clean code and compiles fast. And Go alone is enough, you don't need a framework or even a third party library to build at HTTP server.

Thread Thread
codenameone profile image
Shai Almog

I hope this is an enjoyable debate to you. I don't want to come off "trollish" as in "my language is better than yours" sort of way. I think Go has a lot of interesting strengths, I just think the lack of exceptions isn't one of them. If this debate feels "iffy" let me know and I'll delete my comment with no hard feeling ;-)

You can miss an error in the same way you can miss handling an exception in Java. Miss an if or don't do a proper return when an error occurs and let it cascade. You're forced to call into the standard library like that but third party APIs don't need to follow that convention. Just like 3rd party Java APIs often convert checked exceptions to runtime exceptions.

Panic isn't try/catch. It's setjmp + longjmp. That's not practical at scale.

Go produces fast apps

So does Java, Rust and even C++. That's not a big deal.

clean code

We can debate that. I think that code that has to check for errors all the time is funky but as you clearly said, you disagree.

compiles fast

So does Java and most languages nowadays.

And Go alone is enough, you don't need a framework or even a third party library to build at HTTP server

This is a pretty bad claim. Your presenting lack of choice and diversity as an advantage?

Notice that Spring isn't an HTTP server. It's an application engine that includes everything from IoC onward and integrates dynamically with dozens of frameworks. That's a strength IMHO.

Thread Thread
dominikbraun profile image
Dominik Braun

The one the stack trace points at. You would literally see the name of the problematic function and the line number in the stack. There's no chance that you will accidentally "forget" to log an error properly. No discipline required by the developer.

Absolutely. However, when reading/browsing the source code, you don't know which methods throw which exceptions, where those exceptions are handled and what the control flow looks like. You immediately see what's happening when looking at Go (or Rust) source code - the control flow is very explicit and obvious. But this probably is a matter of personal preference.

Thread Thread
valeriavg profile image
Valeria Author

Oh my, no this isn't an enjoyable debate.
Your claims are based on your subjective perception and, if I'm honest, quite disconnected from the reality.
Go promotes diversity and code sharing much better than a lot of languages. You can literally use any repo as a package. Therefore there isn't a monopoly over http like it is in case of Spring, but plenty of options, yet you are not obligated to use them.
Leave the comments, there was a lot of examples readers might find useful, which was ultimately the goal of the article.

Thread Thread
ecyrbe profile image
ecyrbe

I'm programming in rust nowadays and like Go, error handling is explicit.
Rust has syntactic sugar with the "?" keyword to forward errors and let the caller handle the error. Does Go have the same feature ?

Thread Thread
valeriavg profile image
Valeria Author

Nope, Go doesn't have that.

Both Rust and Go use the return value as an indicator of an error and enforces handling it on spot, but that is the only similarity.

Go's error interface is defined by the presence of an Error() string method and cannot do anything, but becoming a string.

Rust's Result has handy methods like unwrap or expect, along with the sugar.

Collapse
nfrankel profile image
Nicolas Frankel

Go and Rust have a similar way of handling errors: (T,error) and Result

I'm clueless if it's an honest misunderstanding or just trolling. In the former case, I'm willing to exchange and try to explain why they are completely different.

Collapse
kasvith profile image
Kasun Vithanage • Edited on

Go and Rust have a similar way of handling errors: (T,error) and Result

They are similar looking but Rust forces you to handle this Result type by considering error or it wont compile at all. (unless you use unwrap() which is basically handling it somehow).

In Go, its not forced to handle the errors. You can easily skip errors and have them pop up in production without you ever knowing

For example in here I ve skipped error handling and the program still compiles.

data, _ := os.ReadFile("/tmp/data")
// use data anyway
Enter fullscreen mode Exit fullscreen mode

But in runtime, this can panic the program without the programmer ever knowing.

In Rust, this is a different story

let data = fs::read_to_string('/tmp/data')
        .expect("Something went wrong reading the file");
// data is present always
Enter fullscreen mode Exit fullscreen mode

In here without expect or proper error handling, program wont even compile.

So in my opinion,

  • Having to handle error where it occures is a Plus point in Go, its very easy to spot an error
  • Not having a way to safeguard against skipped error handling is a fatal mistake(i know IDEs can help, but still that can lead to problems in Prod)
Collapse
aitor profile image
dragonDScript

I mean, go's error handling is fine. I say it as a person that knows Rust's error handling, which is better but Go's is OK.

Collapse
_mohanmurali profile image
Mohan Murali

The sarcastic humor in this post is wonderful XD. I am learning go NOW!!

Collapse
valeriavg profile image
Valeria Author

Glad you liked it!
There are great resources but my favourite is gobyexample.com/ if you're looking for pointers

Collapse
_mohanmurali profile image
Mohan Murali

Thanks!

Collapse
michaelcurrin profile image
Michael Currin

Indeed I've often had the Rust vs Go debate and done research on it.

Rust is more for systems programming (where you care about optimizing a game or hardware or dealing with bytes). writing something high level ends up being verbose. And Rust is notoriously difficult to learn - esp using a 3rd new paradigm for memory management. But it is supposed to be easier than writing or learning C or C++ or at least has more safeguards to prevent you from writing code which is unsafe for memory. C and C++ can be performance but they can also be slow and wasteful if you them wrong

While Go is meant more for high level programming like Python or JS. Go is known for being much easier to learn than Rust.

The speed is similar for both. The fastest depends on the article you read and the article it was tested against.

Collapse
valeriavg profile image
Valeria Author

Exactly!
At this point I'm collecting languages like Pokémons. You gotta have them all! 😜
Each one has a niche and you never know what kind of wild project you'll be building next.

Collapse
marcello_h profile image
Marcelloh

The thing is, you can't win them all.
To advocate something that's not in the core (like Spring) is for me to giggle about.
The Gophers amongst the readers will know: we have all batteries included for our task.

I think, Valeria wrote this not to troll, but to let you think about certain stuff.

If Go happens to miss a feature from another language, it's probably because the community doesn't need it. It not a language made by hoarders, it just a tool to get things done in a very precise and strict way.

It's not a pi***ing contest, it just like it is: an opinion ;-)
Thanks Valeria for this unique glimpse into your brain: me likes :-)

Collapse
valeriavg profile image
Valeria Author

Thank you!
Nope, certainly not trolling.
And I'm glad you liked it :-)

Collapse
rakeshandrotula profile image
Rakesh Androtula

The title is totally misleading.. :)

Collapse
karanpratapsingh profile image
Karan Pratap Singh

Got me lol, fun post!

Collapse
zappellin profile image
Leon Guillaume

I was about to scold you and i saw the irony tag 😁

Collapse
valeriavg profile image
Valeria Author

😎