I have been using Go in production since 2015 and can honestly say that other than the ternary operator, none of these have been a major issue for me. Granted, I am doing mostly REST API development so my use cases may be different, but I have never had an issue with capitalization or which interfaces are implemented. The tooling is by far some of the best I have used in a language. Paired with a good editor (I personally use VS Code on Ubuntu 18.04 as my main setup) and I have yet to miss exceptions or wonder what my code is capable of doing.
That being said, Go isn't perfect by any stretch. Sometimes panic ins Go routines can be very hard to trace. The transition to Go modules is challenging for larger projects with versions beyond 1.x (when I started it was Glide, then dep, now go mod, which has been a bit frustrating). However, I wouldn't go back to Java, C#, PHP, or NodeJS if I had the choice.
My go to in the server space is Go and Elixir. I don't feel a desire or need for anything else, but again, that's me. You know your use case better than a stranger on the internet :)
The "Capitalization feature" is actually objectively worse because of the reason OP mentioned, of having to rename all semi-local usages when visibility changes. But good IDEs can help mitigate the difficulty of this.
But even more significant is that the Go authors cannot seem to grasp the importance of pre-existing conventions. Almost every language I've used in the past decade allows and encourages the variable, Class, CONSTANT convention.
It reminds me of people who want everyone to use CE and BCE instead of AD and BC, ignoring the inertia and relevance of the latter and almost seeming like we live in a vacuum where fresh ideas have as equal weight as old ones, and history doesn't matter at all.
I don't know how to explain this better, but this is basically the core reason I don't like Go, above and beyond any specific features or lack of features.
I once heard Go described as "what if we took the good ideas of C and started from scratch?" But it feels like they take that very literally, as if they were saying, "what if it was actually 1970 right now, and we didn't have C, and the next 49 years never happened?"
(This is a separate reply than my other one because someone already upvoted that.)
WRT renaming, I find that refactoring code is often such an oversight from language designers. I consider C# to be an elegant language in this way. Public fields and properties look the same in C# so you can effortlessly refactor between them, for example. I wish more languages thought about this stuff.
I liked a similar aspect of Ruby: attributes and 0-args methods had the same syntax, so you could easily refactor a static field into a method that returns a dynamically calculated value. Overall I don't like that feature, but that was a very handy aspect of it.
Ruby actually went one step beyond: there are no attributes. `attr_reader` just generates a 0-arg method, while `attr_writer` just generates a 1 argument `name=` method.
TBF that's pretty directly inherited from the Smalltalk ancestry: just like Smalltalk, Ruby simply doesn't have public (data) fields. Although it does provide shortcuts for automatically generating accessors which I don't think Smalltalk did / does.
Funnily enough, Self opted for the opposite tack of not having private data fields (although it does have readonly and read/write slots), but you can trivially swap "data" slots and "method" slots: http://handbook.selflanguage.org/2017.1/langref.html#constru...
For both C# and Go I use editors that understand the language and can rename all references to a variable with one keyboard shortcut. C# has at least 3-4 of these editors available, and Go has at least 2.
I realize that not everyone's preferred editor may support every language yet, but still I think the solution to this problem is probably just having the language designers provide libraries that editors can use for these refactoring features, instead of through specific capitalization conventions.
It doesn't matter when you're making the edit, it matters when you're looking at the diff and skimming through a hundred identical changes for the things that are actually different.
> But even more significant is that the Go authors cannot seem to grasp the importance of pre-existing conventions. Almost every language I've used in the past decade allows and encourages the variable, Class, CONSTANT convention.
What "pre-existing conventions" do you see in the Go code that you've had to refactor? The fact that conventions from Java or C++ aren't the same simply reinforces the fact that Go is a different language, and that's A Good Thing.
That's really interesting. How do you use Go with Elixir? I've never really gotten into Go, partially because I feel like there's a lot of overlap with what it does and what Elixir does.
I could see using it for CLIs and various Unix scripting, but I've been doing that with Ruby, for the most part. I also considered learning Go for creating some native binaries for a few critical paths, but Rust seems like the best choice for that. A NIF that crashes is one of the few things that will take down the Erlang VM, so Rust's strong safety guarantees (might) make it worth the steep learning curve.
Sorry if I wasn't clear. I don't really combine the two and use whichever makes the most sense. In my day to day, we are primary a Go microservices shop and are exploring Elixir for some video stuff. I've used some Elixir on side projects. Sorry I wasn't clear that I am not combining them, although I imagine if I was going to it would either be over some sort of gRPC implementation or using something like rabbit to hand over tasks.
Note that I wasn't asking why one would use Go instead of Elixir for a given task. If it's CPU bound, Go would be a better choice.
My question was how the poster was using Go with Elixir. Since they have a big overlap in what they're commonly used for and since NIFs (Natively Invoked Functions) on the BEAM really need to be fail-safe, it's not as clear as how to use these two together as how one would use C in the same stack with Python, for example.
If the article had gone up to 250K connections, Elixir would have fallen down similarly to what happened to Node... although the reasons for Node being unable to keep up were unclear in the article, and I think warranted further investigation that the article didn't do.
The plaintext benchmark is about as far from a computationally intensive task as you can get... it should basically be network-bound, supposedly an ideal task for the BEAM VM, yet the results are clear.
If you turn on additional languages and frameworks, you'll note that Elixir or Erlang are significantly faster than, say, Rails, but Elixir or Erlang are a good 5x to 25x slower than Go.
People have built massive, successful companies on Ruby on Rails, such as GitHub, so don't think that I'm discounting these languages wholesale. You just have to accept that you will be paying substantially higher infrastructure costs if your infrastructure needs begin scaling beyond a single server. If you think that Elixir or Ruby or whatever else is the secret sauce to make your company successful, go for it! But those are a lot slower than Go or Rust or other very fast languages.
Elixir along with OTP and BEAM is sitting at a significantly higher abstraction level than Go. Everything is built around the distributed stateful soft-real time problem domain. While maintaining fault-tolerance.
You can have that in Go as well, you just need to build all the clustering mechanisms, OTP behaviors, tooling, actor model, embedded monitoring services and such from the scratch. You may well see Go significantly slower than Elixir at this point when you finish baking all that into it.
Developer hours are significantly more expensive than hosting. I'd happily pay double for hosting if it meant I could need half the people to accomplish the same task.
You're right about the abstraction but Elixir is slow because the runtime is slower, because of immutability and many other reasons, if you don't use all the message passing features and just do pure CPU computation you will find it's still pretty slow.
I've never used Kubernetes so I don't know what it provides. But I doubt it could provide the mechanisms for two application nodes to connect to each other and automatically share real-time state. Or the monitoring services to the underlying virtual machine's green threads.
Actually which of the above does Kubernetes really provide as it is in the Erlang VM?
that cpu spike in BEAM is actually a runtime setting and doesn't reflect usage; it's intentionally busy-looping the connection accepts to reduce latency.
In my experience, having clearer cpu metrics is more useful than any hypothetical latency benefits.
However, the hypothetical benefit would also depend on the cpu model; it made a little more sense when they introduced it than it does now. Back then, CPUs took a significant amount of time to change power states, so going to sleep and waking up for an event that comes shortly after could involve quite a bit of delay. Even if the processor didn't fully sleep, it may reduce the clock frequency, and not increase it until you've done a substantial amount of work.
With more recent processors, these delays are much smaller, and perhaps it would have made more sense to control the power states in another way, but there was some justification.
After a long stint in enterprise Java land, Go was a really big adjustment. Mostly about letting go of unnecessary complexity. I didn't realize how much I didn't miss that complexity until I recently went back into Java. If you learn the golang way of doing things, the issues this guy mentions really are not something you run into.
I think Go will suffer the same fate as Java. Any enterprise language that becomes too popular will slowly devolve into an enterprise monster.
If you come to Java with a blank slate, and try using it with simplicity in mind. There's not much wrong with it. But once people add design patterns, layers of layers, reflection, annotations. Etc. You get a monster. And somehow, the enterprise world seems to always create those.
The problem with Java is that there isn't enough of it to have anything wrong with it. The programs have all those design patterns because the language lacks so much power that you have to write it all yourself. For instance, having value types would really help.
The one thing they should take away is namespaces. You can't write unreadable enterprise software if you can't put every 10-line class six packages deep for no reason.
- Compared to Java: Ecosystem is way over-engineered. You might get along just fine without writing a bunch of boilerplate and factories and XML configs, but sooner or later you're probably going to pull in some dependency that does and have to deal with a bunch of clunky APIs and other annoyances.
- Python: Possibly the only language that's even worse at dependency management than Go. Installs packages globally. The solution is to create a "virtual environment" which is code for hacking up your PATH.
- Node: I dislike the quirks of Javascript like remembering the difference between == and ===, undefined vs null, etc. I haven't used Typescript which may solve some of those language issues. I also dislike dealing with promises/callbacks for so many operations.
I like Go because:
- I can compile to a single statically-linked binary and cross-compile for other platforms
- I can choose to either vendor dependencies or use go-dep to create a dependency lockfile. I can have multiple GOPATHs with minimal magic.
- The "go" CLI handles pretty much everything I need without an explicit config file like pom.xml or package.json.
- The language lacks features. There is no magic. Way more so than even Python, there is one obvious way to do things and there's even an official style guide, so it's easy to jump into someone else's code and understand exactly what's going on immediately. After working with Scala professionally for a few years, I firmly believe this to be a feature, not a flaw.
- The standard library is incredibly comprehensive. It's completely possible to write a web service without importing a single external dependency. There has been a lot of thought put into library APIs for things like byte Readers/Writers and HTTP handlers, to the point that external libraries still usually stick to these standard interfaces. Contrast that with a language like Python where urllib sucks so everyone uses requests.
Obviously completely get that this is subjective, whatever language you're happiest and most productive in is the correct choice for you, etc, but if I might address your points on JS/node perhaps you'll find something useful there:
> == vs ===
Just never use ==, and you're done. This is pretty much a defacto standard. It essentially has no legitimate usecase that couldn't be expressed more explicitly with at most 1 extra line, and should just be ignored.
> undefined vs null
I've only very occasionally run into this being an issue. Standard seems to be using the falsey nature of both undefined and null to check for them, !x rather than x === null for example, so that it doesn't really come up much in practice, but anyway...
> TypeScript may solve some of these issues
Yes, it solves both the above :-)
> Dislike dealing with promises / callbacks
Firstly callbacks are hardly seen anywhere any more. And if they are, certainly no more than one level deep, and that's even assuming you don't just promisify the callback function anyway, which is trivial to do.
For Promises - async/await makes dealing with them syntactically much nicer in node. It's pretty much just like synchronous code to read, and in behaviour.
I see this complaint against Java a lot. If you're going to slum it in a language with no ecosystem so you don't feel overwhelmed, why not just use less of the Java ecosystem?
I think that when GP said, "ecosystem is over-engineered", they didn't mean that it's too rich, they meant to say that it's over-engineered.
Which, I'm not sure there's a more durable reputation in all of informatics than Java's reputation for over-engineering things.
There are cleaner Java libraries for most things nowadays, but the culture around the language is also such that, in many companies, it is politically much easier to use another language entirely than it is to use Java but choose a REST framework that doesn't implement JAX-RS.
It's not always clear how to do that. Sure, in theory it's possible to manually download JARs, configure your classpath, and run javac. In practice, the wisdom seems to be to replace the old ecosystem with a new ecosystem like Maven -> Gradle or Spring -> Spring Boot.
In comparison, the entire standard build process for a Go program is:
$ export GOPATH="/path/to/repo" && go get && go build && ./main
No config files with some DSL to learn or handwritten XML, no weird class loader behavior to track down, no tuning memory limits or any of that stuff that is just par for the course in Java.
The entire build process for java is mvn package. The only xml you encounter in modern java will be your pom.xml. How is that any different from a package.json, go.mod, cargo.toml, or Pipfile?
And then I get a jar. Or maybe a war. Or maybe a zip? And maybe it's a fat jar with all dependencies bundled. Or maybe not and I need to fetch a bunch of deps and configure a classpath. And then I need the right JRE wherever I'm going to run it. And then I can run it like 'java -jar'. Or maybe I need a server like Tomcat. Or maybe something else.
And every time you jump into an existing codebase, you need to scrutinize the docs that are hopefully there to figure out particulars of the build/deploy process.
This is any language in any code base. Even in Go I will need to figure out if go modules, dep, or something else is being used. In rust, does this project produce a bin or rlib? Having options isn't a bad thing.
These complaints are usually by people who have not been using modern Java, or who have just been reading or hearing unsubstantiated claims about it. Or who haven't worked in large golang projects to see all the mess it brings with it because of how underpowered it is.
Look up libraries like Spark[1] or Javalin[2] and you get something quite light weight. Or DropWizard[3] if you need something more holistic.
That being said, the moment you need something more involved, say validation, DB access, pre- or post- endpoint call processing (e.g. for authorization), then golang completely falls on its face. There is nothing in golang that compares to Jooq[4] for instance, and because golang doesn't have annotations, you can't do automatic validation or authorization, and you end up having to do everything manually in a verbose and error prone manner.
For static compilation in Java, GraalVM is supposed to be quite good[5]
You are right that big (especially enterprise) projects in Go are also a mess. But one of the main reasons is that people have a Java (or C++) background and try to replicate all sorts of complexity.. not because Go is "underpowered".
Also if you compare those codebases to Java (again enterprise) projects of a similar size they seem quite readable instantly..
> You are right that big (especially enterprise) projects in Go are also a mess. But one of the main reasons is that people have a Java (or C++) background and try to replicate all sorts of complexity.. not because Go is "underpowered".
That hasn't been my experience at an employer. Their devs mainly had Python and NodeJs experience and similar languages (which is what the first version of the code base was written in), and disliked "enterprisey" code. Somehow, the decision to move to golang was made.
Yet, they somehow managed to come up with their own mess, and yes it is mainly because how underpowered golang is. I keep thinking about how much simpler the code base would be if it were written in Java, let alone something like Kotlin.
I've used Spark in prod. Worked great and was the highest QPS service in the company I worked at. It was pushing >5GB/hour of JS over ~50 running containers at more than ~1000QPS/container each doing a bunch of crypto (AES) & and data wrangling (JDBC). Only thing that was difficult was serving SSL but that was the Java SSL-key ecosystem's fault. Ideally SSL would come from an edge load balancer but this company had a strange requirement that there was no 80 traffic.
There hasn't been a single job advert in the last decade when I was being contacted for Java gigs that didn't involve dealing with legacy code and dependency injection spaghetti.
Everybody has legacy code. Modern Java doesn't mean much if your team is 2 years away from being able to use the modern stuff.
Not OP, but in my experience, it's harder to know how to get from System.out.println("hello world") to GET / in a browser showing "<b>hello world</b>", without adopting some intensely documented framework and infrastructure, along with obscure XML configuration files. I liked that aspect of Clojure, but I wouldn't have a clue how to achieve the same thing in pure Java.
This is patently false. Getting to hello world is as simple as knowing how to add a dependency, just like any other language and requires 0 xml. See javalin [0], or spark [1].
Thanks for sharing these links! My point was that I didn't know how to do this, not knowing these frameworks existed and unable to find them on my own search. You solved that issue for me. If these frameworks get more attention, maybe it'll be solved for more people!
Aside from what others have said, the single tiny binary and cross-environment compilation is a huge plus. I can compile my largest service in seconds for Windows, Linux, and Mac (we still use Docker, but not for the cross-environment reasons). It's simple, concise, and fast out of the box. I also have been bitten far too many times by the JVM being RAM hungry and, frankly, I focus on startups which don't have time or resources to spend tweaking VM variables. Granted, I am sure Java and the JVM have come a long way since I last used it heavily in 2013, but I haven't found a single reason to want to go back.
As far as Python, same kind of reasons. Single binary makes deployments easy, static analysis and built in tooling makes life easier, and I just find it more enjoyable, which is completely subjective.
1. Java - did not want to adopt the entire ecosystem. This is very much wanted a banana and got the whole jungle with a gorilla type of story.
2. Python - dynamic. Don't want that. Go's minimal typing is perfect. It's easy to deploy (binaries). It's fast. It can scale well. It's opinionated (love this).
Python and Java both encourage and allow developers to flex creative solutions that are hard to maintain long-term. Sure, seniority helps with that, and being part of a good team; however using Go, you just run into that less due to the conciseness of the language and strong idioms.
Python with type annotations and use of the mypy typechecker is really darn great.
I work on Golang stuff at work where we made the switch after the troubles associated with refactoring Python. Recently though, I 'typed' a personal project of mine that was fairly large, and it's become a pleasure to work on.
IDE integrations of mypy warn you as soon as type errors occur. The fact that the type annotations are first-class features of the language and not embedded in comments also makes it great. The compromise of type-safety at the boundaries where you interface with 3rd party APIs that don't provide type annotations (the major ones do) does not get in the way too often contrary to what I expected.
> mypy makes Python a pleasure to work with again.
This is my experience as well. I love that I have the option to use Python typed and untyped. For little scripts, fiddles, prototypes, etc. it's often convenient to omit type annotations. For solid software that runs in production it's nice to have them.
Another thing that I'm excited about is nuitka [0], a Python to C compiler that allows you to create binaries from Python code. It cut start-up time of a command line tool that I compiled with in half.
You can, but you can't deploy them. If you really want to keep your services separate, you need a separate JVM installation and jars for each service. Go gives you this automatically with a simple binary. Go's standard library is an order of magnitude (at least) better than Java's, much more comprehensive, much more cohesive, and much more capable.
Don't you think it'd be the other way around - the older a language gets, the standard libraries degrade since you can't introduce newer patterns without breaking compatibility.
I think Java is ok. I just don't like all the weight you inherit with the ecosystem. There's a lot of buttons you have to learn and history. It's working a submarine vs purposed craft (bad example).
I've been entirely satisfied with Go when using it to interact with kubernetes and AWS and do what doesn't amount to much more than CRUD and logging. I've never implemented any non-trivial algorithms with it though, which is what the article appears to be about.
That being said, Go isn't perfect by any stretch. Sometimes panic ins Go routines can be very hard to trace. The transition to Go modules is challenging for larger projects with versions beyond 1.x (when I started it was Glide, then dep, now go mod, which has been a bit frustrating). However, I wouldn't go back to Java, C#, PHP, or NodeJS if I had the choice.
My go to in the server space is Go and Elixir. I don't feel a desire or need for anything else, but again, that's me. You know your use case better than a stranger on the internet :)