Golang, if you haven’t yet heard of it, is a programming language created by Google. Upon first investigating the language, I thought “huh, what a nice shake-up from the usual”. And it was, in many ways! Golang writes very much like a scripted or interpreted language (i.e. Python), but it compiles like a…well, a compiled language, and the syntax has more in common with C# (my daily driver at work) or Java than with Python in many ways.
However…I just can’t use it. Don’t get me wrong, I want to like Go. There are things about it that I love. But even despite my desire to write an excellent tool in Go, there are just some things that seem like small nags, but add up to one huge pile of “nope”.
Convention over Configuration, Extreme Edition
Convention over configuration is not inherently a bad thing, I don’t think. Basically all mainstream web frameworks, for example, focus on convention over configuration, and that’s fine. However, a programming langage is not the same as a framework; users should not be actively foiled when attempting to perform basic functions, such as (in my case) file management.
That’s right, Go’s compiler effectively demands that you dump all files in a package (basically a single application) into the same folder. You cannot organize your code in any subdirectories, lest they be be ignored and cause errors on compilation.
This creates, in my opinion, a rather absurd reduction of options. Either everything goes into a handful of files, leading to source files thousands or even tens of thousands of lines long,
or you end up with dozens upon dozens of source files at more adequate lengths, all heaped into a single pile folder. Neither option seems like a great solution.
Another example would be the requrement of putting “else” statements on the same ending line of an “if” statement. For context, I like to write my code like this in C#:
if (condition){
// do something
}
else {
// handle else
}
Often times, I even forego the braces around the else statement altogether in C#, because they frequently are one-liners that don’t need braces for clarification.
This code, of course, would never compile in Go. Not because it’s logically wrong, mind you; it’s simply because I chose to put the “else” on another line, instead of smushing it up against the closing brace of the if statement.
It’s strange, arbitrary choices like this that start to get under my skin. What reason is there for requiring syntax formatting like this? I, for one, find it ugly and less readable, but the annoyances don’t stop there.
Significant Namespace
Anyone familiar with Python is familiar with the notion of significant whitespace. Code scope is determined by levels of indentation. It’s one of the most frequently referenced reasons why people dislike working with Python, which is - in most other regards - an otherwise quite dandy language, and not without reason.
It’s much easier for the eyes of most programmers to just spot two sets of braces and determine where the scope of a block of code begins and ends; with whitespace, it becomes an exercise in visual approximation. It’s quite easy to, at a glance, misjudge where a variable’s scope begins and ends because of a missed indent somewhere.
Likewise, it’s quite difficult to spot errors in code when you can’t simply highlight brace pairs to see what’s wrong with your scoping. It’s not a huge issue, but it can be annoying at times.
Go has a similarly frustrating feature, except this one goes beyond “visually irritating” and straight into “actively detrimental”. I’m talking, of course, about exported names. In Go, your variable is private if its name starts with a lowercase letter, and it’s public if it starts with an uppercase letter.
…what? Seriously?
Not only does this feel entirely arbitrary, but it also means I can only have two variable scopes: public or private, with no possibility of ideas like a protected scope.
Now, I am not ashamed to admit that I am not a fan of language-enforced naming schemes. I don’t like it in Python when the linter gives me a warning when I name something in camelCase instead of snake_case; I am irked when C# tells me that my function names should be Capitalized. At the end of the day, though, whether I choose to follow pre-established naming conventions or not, my code will still run, and I can always go back and clean up my naming choices later if I choose to release this code for others to look at. But this is something else entirely; now, my naming choice actively changes the way my code functions.
WHY? What purpose does this serve? How does this make my code more readable? was typing a simple pub or priv before the variable name considered too complex?
Stupid Programmers
This may be the most irksome one on this list yet. The Golang developers, in their infinite wisdom, have made multiple decisions intended to “simplify” the language, but which actually result in overly-complex workarounds. Many developers are probably familiar with the complaint about Go lacking generics (which apparently the Golang team is finally giving in on after prolonged outcry). This isn’t the only example, however; Another would be the fact that Go has no ternary operator.
The supposed reason is that the ternary operator leads to code that is too complex. What this really leads to, however, is instead of this:
x := y > 1 ? true : false
we end up with this:
var x bool
if (y > 1) {
x = true
} else {
x = false
}
I fail to understand how making something more verbose makes it more understandable. All this does is make my code more cluttered and longer to read.
Google’s Way or the Highway
I could give a dozen more examples of various irksome things I’ve discovered whilst trying to pick up Go, all of which I ran into while working on my first project.
I’m sure many people reading this will immediately complain that I’ve given up on a language too early, that I simply don’t understand how good the language is if I just adjust my expectations. I would argue, however, that the sort of “programmers should yield to the language” mentality, when taken to the extremes Go stretches them to, is a weak excuse for overly-opinionated language designers to try and enforce their opinions on a large group of language users in a particularly arrogant way.
Unused Variables
One of the most heinous examples of this is in regards to Go’s “unused variable” error. I could link to multiple discussions on Go’s Github issue tracker and on StackOverflow discussing this issue, but it’s so prevalent I don’t feel a need to provide just one link, as a quick Google search will bring up a half-dozen easily. In every case I’ve seen, however, the result is always the same; the beginner or intermediate developers will point out how annoying it is to have the compiler refuse to build the program if a variable is unused (or to have the autolinter delete the variable entirely if it’s unused), and the golang diehards will basically say “if you’re having this error, you’re writing your code wrong”.
No. There are tons of reasons why someone might want to compile code with an unused variable.
- Maybe they’re debugging something and commented out the code that used that variable.
- Maybe they’re deployging A/B copies of a program with different feature sets as a trial run.
- Maybe this is just a personal project and the developer just decided that they don’t want to finish that feature tonight but still want to use what they’ve got so far.
But to say “if you write code this way, you’re wrong” is just arrogant. Not everyone writes industry-level code all the time; some “sub-optimal” design choices are sometimes acceptable.
(And yes, I realize you can work around this by putting in something like:
_ = variable
…but that’s not the point.)
File Organization
Another significant example is the way go handles project files. For many programmers, it’s not uncommon for one file to represent one chunk of related code. This is a somewhat arbitrary design choice - how do you measure what constitutes a “chunk” of related code? - but it helps significantly to organize the project as it begins to grow. Since this “one-chunk-per-file” idea leads to a lot of files, it’s usually best to organize the files into subdirectories to keep the root folder clean.
Go, however, does not allow you to organize your files at all. All files in a single directory are considered part of the same module (though you can split this up with package names), but you are not allowed to add files from subdirectories into a module. The reason given for this restriction? The Go team believes that if you need a subdirectory, you should be making another package. Well, gee, thanks, Go team, but I don’t particularly want my program to be comprised of dozens of packages, especially considering the Go compiler doesn’t support cyclic dependencies so I couldn’t use this method most of the time even if I wanted to.
Attitude
I think the general attitude of the Golang development community as a whole has, quite frankly, been the most offputting part of working with the language.
Seeing SO threads where the answer to a question effectively amounts to “have you tried not seeing
Reading through entire guides on how to do tasks that most other languages have intuitive structure for (i.e. foreach, error handling, etc) and then finding out that the reason it’s so complex is because the language devs felt the simplified method led to “poor-quality code” just reeks of gatekeeping behavior to me.
Summary
All in all, I really wanted to like Go. It actually has some truly nice features (it’s handling of concurrency, for example, is top-notch) and it has a nice set of libraries written by developers who aren’t bothered by the issues I’ve had with the language. Maybe I’m just being nitpicky. But I don’t think I am.
…Who knows, maybe I’ll go give Rust a try instead. ;)
EDIT 1 (10-23-22) - added section discussing file organization, which I forgot to include from my notes the first time around.
>> Home