The article mentions “Simple is better than complex”, but not the next line of the Zen of Python, which I think tells us a lot about that language’s philosophy: “Complex is better than complicated”.
Looking closely, that line says “(not simple) is better than (not easy)”, or more clearly, “easy is better than simple”. Python definitely lives up to this - it’s easy to get started with, but if you look deeply it’s a very complex language.
Go’s philosophy is probably the opposite - that simple is better than easy. This is similar to the philosophy of Clojure, as explained by Rich Hickey in “Simple Made Easy” [0].
"Translation from Python, which is a dreadful language to try to do concurrency in due to its global interpreter lock, really brought home to me how unobtrusively brilliant the Go implementation of Communicating Sequential Processes is. The primitives are right and the integration with the rest of the language is wonderfully seamless. The Go port of reposurgeon got some very large speedups at an incremental-complexity cost that I found to be astonishingly low. I am impressed both by the power of the CSP part of the design and the extreme simplicity and non-fussiness of the interface it presents. I hope it will become a model for how concurrency is managed in future languages."
> dreadful language to try to do concurrency in due to its global interpreter lock
don't take it too seriously. GIL has its issues and it would be nice to see it gone but "dreadful" is an overstatement. Python wouldn't be so widely used as a back-end language otherwise. Concurrency is not parallelism.
On "it [Go] will become a model for how concurrency is managed in future languages." -- it is not as clear cut as it appears at the first glance: "Notes on structured concurrency, or: Go statement considered harmful" https://vorpus.org/blog/notes-on-structured-concurrency-or-g...
Dreadful may be a little strong but anytime I've tried to implement something like asyncio for a non trivial piece of code it becomes pretty obtuse. (imo)
I’ve found asyncio to be a very simple model to understand, using the aio libs: aiohttp, aiopg, aiobotocore, etc.
Basically just slap “async” or “await” in front of everything and understand that anytime there is a network connection being accessed, that method will release control of the main thread.
You just have to pay attention to where something might block the thread for any significant amount of time - heavy calculation or lengthy file IO
You can spawn a multitiude of async tasks on startup and have super basic “scheduling” by using asyncio.sleep with some jitter.
The only time I have seen the performance limits of a naive asyncio app reached was in an auth app that sat in front of every API request for the whole company, and even then it was an obscure DB connection pool management issue deep in psycopg2.
goroutines (and the go keyword) are the primitive, just like async is the primitive for python. Something like golang.org/x/sync/errgroup gives Go the same kind of "nursery" concept and can be leveraged almost identically (modulo Go not having the "with" concept, but defer can play the same role)
there is no doubt that a "nursery"-like construction can be implemented in Go in the same way like any language with "goto" can implement structured loops. The point is in constraining what can be done.
There is a trade off: "goto" is powerful but it is likely to lead to a spaghetti code. "nursery" introduces constrains but makes it easier to deal with error handling/cleanup, back pressure issues such as https://lucumr.pocoo.org/2020/1/1/async-pressure/
> Go community’s practice for grounding language enhancement requests not in it-would-be-nice-to-have abstractions but rather in a description of real-world problems.
jfc, the arrogance of this asshole. Seems like a decent fit for Go though, considering that language’s history of ignoring PL research..
I mean, Go is awesome for containers, and it’s awesome if you have a lot of junior devs and a decent amount of churn.
But the amount of anti-intellectualism by big shots in the community is seriously depressing.
ESR might be a lot of things, but he’s not a junior dev by any standard. And experience reports are pretty consistent across the experience gradient—Go is useful for solving real world problems, and often the very abstract languages fail to do so (often especially those languages beloved by intellectuals). You can challenge the qualifications of the reporters with respect to their own experiences if you like, but that seems like a silly thing to do.
If PLT can’t produce languages that practitioners find useful, then PLT is at fault, not practitioners.
EDIT: Rereading my last paragraph, "PLT is at fault" sounds harsher than I intended it to. Mostly it just sounds like PLT is based on a model of software development practice that doesn't fit well with the real world. The model performs poorly, but PLT supporters like the parent commenter are (implicitly or explicitly) blaming contemporary software development practice for the mismatch.
I never said esr was a junior dev, and he’s obviously not :)
>“Go is useful for solving real world problems”
People repeat this like a mantra (you also hear similar from rich hickey’s most fervent acolytes in the clojure community), and I can’t for the world understand what it means...
I mean fucking BASIC can solve real world problems... I’ve spent ten years writing java and php to great success, but I'm still happy to never write in those languages again.
I even adore Elm, despite its annoying lack of type classes, but I respect Evan’s goal of avoiding complexity in the language. That argument holds up a lot better than That Rob Pike’s argument on types:
“ Early in the rollout of Go I was told by someone that he could not imagine working in a language without generic types. As I have reported elsewhere, I found that an odd remark.
To be fair he was probably saying in his own way that he really liked what the STL does for him in C++. For the purpose of argument, though, let's take his claim at face value.
What it says is that he finds writing containers like lists of ints and maps of strings an unbearable burden. I find that an odd claim. I spend very little of my programming time struggling with those issues, even in languages without generic types.
But more important, what it says is that types are the way to lift that burden. Types. Not polymorphic functions or language primitives or helpers of other kinds, but types.
That's the detail that sticks with me.
Programmers who come to Go from C++ and Java miss the idea of programming with types, particularly inheritance and subclassing and all that. Perhaps I'm a philistine about types but I've never found that model particularly expressive.
My late friend Alain Fournier once told me that he considered the lowest form of academic work to be taxonomy. And you know what? Type hierarchies are just taxonomy. You need to decide what piece goes in what box, every type's parent, whether A inherits from B or B from A. Is a sortable array an array that sorts or a sorter represented by an array? If you believe that types address all design issues you must make that decision.
I believe that's a preposterous way to think about programming. What matters isn't the ancestor relations between things but what they can do for you.”
It’s just your average, obvious complaint about the inflexibility of class hierarchies in OOP, with a slight misdirection at the beginning when he mentions generic types (aka parametric polymorphism) but for some reason that’s an argument against types?! He mentions polymorphic functions, as if they can’t be typed???
I mean I made the same mistake after three semesters of java at uni, but one semester of c/c++/python made me realize there was more to programming and I eventually discovered type theory, which makes Rob Pike’s claims seem odd at best.
For me personally (and thus anecdotally) PLT has been a boon in most aspects, even though I have to deal with imperative or object-oriented languages from time to time. it’s just such a drag...
> I can’t for the world understand what it means...
It means Go performs well on real world projects. People feel productive, the language, tooling, and ecosystem get out of the way. You (and most PLT advocates I've encountered) seem to be evaluating languages on their inputs/features (presumably because you believe axiomatically that certain features--e.g., type systems--have a huge effect on the success or failure of a given software project) while the "useful for real world problems" view is about evaluating languages on their outputs. The latter view is harder to measure objectively, but it accounts for everything (e.g., syntax, type system, tooling, performance, ecosystem, etc) in correct proportion (no axiomatic beliefs).
Many PLT proponents generally seem to struggle with the notion that languages are successful when their model predicts that they shouldn't be. For example, many PLT proponents believe type systems strongly predict the success of a language, yet languages with very sophisticated type systems which are much admired by PLT proponents do poorly in the real world and languages with very flat-footed type systems (e.g., Go) do relatively well.
Either the qualitative data about these languages are wrong (e.g., contrary to the qualitative data, Haskell actually makes for more productive software development on balance than Go), or these PLT proponents' whitebox model is wrong. My money is on the qualitative data.
> You (and most PLT advocates I've encountered) seem to be evaluating languages on their inputs/features (presumably because you believe axiomatically that certain features--e.g., type systems--have a huge effect on the success or failure of a given software project) while the "useful for real world problems" view is about evaluating languages on their outputs.
I don’t, so please keep you assumptions to yourself and don’t put words to in my mouth.
> I don’t, so please keep you assumptions to yourself and don’t put words to in my mouth.
I'm hardly putting words in your mouth. You were expressing more-or-less exactly this sentiment in your previous post.
> What is this based on?
It follows by definition of output-based or blackbox evaluation. Evaluating the output of a system implies that you are evaluating inputs in proportion to their contribution to the output.
> I'm hardly putting words in your mouth. You were expressing more-or-less exactly this sentiment in your previous post.
Trust me, I wasn’t.
> It follows by definition of output-based or blackbox evaluation. Evaluating the output of a system implies that you are evaluating inputs in proportion to their contribution to the output.
I like this! It’s like pure functions/total programming, only not rigorously defined in the slightest.
It’s not an answer to my question though, HOW do you know that the results of your output/blackbox testing is correct?
> Obviously Go chose a different path. Go programmers believe that robust programs are composed from pieces that handle the failure cases before they handle the happy path.
A function which only returns an error can have its result ignored without any warning.
There’s other reasons too! I’ve written the following:
if v, err = func(); err != nil {
nil, err
}
… and then went on to use `v`. Thanks to `v`s zero value being legitimate (and not nil like a pointer would be), the program continues on as if everything is okay. In case you didn’t catch it, I forgot the `return`.
Rust takes a much better approach with Result, where the return value is either `Ok(v)` or `Err(e)`, and there’s no way to access a meaningless value for the other possibility.
Eh. It’s verbose but I like it. It makes me think about the code a bit when I have to write a descriptive error wrap. Kind of annoying I guess... I haven’t written ultra large go codebases though so ymmv.
Don’t know what to tell you, I have personally made this mistake and not had this caught by the compiler. I haven’t used go in several years at this point, so it’s entirely possible this is a newly-caught scenario by the compiler.
Regardless, the fundamental point stands. Using tuples to return “meaningless” values alongside errors allows developers to mistakenly use those meaningless values.
Apparently linters are bad and that’s why go literally refuses to compile if you have unused imports. But crap like this? No problem, flies right through.
Linters aren't bad (Go basically has one as `go vet` that checks for all kinds of "this is probably wrong", like the common "closing over a loop variable")
Warnings are bad, specifically when the warning is unambiguous (importing a package you aren't using is always wrong, though it makes debugging frustrating at times) The idea is that warnings that don't "stop" the build generally get ignored. Build most non-trivial C++ projects and count how many warnings flow past the top of your screen for an example of what they were trying to prevent.
Yes, laser focus on real-world problems like unused variables (don't matter) or unused imports (matter even less).
> Theoretical problem: Someone might mutate a variable intend to be constant.
> Go designers: Then put a comment saying not to do that.
One can only wonder why they even bothered writing a compiler when comments can solve it all.
> Real problem: Exceptions can happen anywhere and often go unchecked.
> Go designers: Then call exceptions "panics" and encourage people not to use them.
> Theoretical problem: Someone might ignore an error return value.
> Go designers: Let paranoid people write linters.
Because that way it's even easier to ignore than exceptions, and that's… good apparently?
Also create `append` where not using the return value just right (with no help from the compiler but that's OK because comments are where it's at) doesn't just create a bug in your program it can create two or more, what relentlessly efficient focus on real-world problems.
I can emphatically confirm that this is not what annoys me about go, and what does annoy me are the real-world issues I ran into it through multiple pieces of production software developed with multiple teams of skill levels ranging from intern to senior.
It’s been at least three years so it’s difficult to do a real write-up. In a lot of ways it was death by a thousand cuts. But some things off the top of my head:
Having to rewrite or copy/paste data structures for different types, given the lack of generics. As I understand it, even Google now has tools that generate go source from “generic” templates. This is absurd.
Defer to free resources (e.g., closing files) is a terrible construct because it requires you to remember to do this. You have lambdas! Use them so that the APIs can automatically free resources for you, like Ruby and Rust. It’s insanely hard to debug these kinds of issues because you run out of file descriptors and now you have to audit every open to ensure matching closes.
Casting to interface{}. The type system is so anemic that you have to resort to switching over this, and now you lose type safety. Combine this with the compiler not caring about exhaustive switch statements. And combine this with interfaces being implemented implicitly, and it’s a mine field for bugs.
I literally had a go maintainer waffle on adding support to calculate SSH key fingerprints because “users can just read the RFC and do it themselves if needed”. This is an indefensible perspective on software development.
Despite “making concurrency easy”, having to implement concurrency patterns by hand for your different use-cases is nuts. I have lots of feelings here, most are summed up by https://gist.github.com/kachayev/21e7fe149bc5ae0bd878
Tulpe returns feel like they were bolted on to the language. If I want to write a function that does nothing more than double the return value of a function that might error (insert your own trivial manipulation here), I have to pull out the boilerplate error handling stanza when all I want to do is pass errors up the stack.
This is the 5% that I remembered off the top of my head years later. All in all, the design of go as a “simple” language just means that my code has to be more complex.
Interestingly, the one time I introduced someone to Go without really "realizing" it (during a coding interview, got to pick the language I used), his first comment was actually that he liked how explict the error return was. (strconv.Atoi, to be specific). That pretty much sums it all up to me: `if err != nil` seems like annoying boilerplate, but then when you see stuff that's not just doing `return err` inside that conditional, you realize that it can actually be a benefit.
Unless your building a user facing frontend please keep art out of your code. Just like a bridge internal's concrete structure doesn't need decoration so does your backend's plumbing. Writing elegant and artful code that is hard to understand and debug does not make you a clever developer. Keep that stuff for the demo-scene or 99-bottles-of-beer. Go is an language for engineering software, not crafting.
This seems to have totally missed the first half of the comment to which you replied: _elegance_ certainly has a place in programming, just as it does in mathematics and many other disciplines. Things that are elegant are often difficult to conceive, but easy to understand being simpler solutions to a problem than something inelegant.
What exactly are you picturing when someone says "elegant" or "artful" code?
To me, elegant code is DRY code. Elegant code is code with useful abstractions where needed, and no abstractions where they just complicate matters. Code that is succinct yet clearly communicates its purpose.
From the sounds of it, you have an entirely different conception of what elegant code looks like.
Honestly if a solution is too hard to understand and debug then it is not elegant. Elegance is turning a complicated solution into an easier-understood one. Clever solutions could be elegant but they could also be shortcuts that are confusing and cause more harm than good. There’s definitely a difference.
> Unless your building a user facing frontend please keep art out of your code.
Honestly, I had rather art be kept out of the user facing frontend. (Computer programming, on the other hand, is considered art by some computer scientists.)
It's probably changed a lot since, but at least back in the 90s demo-scene code was absolutely 100% written for the result alone, even perhaps when it should perhaps have been say 75% for the sake of reusability. Imagine "decent" 90s game code quality, then dial the qualitynotch down a bit, since it will only have to work once on a well-defined machine anway.
I'm long since tuned-out. I do seem to remember Farbrausch making some waves when they started applying the concept of reusability and structure to their work in the early 2000s.
> If you can, don’t reach for a goroutine, or a channel, or embed a struct, anonymous functions, going overboard with packages, interfaces for everything, instead prefer simpler approach rather than the clever approach.
This reminds me of an interview I had with a company recently. I mentioned that although I was using go for my current pet project, I had hardly touched goroutines because net/http automatically spins off a new goroutine for every request so I don't have to think about it. The interviewer was incredulous and said that every database call should be put behind a goroutine so that it's not slow. He said the point of go is to leverage its powerful concurrency mechanisms. What I wanted to say was that I believed go's selling point was its simplicity and not its concurrency mechanism, and that it would be more idiomatic to keep it simple first and refactor only if there's performance issues. But he was the senior and I was the new job seeker, so I just ended up agreeing with him.
You'd want to use db connection pooling for any nontrivial application, a goroutine per db request is a recipe for connection exhaustion. That being said, go's concurrency model is indeed a huge selling point of the language.
OP doesn't say whether it was a SQL database, but Go's sql.DB automatically uses a connection pool. Naively using goroutines for every sql.DB operation is a good way to run into connection, deadlock, and other problems, so I think OP's approach of not using goroutines unless/until there's a demonstrated need (e.g., through profiling) is sound, and that interviewer might want to pump the brakes on the blanket prescriptions.
I'm gonna say this with all the respect in the world for the technical view —I'm a nerd and I agree with you guys 100%, a huge upvote for the whole thread— but experience taught me something along those lines:
There are such situations (people and places) that command one to put their mindset in "agreement mode", not because of logical reasons¹, but absent or even contrary to those, because of a need to reduce friction². In this meta-knowledge space³ lies the solution space⁴ for all your problems in a human environment.
When you approach things like that, you tend to see where "tech" fits in a wider landscape, and it tells me when to speak and when to shut my big nerdy mouth (whenever it's counterproductive, e.g. premature abstraction⁵ is just as bad as premature optimization). It tells me which lines in the code are "political", "organizational", "structural" not to the tech but to the humans around; and by elimination, which lines are left for us nerds to think about deeply.
It's a dance, really, there's no right/wrong/perfect, but there's a certain way to rise above these human contingencies and actually facilitate the path to market while having a blast — just set your expectations right as to what you can and can't touch, at least for now, reassess on a need-to basis.
So when you see a dark "anti" pattern (OP's case), sure you naively ask 'why', but then if you see org/pol structure, if you see this rigid wall of "this is how we do it, this is who we are", you know it's not about tech anymore. It's personal. Don't hit there. People (especially when insecure, so all of us at times) tend to protect their knowledge like it's a judgment on themselves, like it's proof of their worth, and it's very hard but doable to notice when you're entering such territory with someone.
____
[1]: you might be smarter than your boss, you may be "right" technically
[2]: "friction" in the economic sense, which includes overhead in your code as well as overhead from meetings. What stands between idea and productive execution.
[3]: literally, larger than the technical, including all aspects of engineering, organization and market forces.
[4]: the product that which you seek to make, i.e. that people use. The "internal" side of the product is the organization and codes that make it, that support its continued existence.
[5]: in this case, we "abstract prematurely" when we presume to know a system better than their maintainers, or that our "outside looking in" "fresh" pair of eyes has all the answers already. There is often much more than the technical to consider.
Well then you're probably the kind of person who would instinctively give signs that someone like me can open their big nerdy mouth, that this is safe territory because we're all engineers trying (this thing with error in the cycle, you know). You're probably someone in the "having a blast" part of my perspective — the kind of people I can actually work with best. The others, we "manage" kinda, with empathy if we can, like they manage us too.
Slight tangent. You know this idea of "feeling comfortable being wrong" in front of someone, that it's OK to say dumb stuff in front of people we trust. I think it enables us to just try, up to our limits (where we must fail a lot by definition, and also the only place where we make actual progress).
When you can build the kind of team that can transparently fail (thus collectively improve...), within that 'safe' tech space of 'engineers', where we know it's everybody just trying and the ethos is aligned with that (nerds may speak, fail, laugh, etc.), I think you've got a solid basis for any project, any company.
You sound like someone who builds that kind of environment. I wouldn't worry in that case; again, nerds will pick up the signs if you just speak your mind — hints like you saying "So I didn't know X, and then blablabla", or simply asking bluntly "what do you think of our choice to do X?" instead of defending it like OP's interviewer. ;-) In general, people who ask questions have to be prepared to hear the answer, that's how I operate personally. I would just temper the response based on what I actually do know of the situation.
I personally don't spend one minute thinking about mind games if the other person seems direct, transparent, honest, especially problem-solving mission-driven kinda mindset.
The multithreaded nature of the go http library makes it look simple, but in fact, its not. It won't show up problems until you use a specific tool to send concurrent requests.
Just a gotcha i want to mention to anyone reading. ApacheBench is a good first start.
So, accessing shared mutable state from many threads at once is dangerous. It's hard to lay the blame for this on Go - it's true in most languages I know of.
I suppose one ought to guard such maps with a RW lock, or a `sync.Map` which is tailor-made for this kind of situation.
sync.Map is slower than searching a slice of k-v struct for roughly <1000 elements.
Go designed their map implementation to be unsafe for both read and write. I kind of don't care, i was trying to point out something simple and everyone is nitpicking.
You can't declare a read-only map (or slice). Actually Java made exactly the same mistake, the best you can do is to implement Map<K, V> and List<T> with setters that throw at runtime.
Go was so obviously designed by Googlers, for Google. After the initial release, outside adopters would frequently report issues where Go undermined their productivity. And every time the core team's response was the same - complete disbelief.
"Dependencies? What? Do you not vendor every single library you depend upon in your monorepo?"
"Advanced type system? Why? Do you not hire thousands straight out of college, and expect employees to stay put for 2-3 years (give or take)?"
Every single issue that was raised in the past 10 years was met with such incredulity.
Rob Pike, Robert Griesemer, Russ Cox - they've done a tremendous job. A great, stunning achievement for their employer. The more of the infrastructure transitions to Go, the less money Google will need to spend on people in the long term. Those three have earned billions for their company, no doubt.
And none of that means Go is a great language for your 12-person team working on a CRUD web app.
The Zen of Go is not reducing your problem to "can it be put in an array?" or "can it be put in a hashmap instead?"
The Zen of Go is looking at individual persons and deciding whether it's net positive to cut them now, or 6 months from now, after their next performance review.
I've been programming for more than 20 years. I've learned many languages including C, Objective C, Java, Python, Ruby, JS, and some Haskell. I've never worked at Google.
Go is my favorite language of them all and has been since I started using it almost a decade ago. I use it for personal projects, and I use it at my company.
It helps me and the teams on which I've worked write correct code quickly the first time. The code we write is incredibly reliable in production at scale. It's easy to read and debug years later. Binaries can easily be compiled and deployed on any system without messing with dependencies or cross-compiler toolchains.
The same properties of the language which helped Google have helped me and many thousands of others as well.
Then why couple those goals to Google instead of ascribing them to a broader philosophy that Google happens to be adopting (among many others)?
Your original comment read like you were salty that Go didn't adopt ideas that enhanced yours and others productivity. Instead of writing it off as a philosophical difference (that can be attributed to any language with philosophical ideals), you have to take it to the next level with some kind of "Go devs only care about Google's needs" conspiracy. I've seen this kind of thing a lot over the years. Why can't people just agree to disagree?
I thought dmit's comment was fair. I've been programming for over 30 years now and Go is my least favorite language (actually, I absolutely hate it). The reasons for my distaste are basically everything dmit just mentioned (and more). I get that some people love Go but that doesn't mean the criticisms against it aren't fair.
> I've learned many languages including C, Objective C, Java, Python, Ruby, JS, and some Haskell.
You've learned basically the same language over and over again: Algol-family imperative-OO-mishmash. Haskell is probably the most unique out of all of them, and I'm inclined to think you haven't gone too far with it. Perhaps you know SQL though and recognize how a relational (roughly, set-oriented) language can work.
My point is that I understand that Go offers you some compelling advantages. We're just saying that other languages offer some pretty amazing things too, and they are even better designed (as languages) than Go.
Check out this fantastic talk by Scott Wlaschin to get a sampling of truly different language paradigms and how they approach problems: https://youtu.be/0fpDlAEQio4
That attitude is why I never bothered to learn it. Shortly after its 1.0 release, there were a bunch of "here's the good and bad about Go" posts. I remember seeing a post by Rob Pike on reddit where he said he was tired of people being on the fence about Go, and if they don't love it, they should just learn something else. That kind of "love it or leave it" attitude was prevalent everywhere, and it was a big turn off. It felt like instead of Go being designed as a tool for programmers to use, programmers were the tool, and Go was designed as a way to change them so they'd be better at what Google wants.
Golang chooses to avoid feature creep. This seems to annoy some people to no end and so these discussions always devolve into whining about feature X, Y or Z that would have made Golang soooo much better and OH how nasty are [Google or whomever] that blocks all these fantastic ideas from being implemented. If only they listened to all the smart people out there (especially me) it would have made Golang awesome and we'd see a million flowers bloom. Please stop, it's tiresome.
I know. Go was born perfect. That anyone might like Go but think it could be better by changing it disgusts me too! All you whiners -- if you like Go but think it could be made better then please use another language.
That might be how you “feel” but it doesn’t reflect the facts. The go community has been incredibly welcoming, the process for introducing new features in go is fully transparent and in the open.
If you link to the reddit post we could all see what he actually said. Go is a very opinionated language for sure. Every language will be inundated with people who want their pet feature to be implemented in it and will hate on it otherwise. I am happy that the language development is not directed by such kinds of folks.
Well... Go is a language that wants to be something specific, rather than be something for everyone. That means that some suggestions just don't fit, not because they're hard to implement, but because they're wrong for the language. It is very appropriate for the language designers to say "If that's what you want, then you don't want Go. There are other languages that fit what you want better; go find one and use that." That's completely reasonable and proper.
It's also possible for the language designers to say the exact same words, and be ignoring real problems with the language - problems that could be fixed within the spirit of the language.
If you go to the Haskell people and say "This language would be so much more usable by so many more people with an Algol-like syntax", they're not going to listen to your helpful suggestion. What you're asking for is something that would not be Haskell. But to those asking, they think it's a perfectly reasonable request.
I don't have a great example of the second kind, because languages with those kinds of designers typically die early.
As someone working at google, I had never written go code, but I had read quite a bit about how how people didn't like it on places like hn. My impression was that even though personally I would strongly prefer a more typed language like rust, the deficiencies of go were probably overblown.
Then I recently had to write a small amount of go code... and I found out that go doesn't support enums....... what??? Just blew my mind.
In other words, go has somehow managed to shoehorn themselves into a language so rigid that adding pretty basic correctness features comes with serious downsides.
my hypothesis was that as the world has moved forward, Google has accumulated legions of C++/to a lesser extent Java/C experts that they needed to corral into modern software engineering, to include programming languages. Go is exactly that - good enough - in part due to:
- has enough C-like syntax to keep the old "this doesn't look like C/C++" guard muzzled (as opposed to e.g. Java)
- avoids some of the huge C++ productivity land mines (no package management, divining which Boost version to use, build systems, etc.)
- has a 21st century standard library (net/http as a first class citizen as opposed to whatever the C/C++ ecosystem "offers")
- fast enough and memory performant enough, whilst exposing pointers, unsafe ops, and C interop should the need arise
i think most of the above are mind tricks to get the C++ crowd to complain slightly less while increasing productivity multi-fold.
Vendoring prebuilt deps (jar/so/dll files) is the Right Thing. Anything else creates an extra point of failure that could prevent you from going to prod or reproducing an old build. Some take this seriously enough to vendor the exact toolchain as well.
I meant vendoring the source code of dependencies when I wrote the parent comment, but it appears you feel strongly about going one step further? Vendoring arch-specific binaries? What is the benefit of doing that, in your opinion?
I just don't want to be in the business of maintaining our copy of someone else's toolchain and constantly rebuilding code that my team never edits. For non-java I could be talked into in-tree headers and moving native libs to a private deb/rpm repo.
>The Zen of Go is looking at individual persons and deciding whether it's net positive to cut them now, or 6 months from now, after their next performance review.
Your conclusion is nothing more than a hilarious ad hominem attack on the language.
> is outweighed by the value of deliberately handling each failure condition at the point at which they occur. Key to this is the cultural value of handling each and every error explicitly.
I've talked about this before, but it annoys me a lot that checking if an error exists and returning it is referred to as "handling the error". You are not handling anything - you're passing on responsibility up the stack just as clearly as if you had thrown an exception.
You're just doing it manually instead of letting the runtime do that for you. You're also making it much harder to tell the code that handles errors apart from the code which doesn't.
I think what people usually mean in these situations is that you’re forced to think about the error.
Programs in C (and many other languages) have the problem that you can ignore errors accidentally.
Exceptions “solve” this by forcing some level of your stack to at least acknowledge that an error occurred. The problem with that model is that you still don’t have to think at any given level of the call stack what the appropriate handling is for any kind of error. You can just let it propagate up further, deferring responsibility.
For some code that might actually be appropriate, but often it results in code higher up that cannot possibly know how to deal with every error that can happen farther down the call stack just swallowing errors.
One thing I frequently see is mixing validation code with processing logic. By separating the two you can have the validation code deal with errors and the logic simply assert that it’s input is good (because it should have been validated by the error handling code before being passed in).
I think models where you are forced to think about errors at each level but have a simple mechanism to make them propagate up are probably the best single model if you have to choose one model for a language.
In any event, error handling is difficult and frequently doesn’t get the attention it deserves in the design and implementation of libraries and large systems.
In my experience, the vast majority of error handling has been essentially limited to bubbling the error up, cleaning up unmanaged resources, and either re-trying the operation with some back-out logic or giving some details to the user about what happened and letting them decide what the best thing is.
I have very rarely seen something useful to be done by code automatically to recover from an error, other then re-trying. As such, I feel that the way Go makes you constantly write error-handling code is counter-productive, since it forces you to think about something that really doesn't need that much thinking about.
And as I said earlier, the deluge of non-handling error handling code can serve to mask the rare cases where errors are actually handled in some tricky intelligent way.
That can and should be generated; it's not as if the machine doesn't know which function failed. We don't force human beings to plow through all the rote instructions that set up a stack frame or invoke an object's implementation of a method, and for the same reason we shouldn't force them to plow through the rote instructions that bubble an error toward the handler. When errors exist in your problem domain, manipulating them becomes part of the language's job, and nearly every language from the last thirty years acknowledges that.
I mean, that's a not in the standard library, and it's a library that's literally already in maintenance mode.
Edit: you also deleted the context of your call. You didn't create an error type for Unwrap to match on. That means that downstream code only has extremely primitive errors to match on, with the context of the call gone. Just because you can track down the EOF error, doesn't mean that code calling this can do anything useful with that. 'Socket closed unexpectedly' is way less useful than 'Unable to update user record'.
But they were right about it losing error context. Basically errorf can only help you bubble up the lowest level error in a machine readable way. You have to write your own error type if you want to let code handle the higher level errors.
One of my pet peeves about Go is a lot of errors are not exposed as public variables, so your options of handling them are somewhat limited other than a generic "something went wrong". I've recently written a tool that classifies various http request errors (think SSL failure, not a 500 status code), and have had to have a deep dive into Go reflection as part of that.
I understand part of this is because they don't want to have to worry about making potentially breaking changes - but what I've had to do means my code will break if the internal API changes, and as it's using reflection the compiler won't catch it. It would be nice to have a "yes I know I shouldn't, but I really want to" (ala Ruby's send method) way of doing this that the compiler will still check.
> You're also making it much harder to tell the code that handles errors apart from the code which doesn't.
Error handling isn't distinct from, or less interesting than, the principal logic of a block of code. Error handling is frequently the most important part of a block of code, especially in the domains that Go targets.
And the former is less than 1% of the cases I’ve seen from experience. Somehow people have fooled themselves into believing that the language forcing humans to be manual exception bubblers somehow means they have “handled” the error.
This is a misnamed article. There is nothing zen about this article. A better title would be “I’m mad that no language is perfect” because that is really the gist of the article. Author has nit-picks with every contemporary computer language. “If only someone would write the one true language, here is why every other language fails to be that, blah blah blah.” Pure opinion piece.
> Author has nit-picks with every contemporary computer language.
Doesn’t everybody? Programming languages are so vast, and designing one involves so many trade-offs. It’s impossible to find one that’s perfect, where you agree with every decision made by the designers.
I was a fan in the past and still think the language has niches it's good at. I just no longer think it's a good general purpose language - more correctly I think there are languages that are better than Go in every metric that counts for general purpose use cases.
I invested a lot of time and work in the language (about 4 years professionally) but become disillusioned with it over time.
The big reasons for no longer liking the language stem from decisions made around the type system (this being the main reason), tooling (namely packaging, vendoring/modules bleh) and weariness with the verbosity and logic per line density.
So when would I still pick it?
I would pick Go if I specifically need to shuffle bytes between sockets with high I/O concurrency and don't need access to C libraries or any complex logic at all. The moment there is business logic I don't want to be writing Go anymore.
I'm still using it daily, and I think it's a "nicer C", perhaps sometimes even a "nicer C++" or "nicer Java" in a few ways. But I agree that as a general purpose language it's definitely lacking. No other language has inspired me to fight against its design and philosophy or overcome its deficiencies as much as go has ([1] [2] [3]). But for many purposes it's "good enough", and at least it's simple enough to dig around in the guts when I have to.
For me right now it's Kotlin. I believe it picks a good balance of simplicity/readability vs expressibility/power.
In particular I find it's generics, data classes, infix operators and extension functions very useful for expressing business logic concisely.
These features let me write half (or less in many cases) the code I might need to in Go.
More importantly is Kotlin is much easier to refactor. Part of that is there is just less code to refactor, the other important piece is tooling and IDE providing awesome automatic refactoring tools.
In addition, cgo calls are often reeely slow. I've been using the sqlite bindings to go for projects recently. sqlite doesn't allow you to write your own in-language functions, but instead allows you to write your own callback. I recently did some benchmarks, and the golang callback for a simple function was 20x slower than implementing it in C (1600us vs 80us), presumably due to all the marshaling and reflection required.
Using cgo has been the source of near endless pain for me when writing systems software. It criples portability, drastically changes runtime behaviours and generally is best dealt with by CGO_ENABLED=0.
> In Java, and Ruby, and Smalltalk, the core value that everything is an object drives the design of programs around message passing, information hiding, and polymorphism.
This is certainly not an accurate description of Java…
Java has Simula-style OOP. The characteristics used there are often used to describe Smalltalk style OOPs: note that Java does not have message passing, and it has primitive types.
The principles and origin of Go are dear to me, I feel philosophically aligned. Focus on: Dev tooling, simplicity, large scale collaboration, networked services and readability
It’s interesting that you and I have chosen the same language for completely different reasons. I’m interested in static binaries, “batteries included”, better-than-interpreted performance, and parallelism.
Indeed, I've had the same impression from large codebases in C++ - it surely looked like things worked in spite of all the effort to make it as complex and as inefficient as possible. (This, actually, may be a praise for C++ - you can write it as though it was Java and still get a fast program.)
The simplicity of the language means no gigantic class hierarchies. Theres are fewer ways to do something.
Since engineers spend most of their time reading and debugging, readability and low abstractions facilitate collaboration: it should be relatively straightforward to jump into any Go codebase.
People often laud the relative ease of picking up the language. They're productive quickly, that's not by accident.
The fewer ways just means that now you have to jump through hoops to fight against the language, ending up with more complicated code bases (and this has been observed in practice).
> People often laud the relative ease of picking up the language.
That's not a great metric to measure the effectiveness of a language. Furthermore, there are still gotchas that people have to learn to be aware of, and patterns of how the language does things that need to be learned. Again, the same can be said of Java/C#.
I agree but there is a significant tradeoff. You are forced to use lower level expressions, meaning you reinvent algorithms and more importantly their names.
For example splitting a collection with a predicate in FP jargon would be „partition“. The function name is descriptive and universal (to those who know it). In Go it is another for loop, which is more verbose, can be implemented in different ways and doesn’t show intent until read and understood.
This rather small example shows how Go programming (read: reading/writing code) doesn’t scale as well with better understanding and experience, as programming in more expressive languages.
Note that I‘m not saying that this is absolutely good/bad. There are obvious merits to Go‘s approach.
Sure there is, spaghetti interface implementations, even types that happen to implement interfaces that they didn't intend to, with methods that happen to match the signature, having completely different semantics.
Those aren’t hierarchies as I understand them, nor do I understand those to be real problems (I’m sure someone has been bitten by one of those but only by the law of averages).
What are you talking about? You could do exactly this in Java or C# or Python or Ruby or JS or etc. Not sure what ”traditional” languages you’re talking about, but this is just reflection and downcasting. It’s a bad idea, but it’s unrelated to OOP.
They aren’t “hoping it implements an interface”, they use reflection to determine whether or not the type implements the interface. This is pretty much the same thing in Java or C# with reflection and downcasting, but it’s a bad idea in both languages.
As someone who has written more C# and Java than Go... I can read Go much easier than most other languages. I can step into a huge codebase and usually quickly reason what/where I need to make my changes without too much effort. This can be the case with C# and Java, but it seems to almost always be the case with Go.
This means there's no payoff for specializing because the project has no domain-specific idioms that help at all with clarity and concision—no matter what you learn and how fluent you get, all code will look like the bulky mediocre stuff a novice would have written.
All these things are available for Java (MVN/Gradle, JUnit, IDEA, YourKit, etc) but having one right way to do them built in is pretty great.
On a meta level, combination of the AST package and deterministic code formatting means it's relatively easy to write programs that manipulate source code.
> All these things are available for Java (MVN/Gradle, JUnit, IDEA, YourKit, etc) but having one right way to do them built in is pretty great.
It's great if you're just starting out, but it's irrelevant in the long run. It can even be harmful when it's coupled with the language, as you get things like having the packaging tool become deprecated, like with go dep, since a 3rd party tool can't compete with a first party tool for adoption, especially in a relatively young language like Go.
For pprof vs VisualVM - I haven't yet done a lot of CPU profiling with them, but for memory I have had the exact opposite experience. VisualVM and Eclipse MAT are much easier to understand and have much more functionality for analyzing the memory of a Java process than pprof offers.
Not sure what (pseudo-)deterministic code formatting has to do with the ease of manipulating program code, but having a built-in AST package is indeed a good idea. Too bad it hasn't been used to write any useful refactoring tools for Go (outside of JetBrains Goland? Haven't tried that yet). I have heard that Google uses go fix recipes to make modifications across their code base, but haven't seen any examples, and I doubt it can realistically be done in a code base that is not ridiculously well tested, and that it didn't require any manual intervention in a few corner cases in Google as well (assuming they did more than a rename).
I’ve written a a few, all internal sadly. Deterministic formatting is important because you can manipulate the AST and then just call “print” and get a nice diff. Without that it would rewrite entire files.
Ok. But you get to stop discussing some style choices, and instead you lose a decent debugger, a decent (open source) IDE, any graphical profiler, good package management that is separate from your source control tool, and probably a few others I'm missing.
And if you care about style trivialities ,you'll still need an external linter, since go fmt doesn't enforce anything about variable names , line length, function length, comment placement, line breaking and many other things some people like to obsess over.
The beauty of Go is that it's so simple that you don't need a debugger. Do you need a word dictionary to read this comment? No, because this comment is simple. Same with Go. But if you need a debugger, there is Delve [1], though I only used it once to debug a dynamic programming algorithm.
I know about delve, but it's an extremely basic debugger. You can't even pause program execution on demand or execute an arbitrary function while stopped at a breakpoint.
Beyond that, the complexity of a language has nothing to do with how much you need a debugger. If you are writing a complex program, you sometimes need a debugger to quickly figure out what is going wrong, instead of endless theory -> change/log -> rerun cycles.
> Beyond that, the complexity of a language has nothing to do with how much you need a debugger
I disagree. There is a higher chance you need a debugger when trying to understand, say, Scala code versus Go.
> If you are writing a complex program [...]
Complexity of a program depends on its author. Even a stupid simple function can get complex if the programmer is less experienced. Go has a philosophy to simplify things. The community tries to follow this philosophy as much as possible, which is why a lot of Go code is easy to read regardless of how complex the logic is.
In the majority of cases, a need for Go debugger means you are dealing with bad written code. Heck, this idea can be extended to pretty much any language.
I think the general agreement is that there exists essential complexity - the complexity of the problem domain you are trying to solve - and accidental complexity - extra complexity introduced into code code by the tooling or by shoddy design.
Simple code often doesn't need a debugger to be understood. Complex code sometimes does. But code can be complex simply because the problem domain is complex.
Also, a simple tool can very well introduce accidental complexity than a more flexible (and therefore complex) tool. Go is famous for doing so with many of its design choices.
A very good example is if you want to perform a set union of two collections. In many languages, you could simply create a set from each collection and then run the union operation on them. In Go, the easiest way to achieve the same is to use maps and rely on the fact that the keys of a map happen to form a set, while ignoring the value part of the map entirely. This is not only inelegant, it is also unintuitive and it wastes memory, but is at least slightly less complex than writing a set data type for your struct by hand.
Yes, things should be written in a simple way, but excusing the issues with the program with "Well, the developer should have written it simpler" does not resolve the problem, just shifts the blame.
Also, even though there is no default built in unit test framework in Java and C#, it would be hard to claim JUnit and NUnit aren't essentially that. Ditto for log4j in Java.
By the way, I'm not sure you can claim to have a logging framework in your language when it can't even differentiate between debug messages, info and error messages. I'm not sure there is any significant project that can rely on Go log that couldn't as easily use stderr/stdout.
To be fair, all languages that have not had modules built in suffer from this to some extent or another. The main good part about Java is that they have compatibility between code using modules and code that doesn't.
My favorite is having to change Git's global settings if you want to use a dependency that's a private repository. The system is awful through and through.
> 1) Extremely fast build times (even for large codebases).
Not much faster than C# or Java in practice, especially when using a build system that caches your build so you don't have to rebuild everything every time. The difference actually gets smaller the larger the program is. Furthermore, it's (unit/functional/integration) tests that usually take the longer time to run anyway, which you have to execute before submitting your diffs (and caching helps there as well).
> 2) The package system plugs directly into Git/Github.
This doesn't suite everyone, as not all projects are open source.
> with a lower administrative burden.
Any serious company is not going to (1) allow arbitrary code to be pulled from github/etc. and (2) needs to maintain a local repository of it anyway in case github/maven/etc. goes down.
Go’s tooling is generally straightforward and there is usually one canonical/standard implementation. No insane DSLs to build your program, no daemons required for fast builds, and batteries are included (test framework is part of the tooling). No crazy documentation syntax, no docs packages to publish and link against your dependencies, etc. In general, Go is simple so your organization can focus on business problems.
Well, I have. 300k LOC which was ported over from Java to reduce complexity. Go is mind blowingly fast in compilation times compared to Java. Arguing on it is a waste of time tbh
See https://talks.golang.org/2012/splash.article "Language Design in the Service of Software Engineering" for some of the design goals of Go and how they led to its current design. (For example, gofmt/gofix make automated refactorings across a large codebase (different teams) easier or more pleasant.)
I mean it is beyond madness to even think that Go's dev tooling compares favourably to Java tools & ecosystem on the friggin' JVM.
Dev tooling surrounding the JVM blitzes pretty much everything both in terms of dev(maybe code reloading could be improved for Java ala Clojure) & operations.
> I think that we can all agree that when it comes to Go, simple code is preferable to clever code
What even is "simple code"? The problem I have with Go is when you need to do anything non-trivial the intent of the code quickly becomes lost in lots of for loops. You can try to abstract that away so that your program reads better, but in my experience that has always been a poor decision. I get the impression Go wants me to write big for loops that do lots of work and have lots of scope and that it doesn't want me to break my problems up into steps (like I might in a functional style by separating each stage of the computation). Go wants me to have mutable variables and mutable references. State management in Go is questionable and really left up to the programmer (mutable references and data hiding are too easy compared to functional languages where immutability generally forces you to think harder about your data's structure). Sure, this is usually going to lead to a more performant end-result, but it's not going to lead to the "simpler" result (in my eyes)
There's a lot about Go that I do like - the toolchain and docs are particularly good, and the standard libs are modern yet succinct. It is low-level enough when I want it to be and relatively high-level (nits above aside). The support for concurrency and distributed systems is also good. I can see how "simple" is exhibited there
If I had to guess at what "simple" means to Gophers (well, the Golang team at least) it would be using the fewest number of language primitives/features to get done what you want to get done. There are a lot of peculiar ways channels are used for example where other languages might prefer things like condition variables. The range keyword is overloaded and used different in several contexts but it does keep the number of keywords low. Most Go tends to look the same (low-to-the-ground, verbose)
Go is great until it isn't and when it isn't you have little choice but to brute force around some of Go's clunky pieces. Extracting high-level meaning out of Go source can be difficult because of how verbose it can be. I'm glad it's around and it's a good choice for a lot of projects, but I really wish we could have simple things like generics so we can write cleaner code
In short, Go as a language may be simple (and I think it is) but I need to solve complex problems - either Go helps me with that complexity or it leaves me on my own. Go has chosen the latter of those options and I'm not convinced that our Go code can be simple for many non-trivial applications
Like others that have been downvoted to invisibility in this thread I'm a bit upset about the namespace conflicts between the new computer language and the old but highly active game. This "Zen of Go" has to be an intentional overloading of the traditional meaning.
Also, the use of "Zen" to refer to anything intuitive or aesthetic.
Zen itself is pretty fascinating. It sucks to see it invoked as an excuse for "I'm going to go on at great length and at the end still ask you to take everything I said on faith because it's simply too profound for argument."
> Avoid package level state
Seek to be explicit, reduce coupling, and spooky action at a distance by providing the dependencies a type needs as fields on that type rather than using package variables.
stdlib was once an exemplar of good, idiomatic Go code, but a lot of the idioms it uses haven't withstood the test of time, and it's less and less appropriate as an example.
This is true, but it's still worth poking around for pre-existing solutions to problems. My editor will jump to stdlib definitions so I've learned a lot just diving into various stdlib packages (I remember the JSON parser being a good one).
>What if, instead, we take a position that rather than enhancing components, we replace them. Then the best way to know when something needs to be replaced, is when it doesn’t do what it says on the tin.
This is something that I have had trouble reaching a balance. Replacing components could be costly and therefore it makes sense to design components that are extensible (and seem over engineered) especially when the requirements aren’t known completely? What are your thoughts on this
Not the author, but I'd prefer rewriting the code as the requirements become clear rather than building something unspecific. In my experience the benifits to code reuse are vastly overstated and the costs of added complexity vastly understated.
You've replaced a "could be costly" (rewrite if we need to) with a always costly (over engineer for expansion).
Also, when you don't know the requirements completely is the prime time to do/write as little code as possible. You can't know future and guesses are more often wrong.
You should really push back on teams managers that ask you to write code without telling you what that code should do.
Learned over 25+ years of professional dev xp. Coding for 39yrs.
Thanks @njharman , how does one consider between rewrite vs expand when it is known that some requirements will be true not for the current release but 2 or 3 releases down the line. Would you still prefer rewrite vs expansion? The reason I ask is I got severe pushback recently where I proposed building generic enough components and infra to go along with it that could have satisfied the requirements for the next 2 or 3 releases
In my experience, reality is unpredictable enough that no one actually know what the requirements will be two versions down the road. In practice you end up with code that is more complex than what the current problem needs, trying to solve a future need that you presume will happen but you don’t really know what it is and when it will happen.
Here's a hot take from a polyglot who does a fair bit of professional work in Go:
Go is a low-blub language whose advocates are proud of the fact that they never made it past 200-level CS courses. It's the computing equivalent of the blue-collar anti-intellectualism that is rampant in politics these days.
Yes, Go the language is simple - it pushes complexity off to your programs instead! Instead of functional expressions, Go programs are pages and pages of iterative loops and variable manipulation. Without generics, you can't use even the most basic functional constructs like map and reduce. The most boring mainstream languages like Java and Javascript are adopting functional paradigms, whereas Go is actively hostile to functional programming.
Nothing is more tiresome than the continuous prattle about exceptions. "How can you possibly write code that is robust in the presence of errors when you don’t know which statements could throw an exception?" Easily. That's the great thing about exceptions, you don't need to know about them to write robust code.
In general business processing, an exception thrown across a transaction boundary rolls back the transaction. In most cases that's pretty much all you need to know. In the case where you need to explicitly rollback a state change (extraordinarily rare in business processing), you add a try/catch/throw, and you don't even need to look at the exception!
The fact is, 99% of programs need only one exception handler. In webapps, it's the http processor that returns a 500 error. In GUI apps, it's the main execution loop that shows an error dialog.
Go's error handling not only requires endless tedious "if err != nil return err", but each one of those statements destroys stack information. Folks like to berate Java for long stacktraces, but those stack frames are valuable for debugging. By comparison, Go's errors are an opaque bit of text. Third-party libraries can help by wrapping errors in other errors (hand-building stacktraces), but this won't help you when using third-party libraries that don't use these wrappers.
I'll say something positive about Go, and why I still use it: It's a better C. For low-level-ish code that needs performance and fast startup time (CLIs, simple GAE services that scale to 0 and need immediate startup), Go fits the bill. Having concurrency built into the language is nice, although most other common languages have equivalent facilities in their libraries.
But as a language to take over the world: Not a chance.
Please review the site guidelines and, when commenting on HN, do so more in their spirit. We don't want programming language flamewars here, especially after seeing what they did to other communities.
> whose advocates are proud of the fact that they never made it past 200-level CS courses.
I agree with many of your points but is this kind of generalized ad hominem really necessary? There are many people (myself included) who don’t have CS degrees yet are still capable developers and programmers. I don’t see how criticizing someone’s lack of a formal CS education has anything to do with criticisms about Go as a language.
It's a deliberately provocative comment to be sure. But I'm not trying to demean people who don't have CS degrees, just the ones that are proud of it.
The Go community puts out a lot of smug articles like the OP bashing other languages for having features that Go lacks. Inside the community it's largely an echo chamber. When this stuff hits the broader community, I want pushback.
Just a tip, code formatting does not work well for quotes because it often leads to unnecessary horizontal scrolling (especially on mobile). A common convention here is to use a leading > character and italicize the quote by enclosing it in asterisks, like this:
> *This quote can be any length without horizontal scrolling*
which renders as:
> This quote can be any length without horizontal scrolling
Update: Thanks for making that edit. Much easier to read on mobile now!
Someone had to say it. My first impression of Go was that its creators have not worked on a modern, large scale system in decades. And they probably have not. Go skips about 40 years of advances in language design and code maintenance. We have stack traces for a reason. We have exceptions for a reason.
We came to it as software has become more complicated. The fact that you have to assemble the path to the root cause of an error yourself is completely bonkers.
Are you sure about this?? Go is being used to build some of the most important software in devops and databases today. Maybe its the thinking thats different but certainly it doesn't make codebases complex by itself.
Like Terraform? Yes, so? That doesn't change my opinion. It takes serious experience and skill to write good Go code and tests at scale. I don't really understand why people claim that Go is a simple and straight-forward language. For a "Hello, World", maybe, but once you hit the gotchas, the fun begins.
It can be difficult to reconcile comments like this with the reality that Go was designed at Google to address difficulties developing large-scale applications and even Kubernetes is written in it.
I agree with many criticisms of the language but sometimes that criticism seems to enter its own sort of echo chamber.
I don't get the impression that's a conclusion you came to on your own, and I don't find what is essentially hearsay or gossip to have a very high information signal in general.
That boils down to Go needing generics, which I agree with. I don't think we're completely in disagreement. I'm just skeptical that the language is quite as bad as you're sort of suggesting. That article falls short of condemning the k8s codebase and instead just says it could be better with generics.
To be clear I'm not the person who stated K8s has a garbage codebase or whatever. I was just linking relevant material (and I should have clarified that in my post instead of drive-by posting - sorry, was distracted).
this is an unnecessarily divisive take from a "polyglot" (read: software engineer) who admits that Go has its place in a software engineer's toolkit. It seems that you're attacking a straw man who believes that Go should be the only language that exists, and is the right choice for every application? Who argues this?
I find it's the strawman that many (most?) Go "opponents¹" respond to.
Every language has its die-hard cult of a few who claim it should be the one and true only language in the world (and the claim is always wrong). But Go is the only case I know where I see people actually respond to that. As if it were a real question.
I think it's a combination of many aspects:
- Go is quite opinionated as a technical object, thus polarizing; which attracts more contentious debate(-rs) than average.
- In truth, Go was meant to be niche, and wants to remain so; it never actually tried to become as big as it is. The whole "take over the world" mantra is pretty much alien to its core design, its primary intent and, most likely, ultimate destination.
- Go has a few powerful traits (great at concurrency, great at scaling simple services, etc.), but people want to generalize, and indeed Go's not far from being quite more general (if it had generics, etc). There's no consensus as it's arguably hard to bring in more expressiveness while preserving the intricate mechanics of its elementary objects (each must respond to all others properly). This "in flux" situation has passionate advocates on all contentious sides.
- Google's weight behind Go makes it feared by some, but it's not like Google is trying to make Go "the one and only", even internally (that proposition doesn't make any engineering sense).
- Some hype, by 'influencers' in the space who fell in love with it, made Go a shining beacon of something— this elegance in simple abstractions (when it fits one's problem space well), its manageability by teams, ease of programming, etc. There's something to be said for these qualities, but again it was just wrong to generalize... it's just what people do. We move in cycle of hypes, trend-waves, it's best to keep a Bayesian mind through all of it.
So much ado about a niche.
[1]: wth with that even being a thing, it's a programming language for Turing's sake. Let us recall that languages are neither good nor bad, but shit-posting makes it so. Yes, even Rust: neither holy nor evil, I assure you.
What are your thoughts of Ada/SPARK as a replacement of C? In my opinion it is a better C. At times I wish it were not so verbose but I consider it a feature. It has everything you mentioned that made Go fit the bill, and so much more that makes it even better at both low-level programming and high-level programming. My main and only problem with Ada is that it lacks libraries, it needs a more vibrant community. This can be changed though!
As someone who fits this description "never made it past 200-level CS courses" how do I make the leap from interpreted basically procedural programming to a compiled language like Go, or C, or whatever else - the mental model seems very very different to me, and I have trouble understanding what the program is doing when I try to just read along.
My advice is to ignore gatekeepers like OP and just start playing around with Go. The Go Playground is a great place to start, you can run it right in your browser. https://tour.golang.org/welcome/1
You can try Pascal. I switched from BASIC to Pascal 30 years ago then to C 5 years later. And even though C has been my everyday language for 2.5 decades for all my projects, Pascal was still the one that provided me the most natural mental model and that was the easiest to learn by practicing without lessons. I used to code as I thought without being interrupted thinking about stupid stuff like "I need a buffer to store this argument and to verify its length, ah shit I then need the ability to return an error if it's too large". In TP7 you even had "break" and "continue" statements that were missing from previous ones, that made it closer to C and a real joy to work with. The code was quite clean and extremely readable even with few comments.
The lower you get the more your model of computation must understand the host system; but be careful, even C statements often compile into surprisingly unexpected machine code.
If you want to explore this route I recommend picking up an embedded hobby project; Ie grab an Odroid Go and write some fun little arduino games in C++ using the Esp-Idf toolkit. It's got two cores, some IO, and a slowish LCD; and so you'll have to understand async programming and have a mental model of the device's memory to get any satisfaction.
But that still doesn't require much beyond most 200 level computing knowledge. :)
I think you are going in the opposite direction to which the OP seems to be lamenting.
First of all Go is not C; it’s much much easier to learn and write production grade services easily. There are a ton of free/ online resources to learn go; I’m self taught and have written production code in go for the past several years with no formal training. The godocs are great; YouTube has videos from gophercon and other conferences as well.
Second: OP is lamenting the lack of higher order programming paradigms like Generics; If you’re using an interpreted language it likely already has support for those things.
Ingenuity in code for solving real problems is great. Often times I’ve seen those who know a language deeply write code in ways that makes it remarkably hard to read. Mostly it’s because language features are used in unexpected ways which the author feels is great for x reason, but reduces readability.
With Go, it’s actually really hard to write opaque code like that since the language is so laser focused on low level primitives. And honestly... for core systems, I think that’s an incredibly good thing to have.
>Go is a low-blub language whose advocates are proud of the fact that they never made it past 200-level CS courses. It's the computing equivalent of the blue-collar anti-intellectualism that is rampant in politics these days.
But there is, and has for a long time been, a push for blue-collar "coding" jobs, because highly educated programmers are expensive. It dovetails especially well with the mentality where only the next quarter results matter, and software is a cost center, which is a lot of places.
Go is popular for the same reason PHP is popular, or why $0.99 goods are sold in huge quantities despite their quality.
To be fair, the implementation of the Go compiler and runtime is done by real experts, and is impressive. I suppose that nothing in Go is done by ignorance; everything is a conscious choice, to meet the bottom line: a person can learn enough Go in a weekend to be immediately productive.
> The fact is, 99% of programs need only one exception handler.
Maybe if you just write high level business logic and something else already handles exceptions for you. I think my Java code typically has 70% of the exceptions handlers that I also would need in Go. Need to close upstream connections, release resources like buffers, etc. If it’s not catch blocks, then it’s at least a finally block. And often it’s also a good idea to catch and transform an exception, so that the calling code doesn’t need to deal with exceptions that come from pure implementation details.
The parent implied that in exception based languages you can save all the boilerplate since exceptions just propagate. My statement here was that I don’t think this is true, since you still need to do error handling (and have error awareness), eg for resource cleanup during those.
I am very aware about try with resources. It works however only if the lifetime of objects are tied to a scope.
* The overwhelming vast majority of functions do not allocate resources that need to be explicitly cleaned up.
* Cleanup is no more painful with exceptions - try/finally is not materially different from defer.
* In any language, a healthy pattern is "if you allocate it, you clean it up". This pattern in Go and Java looks basically the same.
* Go's lack of exceptions mean that you pay the penalty of error handling on every function call, not just in the tiny fraction of a fraction of a percentage of function calls that allocate resources.
* For business processing... this is just a non-issue, full stop. There's generally only one resource that gets allocated - a transaction - and it gets cleaned up automatically on exceptions. All that noise in Go is an utter waste of time.
Much of your complaints simply do not apply to many use cases for people when developing a server side web application. Just because Go does not have such features you are used to using in your high level language does not make Go a bad language. I also find it funny that you dislike the simplicity of the language because it apparently empowers "blue collar" types (uhk, how gross!) to do the jobs you are used to doing.
Did you just call Rob Pike, et. al idiots, dumb, and landing-on-the-moon-denying Trump supporters?[1]
Thanks for the good laugh. I like the simplicity. I like that it shuns the insane nested psycho-babble that heavily OOP natured codebases can get into.
[1] I am joking, but only to prove a point. Rob Pike is not some CS drop out.
Please don't post in the flamewar style to HN. We're trying for better than that here, especially after having seen how programming language flamewars destroyed other communities.
> When you can't compile because of one unused variable that was commented out . . .
You smile in delighted satisfaction, knowing that the compiler will protect you and your team against this all too common form of bit rot — a protection that, until now, had to be enforced merely by convention and review.
I don't want to be warned, I want to be stopped, and, more importantly, I want everyone else to be stopped, too, unconditionally. -Wxxx doesn't get me there.
For a little while I used to compile with -Werror but I found it very annoying. I very often want to make a quick test commenting out a function call or doing an early return that would trigger a warning for dead code and prevent me from continuing.
It might be a good idea to add it to your build bot though, this way you can enforce it for commited code. That's a good compromise IMO.
You use linters to catch those issues (and more). Any quality project will have linters, CI/CD etc. set up. And it's not like golang obviates the need for linters, if anything, it needs them more than other languages to deal with several issues, including (ironically) accidentally ignoring handling errors.
Is ignoring errors really a problem in practice? I’ve been using this language since before 1.0 and I don’t think I’ve ever been burned by this. I’m sure it happens once in a while (albeit at very low frequency), but people in this thread act like you can’t build software in Go because the compiler doesn’t catch every ignored error.
There’s a huge difference between baking a cake for your kids and running a cake factory.
Enforcing that unused variable rule (and, in some sense the standardized code layout), for me, is sort-of assuming all code that gets compiled will be deployed at scale in a mission-critical context.
A language that also wants to cater to hobbyists should, IMO, be a bit more liberal in what it accepts, and provide flags to enforce additional rules for those who know they need them.
(I also think go could go further in enforcing quality. Code that does in, err := os.Open(path), where in doesn’t escape, should call defer(in.Close). The compiler could enforce that)
> Go is primarily a language for programming in the large
Other than the claims they put up on their blogs and presentations, what hard evidence do we have that this is actually the case ? Java and C# are much more suited for "programming in the large" than golang.
I'm speaking about the language's principles of design, not its market efficacy. For this, there is no higher standard of evidence than the claims of the designers.
> Java and C# are much more suited for "programming in the large" than golang.
I don't agree. One point would be Go's faster compile times.
The standard of evidence is what I see when I use the language at work. And so far, I don't see much that validates the golang designers' claims to be honest.
> One point would be Go's faster compile times
I'm looking for more substantive points, as frankly, this doesn't cut it. I just posted this in a different comment on this thread:
> Not much faster than C# or Java in practice, especially when using a build system that caches your build so you don't have to rebuild everything every time. The difference actually gets smaller the larger the program is. Furthermore, it's (unit/functional/integration) tests that usually take the longer time to run anyway, which you have to execute before submitting your diffs (and caching helps there as well).
I worked at an employer that heavily uses golang, and I'll tell you that build times for actual large scale projects (pulling many dependencies) were not much better (if at all) than Java's.
> The standard of evidence is what I see when I use the language at work. And so far, I don't see much that validates the golang designers' claims to be honest.
Either we're talking past each other or you're being deliberately obtuse.
The claim is that Go was designed to be a language in service to software engineering, for programming in the large. The evidence for this claim is that the designers claim it, that's totally sufficient. The fact that your experience of the language doesn't sync with this claim is irrelevant, because it has no bearing on the design of the thing. If you want to claim that the designers have failed at this goal, that's a totally separate argument.
> The evidence for this claim is that the designers claim it, that's totally sufficient
My argument is exactly that, their claim is unsubstantiated, and people's experiences in practice contradict their claims. Not to mention that some of their design decisions are objectively bad, and go against their claim in the first place: https://github.com/golang/go/issues/16474
If I say I designed X with principal Y in mind, that claim cannot be contradicted or considered unsubstantiated by other people's experience of using X. It's a statement of intent, not of consequence.
> Not to mention that some of their design decisions are objectively bad
Oh, you're just grinding axes. I'm sorry for engaging.
I'd say you weren't exactly engaging in good faith, either. It's pretty clear that their point could be summarised as something like: Go's designers tried or intended to design the language for programming in the large, but they failed (perhaps partially), so therefore Go isn't (after the fact) designed for programming in the large.
You can argue about whether they really failed or not, but your pedantic take on it wasn't helpful for a discussion.
That does not work, because warning are generally based on heuristics, may have false positives, and are not consistent between different implementations and versions of compilers.
Therefore, fail for error is acceptable for internal development, but not for released code.
If i release a project, i would not want users to fail to compile it just because they have a slightly different compiler version.
Actually, it does work, because languages tend to provide ways to notify the compiler that what you're doing doesn't warrant a warning. I've worked on big C codebases, some of which I built and some of which I inherited, and I was always able to get it to the point where it would compile with -Werror -Wall -Wextra etc, many times even -pedantic, with very few (if any) pragmas. The only exception to this was compiling for MSVC, which is no surprise since they don't really care about C.
When I use an external library in my project, a part of me dies inside if I see a stream of warnings. So I understand where Pike, Thompson et al are coming from. They're just misguided in their approach.
The trouble with C is that there's no "official" compiler like modern languages have, so there's no consensus on what merits a warning or not (and the spec is so terrible that you'd have a hard time coming up with them anyway). But for modern languages, you have a clean slate to work from, which means that we have an opportunity to do it right this time.
Practically any large scale project is going to have linters involved at some point. This seems to be a much better suited task for the linter to catch than to be set at the compiler level, since it severely hinders programming locally.
If I insult your mom, I don't insult you, yet you feel insulted.
Same with things you spent a lot of time building.
Now if OP would have said "use case X in Go is absolutely garbage because of Z", it would have been better received. But he didn't.
Because, you see, insulting, insulting anything really, is removing space for debating, sharing ideas and advancing arguments. It's the same reason this site asks for not posting things like "+1", "first" or memes.
And it's why, being carried away by the definitive and aggressive generalization of "Go is garbage", OP closed the door for a proper discussion. The opposite of the goal of Hacker News.
Some medium are a fine place for this, mind you. I enjoy being trolled on IRC, for once, being a child of the eighties. Imgur makes for fun memes. 4chan is great to make fun of pretty much everything. And twitter is big enough that you can find an audience to bash any kind of programing language for therapeutic reason. I did that with JS for some years. Very relaxing.
HN has become a cesspool for certain topics in recent years. Go threads being the worst; all the ego's seem to come out of the woodwork. I almost never bother reading the comments on anything Go related these days because it's just full of wannabe 10x programmers overstating academic edge cases as if no other language in history has ever had a shortcoming.
Yep, they construct a strawman argument about some niche edge case where Go was outperformed by some language. Or come up with some contrived examples where Go would Absolutely Need Generics.
None of those people have actually written any Go professionally.
I agree that calling a very successful language "garbage" is a childish move. I also think that we shouldn't tie our egos to the tools we are using. Nothing good arises from that.
Sure, but most people do. And it's a social convention that we do. If you know the social convention and you still choose to break it, you are willingly being aggressive.
Now breaking social convention and being aggressive has its use. But it's only fair to be called out.
A language that young professionals can use right away and can even support complex projects like kubernetes, Prometheus, consul etc. sounds like an excellent choice for companies to adopt. It might not be as elitist as rust or clojure but if collaboration is important for your project I'd rather have simple without foot guns rather then amazing with dragons all over.
> It might not be as elitist as rust or clojure but if collaboration is important for your project I'd rather have simple
I'm not a rust fanboy, but I wonder why people have the assumption that programming should _not_ be elitist. Where did we get the idea that programming should not be a skilled trade, but rather something the everyman can do?
> without foot guns rather then amazing with dragons all over.
Ironically, Rust and Clojure have less "footguns" if we're talking about concurrency and parallelism.
It’s a good aspiration to eliminate needless complexity from a language so people can spend their cognitive resources (be they great or few) on solving real problems. If you can lower the barrier such that a layman can do it, then a genius specialist can do that much more.
The problems don't go away. If the language refuses to handle them and refuses extensibility, every end user now needs to handle those problems without standard tools.
This hasn’t been my experience. I’ve been involved in the language and community for nearly a decade and I’ve never seen an incident where the grievances against the language weren’t seriously overblown. The most credible one has been dependency management. There’s still plenty of room for improvement of course, but the idea that Go’s flaws make it anything short of one of the most productive languages is generally nonsense.
Also, go is an elitist language as well, in its own way. Its creators and many of the first proponets were big Unix/C/Bell Labs names. Quite opinionated people.
I think that has more to do with the fact the k8s devs had a complete implementation of the the same product (Google's Borg) as reference. Mesos/Marathon only had a couple 10 page journal articles to look at and a ton of on the ground decisions to make and quantify the choices of.
I’ve heard the statements that the Go stdlib and Kubernetes are not good examples of Go code, but I’ve never seen someone say that it’s not a good use case for Go. Would you mind elaborating on that assessment?
There's a real analogy to be made here with mass production and it's impact on labor. We quickly went from a society that valued individual craftsmanship to one that valued the ability to treat people like cogs at the factory. I'm not even mad, mass production is a great thing that enabled access to goods that many people would never have otherwise had. But eventually there was a backlash, and handcrafted, high quality goods are preferred by many who can afford them.
I'm not saying Go programmed products are lower quality, just that we've enabled certain types of organizations with the advent of greater process and rigor in languages. Someday the pendulum will likely swing again.
> I'm not saying Go programmed products are lower quality
I've met plenty of good developers that complain at how Go ties their hands and creativity. Unsurprisingly, the language has been designed for "cogs":
---
The key point here is our programmers are Googlers, they’re not researchers. They’re typically, fairly young, fresh out of school, probably learned Java, maybe learned C or C++, probably learned Python. They’re not capable of understanding a brilliant language but we want to use them to build good software. So, the language that we give them has to be easy for them to understand and easy to adopt. – Rob Pike
I also think that (unused variable) is an annoying feature. It's good that a compiler can warn about it but it should be optional. It can always be made a mandatory setting in a build pipeline or something.
It's not the computer wanting you to do this, it's the collective arrogance and pedantry of Google designers wanting you to do this. Therein lies the rub. When the compiler detects the unused variable, it doesn't warn you, nor does it remove the variable as it knows exactly how, but one-ups you to force your hand.
You see, even if this is the right choice, the way it's carried out is douchey, and so I perfectly understand somebody calling it garbage to express a sentiment.
You cannot remove an unused variable, you just dont refer to a stack place or register with it. Removing a variable would modify your source code. The compiler can only make you do it by pretending it refuses to understand it.
Why does it have to be the compiler? The thing about unused variables seems quite arbitrary to me. There are also other things the Go compiler doesn't enforce.
Why not pick a tool that's specialized for stuff like that, like a style checker. Put it into the build pipeline and let the build fail if the code doesn't adhere to your standards.
If you are worried about wasting time with not being able to compile due to unused variables, chances are you're doing something wrong like creating long functions.
I have been working with Go for five years, and the fact that I'm forced to use all variables I define means I write simpler code. The only instance that I find I waste time is when I want to access a variable that I otherwise don't need to quickly print it when debugging something. Still, this is usually a minor issue and it's a great tradeoff. I wish more languages enforced such rule.
The unused variable thing bothered me enough that I modified the complier to add the -warnunused flag. It's fine to nag for this, but not to block the dev cycle.
Looking closely, that line says “(not simple) is better than (not easy)”, or more clearly, “easy is better than simple”. Python definitely lives up to this - it’s easy to get started with, but if you look deeply it’s a very complex language.
Go’s philosophy is probably the opposite - that simple is better than easy. This is similar to the philosophy of Clojure, as explained by Rich Hickey in “Simple Made Easy” [0].
[0] https://www.infoq.com/presentations/Simple-Made-Easy/