Coming from Python or Java, Go's standard library is worse, and it's syntax is hard to read. I don't get the appeal. Yeah, yeah, performance. Almost every Go project I've seen is not doing anything so ground breaking or performance intensive that this is an issue. Most applications where that is an actual non-func requirement end up going with C/C++ or Rust anyway. /rant
Goroutines are better than C# async/await but C# is still a better language than Go.
When Java 19 lands you can have best of both worlds. Virtual Threads are equivalent to goroutines but you have access to better languages like Java, Kotlin and Clojure.
I don't think virtual threads will be fully baked in time for production use at Java 19. IMO we'll be waiting at least one and probably two more releases.
Valhalla has also produced an amount of language machinery that results in most of the same issues as Go. (E.g. "is this function receiving an interface or concrete value?" becomes "do I have a class, value class, or primitive class?") The performance ripples of requiring value objects to be immutable also remains to be seen.
Yes technically as they have been merged but 19 isn't out until September, you can download early-access builds today but we won't see a release build until the official release date.
Sure, but by Java 21 we'll have Go 1.21 with a couple release cycles to push generics into stdlib, and we're back to more or less useless debates about aesthetic preferences and tooling priorities again.
Yeah goroutines is what i meant by better async. I've used the clojure async stuff a fair bit which was heavily inspired by goroutines and it is quite nice.
Stock gRPC is not a great demonstration of Go’s possible performance. The code is neither clean nor fast nor idiomatic. I have no idea why it’s so messed up.
(That said, C# and Java are no performance slouches and I expect they’d all be roughly comparable in the end if the Go code didn’t suck so much.)
Go isn't fast, it's decent but nothing special for AOT native code. (atleast the standard gc compiler, llgo, gccgo etc could end up being a different story). It can show good overall performance in I/O bound workloads because of native M:N green threading though.
I imagine even that is no longer a point in Go's favor when Java's Virtual Threads land and become widespread however.
Go's only real interesting runtime feature was their emphasis on having a very low latency GC. This was somewhat special for a few years but between improvements made to .NET and Java's new GCs (Shanandoah and ZGC) that gap has been eliminated while these runtimes still provide better throughput on top of their now low latency.
There really isn't a good performance or resource usage reason to pick Go over C#/Java anymore, all of its advantages have been eroded.
Go’s GC throughput isn’t great, but they’re pretty good at keeping values off the heap. Java’s GC is amazing, but we’ll have big working sets and boxing overhead until value types land.
I was poking into a 7-10 second test a couple years ago out of curiosity, and pulled a tracing profile.
Garbage collection accounted for over 95% of the run-time. It was a perfect pathological case where it continually thrashed below/above a GC threshold, without hitting the point where it would make more room. Adding just a couple kilobytes of ballast[1] pushed it over the limit to where it reliably took about 200ms. (and then I rewrote the test to stop allocating so much junk)
Go's GC is mostly pretty good in practice, but yeah. It's definitely not something you can rely on blindly.
Also I wish they'd stop claiming that its pause times are tiny - the stop-the-world pauses are indeed consistently very short, but it hardly matters when it can pause goroutines for far, far longer[2]. And e.g. when you're allocating, your goroutine may be paused to "help" the GC, which can take a substantial amount of time (as happened in that^ test), which frequently leads to very large tail-latency. Most people don't care about stop-the-world times, they care about tail-latency, and Go is just leaning on people's misunderstanding that one is the cause of the other. It can be, but it's not the only source.
It's still possible to pace the GC like this in certain cases, but most specific benchmarks before the preemptive scheduler introduced in Go 1.14 aren't particularly relevant anymore. Today I think that Twitch blog post does more harm than help. (And even at the time, while an interesting case to analyze, the right fix is usually "make less garbage", not "add some ballast".)
Oh yeah, completely agreed. The GC does keep improving, and that post is much less relevant than it used to be. It does still apply in some niches though :)
I think Go's GC is solidly "good enough", and much better than many languages, so I broadly like it. Coupled with built-in tracing that shows GC activity and it's in a pretty good position. It's more that all GCs have pathological behavior somewhere, and no amount of distracting hand-waving changes that.
All three use load-barriers which result in more work done in application threads as the pressure on the GC increases.
That said these GCs are substantially better than the Go GC so you are talking about much higher allocation throughput before running into these issues in practice vs Go.
> They originated from the same corporation which heavily leans on both
At Google's size this means nothing. The protobuf and gRPC developers don't even always align well, let alone with an entire language ecosystem.
By the same token, C#'s excellent gRPC performance is the result of MS dumping a chunk of resources into gRPC/PB optimization most recently. For Google it's cheaper to buy 2x as many nodes as it is to make Go gRPC 2x faster.
> For Google it's cheaper to buy 2x as many nodes as it is to make Go gRPC 2x faster.
That is definitely not true if Google was using Go gRPC at any reasonable scale.
A more accurate conclusion to draw from it is MSFT is using C# gRPC at scale and Google isn't using Go gRPC at scale. Which in itself makes a lot more sense because C# is the bread and butter language at MSFT for most large network services and Go isn't at Google, it barely has any penetration compared to Java and C++ (which predictably have much much faster gRPC and PB implementations).
I can get that Go's stdlib is smaller than Python's, but can you elaborate on why you feel that Go's syntax is hard to read compared to Java? Because both of them are very much C-like languages, syntax-wise.
if err != nil is continuously painful. f(g(), h()) with error handling takes nine lines, maybe twelve? Everybody has to use Errorf() everywhere or nobody gets a stack trace.
There are no stdlib filter/map/reduce yet, and generics are new enough that most people are still writing the same for-range loops over and over. Lambdas don’t capture loop variable values.
x, y := f() sometimes assigns and sometimes shadows an outer scope.
defer isn’t block scoped, you have to simulate the code to figure out which calls are stacked at this point. Hopefully they’re added deterministically, but you can keep deferring stuff in a loop if you want.
(This is aside from the many ways Scala and Kotlin are far more readable than Java.)
I thought that "if err != nil" is much less common now because of the changes in error handling. I personally do like "if err != nil" better. I can easily ignore that when I want, and I can easily check if I am handling the error properly without any cognitive overhead.
The fact that you think that error handling should be ignored is probably what prevents you from seeing the point of the Go error handling. The point is that one should never only consider the happy path. That one should actually think about how things can fail, what to do about those failures, and how to report those failures with enough context to figure something out on the outside if no appropriate handing is available in the code. Stack traces only give you line numbers. Properly used explicit error handling gives you more context in which a failure occurred.
If the only thing you're doing is saying `if err != nil { return nil, err }` anyway, which is what empirically the majority of go's error handling code does, yes, you should have a better way of representing that, whether it's a "?", ASSIGN_OR_RETURN, or exceptions, which take a single character, a single line, or a no characters at all, as opposed to golang's 3 lines of visual noise!
Saying that "you can use custom error types to give finer grained error information" is great and all, but I learned that in my CS101 class, in Java, in like 2012. It's not fancy or better or good, except compared to C. Its a worse way of doing a thing that Java and Python (and...) have been doing for decades.
Once again, you're referring to the conditional as “visual noise” as opposed to the actual thing that reminds you that there is a bifurcation here. The point is not to do this:
// Bad, most of the time.
result, err := foo(x)
if err != nil {
return err
}
But to do this:
// Better, most of the time.
result, err := foo(x)
if err != nil {
return fmt.Errorf("performing foo on x = %v: %s", x, err)
}
This isn't noise. This is meaningful logic. That is what exception mechanisms seem to discourage, as they are invisible (unless you wrap every call to everything that throws into a try{}, but then it's not better than an if{} is it?), and this is what the ? operator in some languages discourages as well, because it adds no human- or machine-readable context.
There must be syntax that indicates what to do when an error occurs, and that syntax must support addition of information about the context in which the error has occurred. And why not have the most basic conditional take the role of that syntax?
even if you wrap in try catch its worse because it moves the error to the bottom, messes with your scopes, and you can't tell which line actually threw
Or — you can wrap logical parts into a try-catch block, because it doesn’t really makes sense to throw 263 different errors for the same logical unit of work.
Also, bubbling up is the sane default, even if you are writing a fast script/trying out some code it won’t clutter anything and you will be notified to handle the problems once you want to write robust code. While in Go you will just put empty if errs and will forget about the whole thing, swallowing errors which are arguably the worst thing.
And it will bubble your error status just like you wanted, only takes 1 line of code, and keeps all the error information (assuming you're using absl::Status).
I haven't really used Go, but it sounds like the worst of both worlds.
> even if you are writing a fast script/trying out some code it won’t clutter anything and you will be notified to handle the problems once you want to write robust code
How are you going to go back and find every callsite that might potentially fail at a later date? There's no easy way to even tell if somethingThatMightFail might fail if it doesn't return something like a "StatusOr" or "Option" or something. I think Java has some tooling to force exception handling. Not sure about Go.
Well, Java has checked exceptions which have to be handled at call site, or be declared as being thrown in the method handler (making it ‘viral’). I believe these solve this “find every callsite later” thing quite well.
While Java’s exceptions are not without problems (too deep inheritance trees don’t make sense on exception types, lambdas not operate too well with this feature, etc), I think it is a really good direction not utilized to the full by any mainstream language.
That tells you absolutely no information a stacktrace doesn't. It's actually worse than just passing the err! Like, foo knew you failed performing foo, that's why you have the error, because foo failed. You don't need to reiterate that foo failed while performing foo, because the machine and user already know foo failed. See how annoying that kind of repetition is.
I've seen people that do that and it sounds nice until you end up with
Failed foo on x: failed bar on x: failed doing baz on x: failed to do quux".
You've created a worse stacktrace! I now have to search for a string and hope I can guess where the %s (or v or q) was instead of just grabbing the line number.
Or I do none of that, pass errors around directly, and just use the stacktrace.
The issue with your claim is that we only care about errors in two places, where we create them (like where the file was unable to be read) and where we handle them (when they are either swallowed bc the behavior was expected or passed to the user).
You don't care about them in all the intervening code, and exceptions handle that better.
> And why not have the most basic conditional take the role of that syntax?
Because it bloats your code and you end up with in practice more lines spent on verbose, cookie cutter error returning than actual logic!
Like, you don't need additional human readable context except in very particular cases. Go proves this in practice as almost everyone just returns the unmodified error most of the time, and it works great. Except for the annoying syntax.
> And why not have the most basic conditional take the role of that syntax?
Because having approx. 3/4 of your code devoted to error handling and error propagation is both hard to write and hard to read.
IMO rust's approach is close to ideal, in the common case where you just propagate up, the syntax is succinct, but still explicit, and when you handle it, the logic is close to the callsite, and is just working with a normal value.
Useful stack traces are still a bit of a problem, but there is work being done on that.
And that's not to mention how in go you always return both the error and regular values, even though you usually only use one.
Most of these are a part of the syntax though. When the parent comment talked about syntax, I'd assume that they said it the way most people say that Perl or Rust syntaxes are hard to read.
I agree about defer and the walrus operator though. Both can be quite confusing at times, and I think that Go with block-scoped defer and no walrus would be a more consistent language.
1) Function signatures do not read in the same way as most other languages. This "muscle memory" is a barrier to unlearn when moving to Go, and I don't see any good reason for it other than "being different".
2) No try/catch. Inexcusable in my opinion.
3) The syntax for declaring maps/arrays looks gross. Again, being different for the sake of it.
4) No while loops. The for loop syntax is not too bad, but I think this was again a choice made out of trying to simplify things when not a single person ever complained that "Java/C has too many types of loops".
I could go on, but in general my opinion is that Go deviated from some good established norms for no good reason, and this makes it much harder to adopt. It also isn't fun when you have a code base with e.g. Go and Java or Go and C# mixed in. The mental overhead of having an entirely different syntax to reason about is frustrating. And for what benefit? At least list comprehensions in Python are nice to read. I can't think of anything that is nice to read in Go.
You won't like Rust either then. Though its just a matter of time till Rust gets try-catch too. It's slowly moving towards that step by step. Folks have already made crates https://docs.rs/try-catch/latest/try_catch/
I haven't tried Rust out yet, but the syntax doesn't look appealing. However from what I've seen of the stdlib, it seems to make up for it. If Go had a better stdlib I could probably forgive some syntax problems. But having large gaps in both is very frustrating.
That crate seems to desugar to regular Rust style error handling, ie with Result<>s and ? operators. But I would imagine strong community push-back against any movement towards genuine stack unwind style exception handling in Rust beyond the current panic handling mechanism.
TBH 1,3 and 4 of your arguments are just subjective. You are used to something different and not willing to learn this way or dislike the appearance..
For 2 you can see it that way. Or you are glad there are no exceptions and prefer the explicitness of error handling in Go. So in the end it's also subjective I guess.
I remember when I first looked at Go ~7-8 years ago I also didn't like the syntax. However it grew on me and now I like it. Opinions change. These days I try to not instantly judge something on my initial feelings.
The appeal of go is that it is easy to learn, you can put any young person just out of college and have it produces a lot of line of code that resemble something working.
The reality is that go is a very hard language to master and to avoid footgun. But it is an hard truth that few people are willing to accept.
The difference between pointers/references and values is there in most languages in the same ilk as Go, maybe with the exception of Java. (C# has structs, which are value types.) It's a concept pretty much any developer in that area must know.
As for concurrency, that topic is hard in pretty much any mainstream language. Maybe with the exception of Rust, but Rust isn't exactly a langauge that is not “hard to master”, and even Rust doesn't protect you from all forms of concurrency issues. Merely most of them, heh.
Concurrency is a major stumbling block https://eng.uber.com/data-race-patterns-in-go/. Mutexs, conditional variable subtleties, wait groups, null pointer propagation, partial struct initialisations, channel lifetime coordination. tl;dr shared memory concurrency is hard.
The introduction of generics should make it easier to wrap these lower level concurrency primitives into higher level safe constructs without needing code generation or runtime type casting via interface{}.
Most devs also struggle to structure go programs in a way that makes them easily testable, but that’s true in a lot of languages.
Concurrency is hard generally, so I agree with that.
> The introduction of generics should make it easier to wrap these lower level concurrency primitives into higher level safe constructs without needing code generation or runtime type casting via interface{}.
That process has actually already begun with Go 1.19's sync/atomic.Pointer[T]. See https://pkg.go.dev/sync/atomic@master#Pointer. Things like easier non-blocking send and receive or easier fan-in and fan-out channels should probably follow suit.
> Most devs also struggle to structure go programs in a way that makes them easily testable, but that’s true in a lot of languages.
My experience especially relative to Java is the opposite - between httptest and sqlmock and the ease of setting up listeners in a separate goroutine, it's easy even for new Go developers to create new, realistic request-to-response functional (behavioral / Detroit-style / whatever they're called today) tests.
On the other hand, getting our long-time Java devs to hook up a reasonable wiremock test is like pulling teeth. They much prefer piles of redundant unit tests with a few Spring-injected "integration tests" against H2 and an in-process gRPC "endpoint" or whatever, that of course therefore aren't testing much integration at all.
Go may win out in the early days of a program for resource usage and perceived complexity but by the time your team has cooked up some frankenstien of an application framework and tried to standardize logging, metrics, etc you would have been better off in JVM land from the start.
At least then you can pick battle-tested and supported frameworks.
Python, Ruby, JS/TS -> Go is a weird one though. I can understand why when people make that transition they think Go is the best thing since sliced bread. It's by far one of the easiest languages to learn, it's miles faster than what they are coming from and the tooling is way less shit than those interpreted languages.
However all of those languages represent a very very low bar. I think if the same people could be convinced to try Kotlin (or even modern Java) they would be a) surprised how easy it is these days and b) finally understand that it's ecosystem and tooling that really matters at the end of the day.
Though I have to say Go existing is by and large a good thing even if I don't like it. It helps get people off things like Typescript which are absolute garbage-tier onto something half-reasonable. When they grow out of Go they generally end up somewhere better, just takes a few years.
One area where Go has quickly established a niche is in tooling, for example Terraform. Here the benefits of being able to run a single (non-containerised) binary vs having to fret about what version of Java your punters happen to have installed in compelling no?
Yeah that is fair. By and large such tools remain smaller codebases so it's flaws aren't as apparent either.
My personal preference for such tools is Rust but it's admittedly a harder sell.
Comment above is mostly addressing the server software use case with larger teams and 100KLOC+ (either one or many programs).
I don't think anyone was using Java much in the CLI niche. It was dominated mostly by Python and Ruby (and Perl before that) before Go came along. I do think Go represents an improvement on that status quo.
I often wish terraform wasn't written in go. Usually while making a patch to it, or reading the source code. Though not as much as I wish that terraform had used an existing "real" programming language rather than creating HCL.
Terraform not written in Go wouldn't have existed. If it relied on a JVM or Python/Ruby interpreter being present, at the correct version, it never would have gotten as popular, and if it were statistically compiled C/C++ it would have never gotten as far and as quickly.
Go offers a nice balance between performance and platform agnosticism, features like parallelism without it being too complex to write. Manual memory management would have been a useless obstacle wasting developer time for little real gain. Fighting the language to have parallel execution, easy crosscompile, etc. why?
As for HCL, I'm on the opinion that a DSL is better. It's even in the name, Infra as Code, not with code. You want your infrastructure declarations to be easily readable and supportable. Anyone who has used Terraform can read any HCL; your Python code doing the same could be terrible and unreadable by anyone not already on your team. Too much flexibility for something as critical can end badly, imho.
In any case, there's cdktf now, you can mix and match terraform with cdktf.
Disclaimer: I work at Hashicorp, opinions my own and have stayed the same since before joining
Yeah but Ruby and Python aren't relocatable so it's hard to ship a self-contained interpreter with the program itself. This is due to their reliance on system paths among other things.
You can trivially ship a self-contained JVM that is completely isolated within a single directory.
If it were created today rust might be a good option, although in the early days of rust, it probably wasn't mature enough. Although, it does require some amount of memory management, but to a lesser degree than c/c++.
> Go offers a nice balance between performance and platform agnosticism
I don't disagree with this. It's too bad the language itself has major problems (which I won't get into here).
> I'm on the opinion that a DSL is better
The two aren't mutually exclusive. What I want is a DSL embedded in a full programming language, like chef's dsl in ruby.
> You want your infrastructure declarations to be easily readable and supportable.
Absolutely.
> Anyone who has used Terraform can read any HCL; your Python code doing the same could be terrible and unreadable by anyone not already on your team
It's possible to write terrible and unreadable HCL too. I know because I've written some.
> In any case, there's cdktf now, you can mix and match terraform with cdktf.
Unfortunately, I haven't had a chance to look too deeply into it yet, although I definitely want to.
> The two aren't mutually exclusive. What I want is a DSL embedded in a full programming language, like chef's dsl in ruby.
I've seen some atrocious Ruby for Chef, so I'm not sure I'd agree here. The advantage of a DSL is that it limits how far you can go. In the case of HCL pre-v2, it had some limitations around dynamism, but now that's out of the way IMHO it strikes a decent balance.
You can write hardcore HCL, but IMHO it's still way easier to parse by a human that bad Python or JavaScript.
As for CDKTF, I'd recommend you do. You can mix and match like having a project with CDKTF in e.g. Python or Go use regular TF HCL modules, but also vice versa, defining a module in TypeScript and consuming it from regular Terraform. So you can have the extra flexibility ( with all of it's advantages and risks) on a per module basis, not all or nothing.
Sorry yes you were talking about transitioning from an existing Java situation and you're right, for the spaces that Java typically operates in I don't imagine there is much advantage in considering a switch to Go; it's as much an ecosystem factor as anything, the Java world has had a long time to mature, and while I dislike many aspects of it (assemblages of classes communicating in spooky hard-to-grok ways via oodles of config) it is at least battle-hardened.
To address your last point I think that style of Java has fallen out of favour in recent years.
It's still around, there is a lot of existing J2EE codebases but it's successor - Quarkus has done away with the vast majority of the config mess. I can't speak to how well it manages that transition though as I was never a big J2EE guy.
The other old offender, Spring, developed a new convention over configuration distribution called Spring Boot which requires minimal configuration and has pretty much displaced the old way of doing things entirely. It's been around for years as the default option at this point.
The way I see Java is that it moves slowly but tends to happily assimilate the best ideas from other languages, runtimes and frameworks and over time sheds it's warts (Java 8 era sun.internal.*, old Date/Time APIs, etc).
Not everyone seems to have an up to date opinion of Java because the last time they touched it was J2EE days or worse yet in university.
I'm not fond of Go, but yeah - it has completely replaced Python for my command-line-tool building and I'm much happier with it. It's almost enjoyable for that.
A lot of things about Go work out well in practice when there's a human watching it that can just slaughter the process if it starts going off the rails, and the trivial distribution and excellent startup performance make it almost perfect for CLIs. Small code works fairly well, and the sometimes-quite-yolo stdlib gets you to "works on my machine" for 99% of your needs quickly and that's often good enough for ad-hoc tooling (and when it isn't: just delete that non-UTF-8-named file, sheesh).
For large stuff? No please. The type system is far too weak, and the runtime has too many sharp corners. About the only thing I like about it for this is that runtime reflection is very tightly restricted, which prevents some of the worst insanity you see in Java.
I am not sure what you are trying to say. That Go does not have a place because Python and Java exist? My personal experience: I cannot get anything related to cryptography done in Java, thanks to its standard library. I would choose Go over both Python and Java. The choice depends on a lot of things. Regardless, all I get from your message is that you mind the existence of Go, which I believe is silly.
The solution to a good stdlib for eg crypto should not be a new language. This just complicates the landscape. C++ and Python have fantastic crypto libraries. Trying to get one included in Java would've been time better spent than coming up with an abomination of a new language. Go's stdlib has massive gaps in other areas (the fact you still have to write your own "contains" method for slices is a joke).
That is not a good faith argument.. the only long thing in your example is a name you yourself created, absolutely nothing inherent in the language.
But taking your argument in good faith, sure naming conventions are an important part of a language, but Java’s is not “obtusely long”, but “meaningful camel-case”.
1. snake_case - best of the best. words are separated, easy to read even if the words are not in english or latin
2. ShrtCmlCase - worse than snake. Pros: quite short, does not take whole monitor width. Cons: requires additional thinking and some sort of in-team guidelines. Very opinionated.
3. JavaCamelCase - worse than ShrtCmlCase. Pros: you can put in and describe anything in a single name of a class\variable. Cons: you spend too much attention on reading rather than analysing the program.
I can understand why Java uses long names though. Its oop and enterprise nature is to "blame". This kind of naming is unavoidable when you are building a giant monolith with hundreds or thousands of classes, all kind of inheritance nuances and trying to have all names actually mean and describe something.
I frankly don’t see a significant difference between 1 and 3. I do prefer snake case, but it is such an insignificant change that probably the IDE color scheme is an order of magnitude more meaningful.
Java's core syntax is very similar to existing languages. Function signatures, loops, var declarations, error handling. Excluding access restrictors (public/private), you could pass it off as C/C++/C#/Python with braces/PHP without $/etc.
There isn't a ton about Java's syntax to be super opinionated on, compared to Go, simply due to how unique Go's syntax is.
Idk, I don't see much difference between Java and Go (and similar languages). But Go feel lighter and more concise.
My main problem with Java code is that it's too hard to read due to endless variables\classes etc names and such. It's almost like reading some Oracle's documentation rather than reading code.
Go’s function signatures are very hard to parse in my opinion. Like I can literally extract every information from both C-like signatures, from Haskell-like ones as well as Scala/Kotlin-like (var: type), but Go’s is not intuitive at all.
Coming from Python in the field of web backends/webservices, I just find Java's ecosystem in general has too much ceremony. Go is fairly straight forward, to the point. I don't care too much for performance, but being lightweight on resources is a nice plus compared to Python (smaller VMs) and deployment is much easier. Yes, I do miss some language features (exceptions, classes...), but it's still very manageable. Of course Python has third party libraries for everything, but Go has all the basics for web services in the standard library. C++/Rust is out as I have no such performance requirement and GC is fine in my case. So that's it... Go fills a niche like a lot of other languages, so why not ?
Python is the slowest mainstream language by a factor of 10. You could have easily gotten that much increase by going to even JS. Go is not faster than other mainstream managed languages like C#, Java, JS .
Dotnet and C# are a killer combination and I frankly dont get why go has to be even considered.
Go syntax is very cognitively demanding. Maybe I am getting old or have become used to C#, but between reading well formed go and C# code, C# always wins for me.
You might as well say the same about Java (with select codebases), or even Python. Or, for the folks who have familiarity with different stacks, maybe something along the lines of Ruby, Node or whatever else they enjoy more.
Admittedly, a lot of it comes down to personal preference and familiarity with different technologies.
Though in regards to Go in particular, on a technical level:
- it's simple to build self-contained executables, it's up there with the likes of FreePascal in this regard
- it's also a relatively simple language that's easy to pick up and use, in contrast to how complex code can get with C# and Java
- it also has fewer footguns than C or C++ without the complexity of Rust (which some might prefer), although it doesn't try to do as much
- it has a decent community and lots of useful packages, even the included http libraries are nice to just get things done
- it doesn't have a history of challenging versioning and platform support, when compared with .NET (Windows only support with IIS for legacy projects, anyone?)
- the performance is generally good enough
All of that combined means that Go is nice for writing both quick utilities, as well as smaller and larger services alike. Just look at Nomad, a container orchestration platform, the executables of which can be shipped as a single binary: https://www.nomadproject.io/downloads
Overall, it's pretty good for just getting things done in a reasonable amount of time and a limited amount of headaches. For example, Python might lead to code that is easier to write and read, but packaging can be a nightmare. Java might be better for select enterprise systems, but configuration is generally a bit of a mess there. .NET is a good alternative, but some folks also try to steer clear of Microsoft and tooling on *nix can be limited, much like it is for Java (your best bet would be to use JetBrains commercial products, since MonoDevelop isn't all that popular anymore, Visual Studio Code plugins wouldn't give you a "full IDE" experience and there is nothing quite like Eclipse or NetBeans for .NET).
Further thoughts: the age of a language might also coincide with how loved or dreaded it is, since we simply don't see that many horrible enterprise monolith messes written in Go as of yet: https://earthly.dev/blog/brown-green-language/
> it's also a relatively simple language that's easy to pick up and use, in contrast to how complex code can get with C# and Java
Go is not a simple language. It has made choices of syntax that are baffling. Not to mention the mess that is exceptions and generics.
C# and Java, on the other hand, feel natural to read.
> it also has fewer footguns than C or C++ without the complexity of Rust (which some might prefer), although it doesn't try to do as much
C# is the same in this case. Far fewer footguns.
> it has a decent community and lots of useful packages, even the included http libraries are nice to just get things done
C# wins, but perhaps because it has been around for so long. But its ecosystem is very good.
> it doesn't have a history of challenging versioning and platform support, when compared with .NET (Windows only support with IIS for legacy projects, anyone?)
Things change, systems change, environments change. At present, C# and dotnet are truly cross platform and are amazing platforms.
> the performance is generally good enough
In Go, as your program size and team gets bigger, code management becomes harder. C#, along with Visual Studio is at its productive best, whether its with one developer or a 100 developers.
If it is the best option for you, then that's great - finding a good fit for your needs is what you should choose technology based on, after all. My own experiences do not allow me to agree with all of your arguments, nor do I think you or anyone else can make them in such an absolute manner (such as about the language complexity/scope) but I also acknowledge that people will have different experiences, even with the same technologies!
A trivial example of comparing the possible scope of any two languages in question, would be to glance at their lists of reserved words, for example https://go.dev/ref/spec#Keywords and https://docs.microsoft.com/en-us/dotnet/csharp/language-refe... However admittedly that's not a discussion that I have the time for, though I'm sure that lots of people here might share their own thoughts and experiences. Honestly, for web development, everything starting with .NET Core and the newer versions has been pretty cool, so it's not like you have to deal with the inherent complexity every day, unless someone gets a bit too clever.
The only thing I cannot agree with here strongly is your argument about Visual Studio: I yearn for the day when Microsoft will finally port it over to *nix, but if you don't need to support it as a developer platform then you're in a pretty nice place to be! For everyone else, in the mean time, I guess there is Rider https://www.jetbrains.com/rider/ (though, of course, a paid product)
> it's also a relatively simple language that's easy to pick up and use, in contrast to how complex code can get with C# and Java
I never really understood this claim. Arguably, assembly is even less complex of a language, yet you would be hard pressed to say it is readable. Primitive primitives don’t necessarily result in readable code. For example a declarative description of what should happen with a stream of data is much more readable in many cases than 4 nested for loops with breaks and ifs.
But back to the topic, while there is a (fortunately getting less and less relevant) branch of Java which is very into needless abstractions, the language is a very small and easy to pickup one, allowing very readable and concise programs.
> But back to the topic, while there is a (fortunately getting less and less relevant) branch of Java which is very into needless abstractions, the language is a very small and easy to pickup one, allowing very readable and concise programs.
How often do you actually just use the core language without one of the frameworks, though? I guess it largely depends on what you do. For example, if you use Java for web app development, one of the more popular frameworks out there is Spring or maybe Spring Boot, which will force you to deal with a large amount of abstractions.
So while the languages themselves might not be that dissimilar, the ecosystems and frameworks/libraries around them, which actually shape how you'll use them most of the time (depending on the context), will most certainly lead to the differences being more pronounced.
In practice, Spring will not be much more than putting some annotation here and there (which is strangely enough what others like about Python’s Flask for example), it is very much not the XML-hell from decades ago - even Java EE is much better nowadays.
> In practice, Spring will not be much more than putting some annotation here and there...
As someone who's worked with quite a few (monolithic) enterprise projects, I couldn't disagree more!
The regular Spring framework can turn out to be a total mess of various intertwined .xml configuration files, based on whatever the project needs - access control mechanisms or integrations with external providers (say, LDAP or Kerberos), perhaps various filters for processing web requests (transformations, logging, additional flow controls), things to make any number of integrated libraries work correctly (JSR310 integration with Jackson, for example), all of which gets even worse if you need to get server side rendering in any of the older technologies working correctly, like JSF and PrimeFaces. You can get a variety of things done with Spring, true, but in practice it's going to be brittle and needlessly verbose.
The Spring Boot framework is a marked improvement, however you will still need a whole bunch of configuration in your .properties file(s), or maybe your YAML configuration if someone has opted to use that format. Of course, you shouldn't forget that you still need to figure out how to ensure that the profiles are loaded correctly and that you somehow haven't screwed up the settings in each file. But with Spring Boot, all of the sudden you need to also figure out which configuration related annotations you need and which ones could cause problems - how to better figure out passing DB connections to myBatis or Hibernate or whatever and also ensure that the session parameters are set as needed (say, default locale, so you don't get issues with Oracle).
Spring Boot simply takes a lot of the XML hell and turns it into a Java hell - better, because you're working with a proper programming language, but still not quite there yet, because a lot of the problems will only manifest at runtime (ever needed @Lazy with @Autowired?), which isn't fun when your app with thousands of source files takes minutes to start up so you can check everything out. Personally, I blame reflection and dynamic behavior for a lot of these problems, which is far harder to reason about than your IDE telling you that what you're attempting to write is weird and won't work before you launch everything.
Of course, my dislike of Spring is shaped by the utter messes of projects that I've had to work with, which is largely the inevitable outcome for any monolithic codebase that has to be kept around and is important to a business for close to a decade but also with limited resources (say, unlike the Linux kernel). That's also why previously I mentioned the "Green Vs. Brown Programming Languages" article - you tend to dislike some stacks once you've seen what long term projects in them end up looking like.
So, if you can, it's probably best to stick to smaller projects and microservices so these pain points don't become readily apparent and then most frameworks would be passable - be it Spring, Spring Boot or most others. Though I sometimes wonder whether people who wrote COBOL and FORTRAN also found starting out and having smaller projects to be enjoyable. Somehow we talk about languages and frameworks, but not how much of a difference there can be between a new project and an older one - some sort of a maintainability degradation coefficient that would express how maintainability changes over time.
I am by no means in disagreement with you, sure, spring (and javaee) monorepos can grow to barely maintainable complexities, but with the amount of features you listed I have to wonder, would it look any better in another stack? That huge complexity has to live somewhere, and I don’t know of any silver bullet platform managing to remain easy to maintain down that far the “add this feature as well” line.
I would even go as far and claim that the reason Java has this reputation of huge complexity in every monorepo is partly the effect of survivorship bias. Some other stack may have crumbled long before growing to such a monstrosity.
Nonetheless, I would also like to have more of compile-time DI, records, etc, but we shouldn’t just reinvent everything in a heartbeat.
It's the opposite for me. C# is becoming a complete clusterfuck of half baked features, bad historical choices, fear from Microsoft product owners to clean up the language and deprecate some old shit that is just polluting the language and making it hard for new beginners to learn and the worst of all, C# has a huge identity crisis and it shows in the yearly updates. The language changes so much every year that it's too tiresome to keep up with, it's increasingly more confusing and it is very unclear if C# wants to be an OOP language, an FP language or some weird jack of all trades. It's a horrible choice for beginners and I have seen many newbies who absolutely got frustrated by C#.
Go is very neat, true to its core identity and very very stable. I have been developing .NET (C# and F#) for almost 20 years and I don't enjoy it at all anymore.
EDIT:
Just last week the .NET team tweeted that they are looking to introduce green threads to .NET after all. I love the green thread implementation in Go and I think it is superior to .NET's async/await model which get scheduled by OS threads, but .NET has gone down that route many years ago and it's embedded everywhere now and introducing such a new fundamental change now is just plain fucking stupid. Microsoft is so dumb for even thinking this is a good thing to do. If they don't like how C# works then invent a new language with modern fundamentals which you like but stop changing C# so much that it's become completely unrecognisable. .NET developers don't want to rewrite their code bases every 12 months. We have real work to do.
The go1.19beta1 binary (which you would use to try Go 1.19 Beta 1) should be placed in the usual place where 'go install' places them. If you’re using Go’s defaults, it’s likely $HOME/go/bin, unless you’ve set either GOPATH or GOBIN env vars to point somewhere else.
106 comments
[ 3.4 ms ] story [ 127 ms ] threadWhen Java 19 lands you can have best of both worlds. Virtual Threads are equivalent to goroutines but you have access to better languages like Java, Kotlin and Clojure.
Valhalla has also produced an amount of language machinery that results in most of the same issues as Go. (E.g. "is this function receiving an interface or concrete value?" becomes "do I have a class, value class, or primitive class?") The performance ripples of requiring value objects to be immutable also remains to be seen.
I think by Java 21 they will be rock solid though which isn't really that far away in Java timescales.
Immutability is trivial to optimize away.
(That said, C# and Java are no performance slouches and I expect they’d all be roughly comparable in the end if the Go code didn’t suck so much.)
Go isn't fast, it's decent but nothing special for AOT native code. (atleast the standard gc compiler, llgo, gccgo etc could end up being a different story). It can show good overall performance in I/O bound workloads because of native M:N green threading though. I imagine even that is no longer a point in Go's favor when Java's Virtual Threads land and become widespread however.
Go's only real interesting runtime feature was their emphasis on having a very low latency GC. This was somewhat special for a few years but between improvements made to .NET and Java's new GCs (Shanandoah and ZGC) that gap has been eliminated while these runtimes still provide better throughput on top of their now low latency.
There really isn't a good performance or resource usage reason to pick Go over C#/Java anymore, all of its advantages have been eroded.
Garbage collection accounted for over 95% of the run-time. It was a perfect pathological case where it continually thrashed below/above a GC threshold, without hitting the point where it would make more room. Adding just a couple kilobytes of ballast[1] pushed it over the limit to where it reliably took about 200ms. (and then I rewrote the test to stop allocating so much junk)
Go's GC is mostly pretty good in practice, but yeah. It's definitely not something you can rely on blindly.
Also I wish they'd stop claiming that its pause times are tiny - the stop-the-world pauses are indeed consistently very short, but it hardly matters when it can pause goroutines for far, far longer[2]. And e.g. when you're allocating, your goroutine may be paused to "help" the GC, which can take a substantial amount of time (as happened in that^ test), which frequently leads to very large tail-latency. Most people don't care about stop-the-world times, they care about tail-latency, and Go is just leaning on people's misunderstanding that one is the cause of the other. It can be, but it's not the only source.
[1]: https://blog.twitch.tv/en/2019/04/10/go-memory-ballast-how-i...
[2]: http://big-elephants.com/2018-09/unexpected-gc-pauses/ among other sources of individual goroutine pauses.
I think Go's GC is solidly "good enough", and much better than many languages, so I broadly like it. Coupled with built-in tracing that shows GC activity and it's in a pretty good position. It's more that all GCs have pathological behavior somewhere, and no amount of distracting hand-waving changes that.
All three use load-barriers which result in more work done in application threads as the pressure on the GC increases.
That said these GCs are substantially better than the Go GC so you are talking about much higher allocation throughput before running into these issues in practice vs Go.
At Google's size this means nothing. The protobuf and gRPC developers don't even always align well, let alone with an entire language ecosystem.
By the same token, C#'s excellent gRPC performance is the result of MS dumping a chunk of resources into gRPC/PB optimization most recently. For Google it's cheaper to buy 2x as many nodes as it is to make Go gRPC 2x faster.
That is definitely not true if Google was using Go gRPC at any reasonable scale.
A more accurate conclusion to draw from it is MSFT is using C# gRPC at scale and Google isn't using Go gRPC at scale. Which in itself makes a lot more sense because C# is the bread and butter language at MSFT for most large network services and Go isn't at Google, it barely has any penetration compared to Java and C++ (which predictably have much much faster gRPC and PB implementations).
There are no stdlib filter/map/reduce yet, and generics are new enough that most people are still writing the same for-range loops over and over. Lambdas don’t capture loop variable values.
x, y := f() sometimes assigns and sometimes shadows an outer scope.
defer isn’t block scoped, you have to simulate the code to figure out which calls are stacked at this point. Hopefully they’re added deterministically, but you can keep deferring stuff in a loop if you want.
(This is aside from the many ways Scala and Kotlin are far more readable than Java.)
How do you easily ignore the majority of the screen? Is there an editor plugin that just doesn’t display those ifs?
Here's one example that might make it clearer: https://commandcenter.blogspot.com/2017/12/error-handling-in....
If the only thing you're doing is saying `if err != nil { return nil, err }` anyway, which is what empirically the majority of go's error handling code does, yes, you should have a better way of representing that, whether it's a "?", ASSIGN_OR_RETURN, or exceptions, which take a single character, a single line, or a no characters at all, as opposed to golang's 3 lines of visual noise!
Saying that "you can use custom error types to give finer grained error information" is great and all, but I learned that in my CS101 class, in Java, in like 2012. It's not fancy or better or good, except compared to C. Its a worse way of doing a thing that Java and Python (and...) have been doing for decades.
There must be syntax that indicates what to do when an error occurs, and that syntax must support addition of information about the context in which the error has occurred. And why not have the most basic conditional take the role of that syntax?
Also, bubbling up is the sane default, even if you are writing a fast script/trying out some code it won’t clutter anything and you will be notified to handle the problems once you want to write robust code. While in Go you will just put empty if errs and will forget about the whole thing, swallowing errors which are arguably the worst thing.
try { let res = somethingThatMightFail(); catch(err) { // log the error or something return null; }
// res is good now... but it's out of scope
Whereas in C++ you'd just write
ASSIGN_OR_RETURN(auto res, somethingThatMightFail())
And it will bubble your error status just like you wanted, only takes 1 line of code, and keeps all the error information (assuming you're using absl::Status).
I haven't really used Go, but it sounds like the worst of both worlds.
> even if you are writing a fast script/trying out some code it won’t clutter anything and you will be notified to handle the problems once you want to write robust code
How are you going to go back and find every callsite that might potentially fail at a later date? There's no easy way to even tell if somethingThatMightFail might fail if it doesn't return something like a "StatusOr" or "Option" or something. I think Java has some tooling to force exception handling. Not sure about Go.
While Java’s exceptions are not without problems (too deep inheritance trees don’t make sense on exception types, lambdas not operate too well with this feature, etc), I think it is a really good direction not utilized to the full by any mainstream language.
I've seen people that do that and it sounds nice until you end up with
Failed foo on x: failed bar on x: failed doing baz on x: failed to do quux".
You've created a worse stacktrace! I now have to search for a string and hope I can guess where the %s (or v or q) was instead of just grabbing the line number.
Or I do none of that, pass errors around directly, and just use the stacktrace.
The issue with your claim is that we only care about errors in two places, where we create them (like where the file was unable to be read) and where we handle them (when they are either swallowed bc the behavior was expected or passed to the user).
You don't care about them in all the intervening code, and exceptions handle that better.
> And why not have the most basic conditional take the role of that syntax?
Because it bloats your code and you end up with in practice more lines spent on verbose, cookie cutter error returning than actual logic!
Like, you don't need additional human readable context except in very particular cases. Go proves this in practice as almost everyone just returns the unmodified error most of the time, and it works great. Except for the annoying syntax.
Because having approx. 3/4 of your code devoted to error handling and error propagation is both hard to write and hard to read.
IMO rust's approach is close to ideal, in the common case where you just propagate up, the syntax is succinct, but still explicit, and when you handle it, the logic is close to the callsite, and is just working with a normal value. Useful stack traces are still a bit of a problem, but there is work being done on that.
And that's not to mention how in go you always return both the error and regular values, even though you usually only use one.
I agree about defer and the walrus operator though. Both can be quite confusing at times, and I think that Go with block-scoped defer and no walrus would be a more consistent language.
2) No try/catch. Inexcusable in my opinion.
3) The syntax for declaring maps/arrays looks gross. Again, being different for the sake of it.
4) No while loops. The for loop syntax is not too bad, but I think this was again a choice made out of trying to simplify things when not a single person ever complained that "Java/C has too many types of loops".
I could go on, but in general my opinion is that Go deviated from some good established norms for no good reason, and this makes it much harder to adopt. It also isn't fun when you have a code base with e.g. Go and Java or Go and C# mixed in. The mental overhead of having an entirely different syntax to reason about is frustrating. And for what benefit? At least list comprehensions in Python are nice to read. I can't think of anything that is nice to read in Go.
You won't like Rust either then. Though its just a matter of time till Rust gets try-catch too. It's slowly moving towards that step by step. Folks have already made crates https://docs.rs/try-catch/latest/try_catch/
For 2 you can see it that way. Or you are glad there are no exceptions and prefer the explicitness of error handling in Go. So in the end it's also subjective I guess.
I remember when I first looked at Go ~7-8 years ago I also didn't like the syntax. However it grew on me and now I like it. Opinions change. These days I try to not instantly judge something on my initial feelings.
The reality is that go is a very hard language to master and to avoid footgun. But it is an hard truth that few people are willing to accept.
What makes go powerful also make it weak.
In general the complexity need to live somewhere and in go it can only be the code itself as the language is too simple.
As for concurrency, that topic is hard in pretty much any mainstream language. Maybe with the exception of Rust, but Rust isn't exactly a langauge that is not “hard to master”, and even Rust doesn't protect you from all forms of concurrency issues. Merely most of them, heh.
The introduction of generics should make it easier to wrap these lower level concurrency primitives into higher level safe constructs without needing code generation or runtime type casting via interface{}.
Most devs also struggle to structure go programs in a way that makes them easily testable, but that’s true in a lot of languages.
> The introduction of generics should make it easier to wrap these lower level concurrency primitives into higher level safe constructs without needing code generation or runtime type casting via interface{}.
That process has actually already begun with Go 1.19's sync/atomic.Pointer[T]. See https://pkg.go.dev/sync/atomic@master#Pointer. Things like easier non-blocking send and receive or easier fan-in and fan-out channels should probably follow suit.
My experience especially relative to Java is the opposite - between httptest and sqlmock and the ease of setting up listeners in a separate goroutine, it's easy even for new Go developers to create new, realistic request-to-response functional (behavioral / Detroit-style / whatever they're called today) tests.
On the other hand, getting our long-time Java devs to hook up a reasonable wiremock test is like pulling teeth. They much prefer piles of redundant unit tests with a few Spring-injected "integration tests" against H2 and an in-process gRPC "endpoint" or whatever, that of course therefore aren't testing much integration at all.
Go may win out in the early days of a program for resource usage and perceived complexity but by the time your team has cooked up some frankenstien of an application framework and tried to standardize logging, metrics, etc you would have been better off in JVM land from the start. At least then you can pick battle-tested and supported frameworks.
Python, Ruby, JS/TS -> Go is a weird one though. I can understand why when people make that transition they think Go is the best thing since sliced bread. It's by far one of the easiest languages to learn, it's miles faster than what they are coming from and the tooling is way less shit than those interpreted languages.
However all of those languages represent a very very low bar. I think if the same people could be convinced to try Kotlin (or even modern Java) they would be a) surprised how easy it is these days and b) finally understand that it's ecosystem and tooling that really matters at the end of the day.
Though I have to say Go existing is by and large a good thing even if I don't like it. It helps get people off things like Typescript which are absolute garbage-tier onto something half-reasonable. When they grow out of Go they generally end up somewhere better, just takes a few years.
My personal preference for such tools is Rust but it's admittedly a harder sell.
Comment above is mostly addressing the server software use case with larger teams and 100KLOC+ (either one or many programs).
I don't think anyone was using Java much in the CLI niche. It was dominated mostly by Python and Ruby (and Perl before that) before Go came along. I do think Go represents an improvement on that status quo.
Kubernetes, Docker, Terraform and their respective ecosystems really aren't what one would call a smaller codebase.
Go offers a nice balance between performance and platform agnosticism, features like parallelism without it being too complex to write. Manual memory management would have been a useless obstacle wasting developer time for little real gain. Fighting the language to have parallel execution, easy crosscompile, etc. why?
As for HCL, I'm on the opinion that a DSL is better. It's even in the name, Infra as Code, not with code. You want your infrastructure declarations to be easily readable and supportable. Anyone who has used Terraform can read any HCL; your Python code doing the same could be terrible and unreadable by anyone not already on your team. Too much flexibility for something as critical can end badly, imho.
In any case, there's cdktf now, you can mix and match terraform with cdktf.
Disclaimer: I work at Hashicorp, opinions my own and have stayed the same since before joining
As far as I know, it's not hard to ship a JVM runner with the installer of your program, so this should be a non-issue.
You can trivially ship a self-contained JVM that is completely isolated within a single directory.
> Go offers a nice balance between performance and platform agnosticism
I don't disagree with this. It's too bad the language itself has major problems (which I won't get into here).
> I'm on the opinion that a DSL is better
The two aren't mutually exclusive. What I want is a DSL embedded in a full programming language, like chef's dsl in ruby.
> You want your infrastructure declarations to be easily readable and supportable.
Absolutely.
> Anyone who has used Terraform can read any HCL; your Python code doing the same could be terrible and unreadable by anyone not already on your team
It's possible to write terrible and unreadable HCL too. I know because I've written some.
> In any case, there's cdktf now, you can mix and match terraform with cdktf.
Unfortunately, I haven't had a chance to look too deeply into it yet, although I definitely want to.
I've seen some atrocious Ruby for Chef, so I'm not sure I'd agree here. The advantage of a DSL is that it limits how far you can go. In the case of HCL pre-v2, it had some limitations around dynamism, but now that's out of the way IMHO it strikes a decent balance.
You can write hardcore HCL, but IMHO it's still way easier to parse by a human that bad Python or JavaScript.
As for CDKTF, I'd recommend you do. You can mix and match like having a project with CDKTF in e.g. Python or Go use regular TF HCL modules, but also vice versa, defining a module in TypeScript and consuming it from regular Terraform. So you can have the extra flexibility ( with all of it's advantages and risks) on a per module basis, not all or nothing.
It's still around, there is a lot of existing J2EE codebases but it's successor - Quarkus has done away with the vast majority of the config mess. I can't speak to how well it manages that transition though as I was never a big J2EE guy.
The other old offender, Spring, developed a new convention over configuration distribution called Spring Boot which requires minimal configuration and has pretty much displaced the old way of doing things entirely. It's been around for years as the default option at this point.
The way I see Java is that it moves slowly but tends to happily assimilate the best ideas from other languages, runtimes and frameworks and over time sheds it's warts (Java 8 era sun.internal.*, old Date/Time APIs, etc).
Not everyone seems to have an up to date opinion of Java because the last time they touched it was J2EE days or worse yet in university.
A lot of things about Go work out well in practice when there's a human watching it that can just slaughter the process if it starts going off the rails, and the trivial distribution and excellent startup performance make it almost perfect for CLIs. Small code works fairly well, and the sometimes-quite-yolo stdlib gets you to "works on my machine" for 99% of your needs quickly and that's often good enough for ad-hoc tooling (and when it isn't: just delete that non-UTF-8-named file, sheesh).
For large stuff? No please. The type system is far too weak, and the runtime has too many sharp corners. About the only thing I like about it for this is that runtime reflection is very tightly restricted, which prevents some of the worst insanity you see in Java.
Excuse me but Java? This is as subjective as it can be.
All java code be lie `public static void SomeReallyLongNameDescribingEverthing42 implements SomeOtherEvenLongerName`
For me it's 10 times harder to follow than Go's `if err` blocks (which I actually do appreciate most of the time).
CamelCase makes it even worse. Short CamelCase is quite okay, but the longer it gets, the harder it on my eyes.
But even with short naming it still like 2-3 words just to name a function. wtf.
1. snake_case - best of the best. words are separated, easy to read even if the words are not in english or latin
2. ShrtCmlCase - worse than snake. Pros: quite short, does not take whole monitor width. Cons: requires additional thinking and some sort of in-team guidelines. Very opinionated.
3. JavaCamelCase - worse than ShrtCmlCase. Pros: you can put in and describe anything in a single name of a class\variable. Cons: you spend too much attention on reading rather than analysing the program.
I can understand why Java uses long names though. Its oop and enterprise nature is to "blame". This kind of naming is unavoidable when you are building a giant monolith with hundreds or thousands of classes, all kind of inheritance nuances and trying to have all names actually mean and describe something.
As I said earlier - all are very subjective topics.
There isn't a ton about Java's syntax to be super opinionated on, compared to Go, simply due to how unique Go's syntax is.
My main problem with Java code is that it's too hard to read due to endless variables\classes etc names and such. It's almost like reading some Oracle's documentation rather than reading code.
Golang date formatting on the other hand... I still mad an whoever is responsible for this.
I then rewrote the backend in Go, which gave a 90% performance increase.
It could now run all the streams on any normal pc.
Go syntax is very cognitively demanding. Maybe I am getting old or have become used to C#, but between reading well formed go and C# code, C# always wins for me.
Admittedly, a lot of it comes down to personal preference and familiarity with different technologies.
Though in regards to Go in particular, on a technical level:
All of that combined means that Go is nice for writing both quick utilities, as well as smaller and larger services alike. Just look at Nomad, a container orchestration platform, the executables of which can be shipped as a single binary: https://www.nomadproject.io/downloadsOverall, it's pretty good for just getting things done in a reasonable amount of time and a limited amount of headaches. For example, Python might lead to code that is easier to write and read, but packaging can be a nightmare. Java might be better for select enterprise systems, but configuration is generally a bit of a mess there. .NET is a good alternative, but some folks also try to steer clear of Microsoft and tooling on *nix can be limited, much like it is for Java (your best bet would be to use JetBrains commercial products, since MonoDevelop isn't all that popular anymore, Visual Studio Code plugins wouldn't give you a "full IDE" experience and there is nothing quite like Eclipse or NetBeans for .NET).
Further thoughts: the age of a language might also coincide with how loved or dreaded it is, since we simply don't see that many horrible enterprise monolith messes written in Go as of yet: https://earthly.dev/blog/brown-green-language/
Also doable in dotnet
> it's also a relatively simple language that's easy to pick up and use, in contrast to how complex code can get with C# and Java
Go is not a simple language. It has made choices of syntax that are baffling. Not to mention the mess that is exceptions and generics.
C# and Java, on the other hand, feel natural to read.
> it also has fewer footguns than C or C++ without the complexity of Rust (which some might prefer), although it doesn't try to do as much
C# is the same in this case. Far fewer footguns.
> it has a decent community and lots of useful packages, even the included http libraries are nice to just get things done
C# wins, but perhaps because it has been around for so long. But its ecosystem is very good.
> it doesn't have a history of challenging versioning and platform support, when compared with .NET (Windows only support with IIS for legacy projects, anyone?)
Things change, systems change, environments change. At present, C# and dotnet are truly cross platform and are amazing platforms.
> the performance is generally good enough
In Go, as your program size and team gets bigger, code management becomes harder. C#, along with Visual Studio is at its productive best, whether its with one developer or a 100 developers.
A trivial example of comparing the possible scope of any two languages in question, would be to glance at their lists of reserved words, for example https://go.dev/ref/spec#Keywords and https://docs.microsoft.com/en-us/dotnet/csharp/language-refe... However admittedly that's not a discussion that I have the time for, though I'm sure that lots of people here might share their own thoughts and experiences. Honestly, for web development, everything starting with .NET Core and the newer versions has been pretty cool, so it's not like you have to deal with the inherent complexity every day, unless someone gets a bit too clever.
Of course, less isn't always better, as certain other languages might demonstrate (a silly example, but still): https://en.wikipedia.org/wiki/Brainfuck
The only thing I cannot agree with here strongly is your argument about Visual Studio: I yearn for the day when Microsoft will finally port it over to *nix, but if you don't need to support it as a developer platform then you're in a pretty nice place to be! For everyone else, in the mean time, I guess there is Rider https://www.jetbrains.com/rider/ (though, of course, a paid product)
I never really understood this claim. Arguably, assembly is even less complex of a language, yet you would be hard pressed to say it is readable. Primitive primitives don’t necessarily result in readable code. For example a declarative description of what should happen with a stream of data is much more readable in many cases than 4 nested for loops with breaks and ifs.
But back to the topic, while there is a (fortunately getting less and less relevant) branch of Java which is very into needless abstractions, the language is a very small and easy to pickup one, allowing very readable and concise programs.
How often do you actually just use the core language without one of the frameworks, though? I guess it largely depends on what you do. For example, if you use Java for web app development, one of the more popular frameworks out there is Spring or maybe Spring Boot, which will force you to deal with a large amount of abstractions.
So while the languages themselves might not be that dissimilar, the ecosystems and frameworks/libraries around them, which actually shape how you'll use them most of the time (depending on the context), will most certainly lead to the differences being more pronounced.
As someone who's worked with quite a few (monolithic) enterprise projects, I couldn't disagree more!
The regular Spring framework can turn out to be a total mess of various intertwined .xml configuration files, based on whatever the project needs - access control mechanisms or integrations with external providers (say, LDAP or Kerberos), perhaps various filters for processing web requests (transformations, logging, additional flow controls), things to make any number of integrated libraries work correctly (JSR310 integration with Jackson, for example), all of which gets even worse if you need to get server side rendering in any of the older technologies working correctly, like JSF and PrimeFaces. You can get a variety of things done with Spring, true, but in practice it's going to be brittle and needlessly verbose.
The Spring Boot framework is a marked improvement, however you will still need a whole bunch of configuration in your .properties file(s), or maybe your YAML configuration if someone has opted to use that format. Of course, you shouldn't forget that you still need to figure out how to ensure that the profiles are loaded correctly and that you somehow haven't screwed up the settings in each file. But with Spring Boot, all of the sudden you need to also figure out which configuration related annotations you need and which ones could cause problems - how to better figure out passing DB connections to myBatis or Hibernate or whatever and also ensure that the session parameters are set as needed (say, default locale, so you don't get issues with Oracle).
Spring Boot simply takes a lot of the XML hell and turns it into a Java hell - better, because you're working with a proper programming language, but still not quite there yet, because a lot of the problems will only manifest at runtime (ever needed @Lazy with @Autowired?), which isn't fun when your app with thousands of source files takes minutes to start up so you can check everything out. Personally, I blame reflection and dynamic behavior for a lot of these problems, which is far harder to reason about than your IDE telling you that what you're attempting to write is weird and won't work before you launch everything.
Of course, my dislike of Spring is shaped by the utter messes of projects that I've had to work with, which is largely the inevitable outcome for any monolithic codebase that has to be kept around and is important to a business for close to a decade but also with limited resources (say, unlike the Linux kernel). That's also why previously I mentioned the "Green Vs. Brown Programming Languages" article - you tend to dislike some stacks once you've seen what long term projects in them end up looking like.
So, if you can, it's probably best to stick to smaller projects and microservices so these pain points don't become readily apparent and then most frameworks would be passable - be it Spring, Spring Boot or most others. Though I sometimes wonder whether people who wrote COBOL and FORTRAN also found starting out and having smaller projects to be enjoyable. Somehow we talk about languages and frameworks, but not how much of a difference there can be between a new project and an older one - some sort of a maintainability degradation coefficient that would express how maintainability changes over time.
I would even go as far and claim that the reason Java has this reputation of huge complexity in every monorepo is partly the effect of survivorship bias. Some other stack may have crumbled long before growing to such a monstrosity.
Nonetheless, I would also like to have more of compile-time DI, records, etc, but we shouldn’t just reinvent everything in a heartbeat.
Go is very neat, true to its core identity and very very stable. I have been developing .NET (C# and F#) for almost 20 years and I don't enjoy it at all anymore.
EDIT:
Just last week the .NET team tweeted that they are looking to introduce green threads to .NET after all. I love the green thread implementation in Go and I think it is superior to .NET's async/await model which get scheduled by OS threads, but .NET has gone down that route many years ago and it's embedded everywhere now and introducing such a new fundamental change now is just plain fucking stupid. Microsoft is so dumb for even thinking this is a good thing to do. If they don't like how C# works then invent a new language with modern fundamentals which you like but stop changing C# so much that it's become completely unrecognisable. .NET developers don't want to rewrite their code bases every 12 months. We have real work to do.
I tried instructions on the site:
$ go install golang.org/dl/go1.19beta1@latest
$ go1.19beta1 download
But I don't get where the new binary is placed. It's not in my PATH.