The Zen of Go
dave.cheney.net
dave.cheney.net
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].
The whole purpose, to me, of artfulness in code is to take unartful, hard to understand code and make it simple. What other objective is there?
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.
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.
You do not know the meaning of elegant. At least in context of programming and maths.
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.)
"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."
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...
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.
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/
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.
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.
*self-proclaimed intellectuals
To be fair, it also took me years to realize how programming as tought in academia is out of touch with reality.
>“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...
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.
I don’t, so please keep you assumptions to yourself and don’t put words to in my mouth.
> in correct proportion (no axiomatic beliefs).
What is this based on?
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.
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.
Since, given this, Go is no better than a language with unchecked expressions nobody handles...
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.
At least then errors as return values would be solid.
Of course now they make programmers do all the extra error wrapping thing in 1.14 to pass "richer" errors...
Having the compiler force you, as is my suggestion, would make you think even more -- or not be able not to think and skip the error check or miss it.
Go doesn't allow values to just be referenced without having some use, e.g., JavaScript's `"use strict";` hack could not be done.
In general, I have never seen a bug caused by accidentally ignoring an error. It's a theoretical concern, but not a real world problem.
Regardless, the fundamental point stands. Using tuples to return “meaningless” values alongside errors allows developers to mistakenly use those meaningless values.
It should perhaps be an error to not assign an error to a variable. Internally, Google has linters that enforce this.
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.
Theoretical problem: Someone might mutate a variable intend to be constant.
Go designers: Then put a comment saying not to do that.
Real problem: People ignore compiler warnings.
Go designers: Then eliminate warnings.
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.
Etc. etc.
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.
> 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.
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.
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.
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.
Just a gotcha i want to mention to anyone reading. ApacheBench is a good first start.
For instance, try and read a map, it will crash your code.
It won't show this as a problem until you have real traffic on your service.. Hitting it in your browser isn't enough to test properly.
Slightly ambiguous wording, so just to clarify:
Go maps are safe to read concurrently
Go maps are not safe to read AND write concurrently
Go maps are not safe to write concurrently
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.
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.
wrk, h2load, boom, vegeta are more sound load testing tools.
Whats with the nitpicking on hacker news?
"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.
To be fair, I don't think anything I wrote contradicts that. If your goals align with Google's, great.
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?
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
I agree that many languages have interesting features.
Your point however is not at all the argument the GP was making, which was that Go suits only Google, junior developers, and huge teams.
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.
> The go community has been incredibly welcoming.
Jeez, what a disconnect.
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.
- 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.
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.
TBH I thought adding enums was a cut and dried case until reading through some opposing opinions. Now I'm not so sure.
This is not a selling point for the language.
Your conclusion is nothing more than a hilarious ad hominem attack on the language.
Okay, that's probably not the real reason, but I'm not joking that this would be the easier way :).
if err != nil {
return err
}
> 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.
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.
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.
Exceptions are certainly more robust, but purposely ignoring errors can be done with exceptions too.
try {
// do stuff
} except( Exception e) {
// ignore
}
and yes, I've seen it often.I will agree that it's not possible to accidentally ignore an exception, since it will likely crash your application.
result, err := computeVector(input)
if err != nil {
return nil, fmt.Errorf("error computing vector: %w", err)
}
But that's a bit unwieldy to write in an article or talk.Error handling would look something like this:
for err := callService(); err != nil {
logger.Info("Retrying...")
}
Or value, err := readValueFromFile("abc.conf")
if err != nil {
value = getDefault()
}
etc.This statement is categorically false, and I would encourage you to search GitHub for `return err` under the Go language.
There are 200k+ code results, and a quick survey of the first few pages shows plain ol' `return err` to be _quite_ common.
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.
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.
Code that handles an error looks like this:
if err! = nil {
if fileErr,ok := err.(MissingFileError); ok {
createFile(fileErr.FileName())
} else {
return fmt.Errorf(...)
}
}
While code that does NOT handle an error looks like this: if err!=nil {
return fmt.Errorf(...)
}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.
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'm no longer a fan of Go but still a fan of many of the people the work with it because they also value many of these things.
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.
[1] https://github.com/kstenerud/go
What do you prefer for writing business logic type apps then?
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.
Can you expand on your objection to the new Go module design?
Have you tried the "cgo" facility and found it lacking? If so, how?
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.
This is certainly not an accurate description of Java…
What does golang have to facilitate large scale collaboration that isn't done better in languages such as Java or C#?
Code formatters are available in Java/C# but not ones that have a default set in stone.
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.
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.
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.
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.
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.
- Auto-formatting
- Good first-party testing and benchmarking
- Ppprof runs circles around VisualVM
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.
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).
1) Extremely fast build times (even for large codebases).
2) The package system plugs directly into Git/Github.
The second reduces friction for distributed/large teams to share packages with a lower administrative burden.
I can't compare to modern Java/C# because I haven't used either for quite some time.
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 compile times are pretty inarguably faster than these in practice, yes.
Compilation time between them is a magnitude apart it's not even arguable.
Parent said "Git/Github". You can use private git repo, you can use private github repo.
[0] https://arslan.io/2019/08/02/why-you-should-use-a-go-module-...
Pascal and Modula-2 compilers were already doing that in MS-DOS days.
As several other AOT toolchains.
It requires our source control system to be Go-mod-compatible.
It requires commits/tags in source control to publish internal builds.
Its complete reliance on semver completely breaks when you want temporary branches for internal versions.
It makes it very difficult to have multiple Go modules in the same repository.
Overall, it's a horrible system.
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.
> 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#.
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.
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.
Lightyears ahead of Go.
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
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."
I can't parse it.
Bonus points if they are using Docker / Kubernetes for deployment and hosting.
More about him: https://www.newyorker.com/magazine/2018/12/10/the-friendship...
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
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.
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.
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.
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.
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.
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.
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.
> polyglot, blub, functional paradigms
Damn, one more I would have had bingo.
> Go is actively hostile to functional programming
Well, it does have genuine first class functions, lambdas and closures.
> you add a try/catch/throw, and you don't even need to look at the exception! 99% of programs need only one exception handler
pretty sure this is the exact reason Go does not have exceptions
> It's a better C. But as a language to take over the world: Not a chance.
Oh, you sweet summer child.
> *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!
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. :)
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.
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.
I agree with many criticisms of the language but sometimes that criticism seems to enter its own sort of echo chamber.
I agree with what you're saying.
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.
I am very aware about try with resources. It works however only if the lifetime of objects are tied to a scope.
* 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.
You say that like it's a bad thing!
A bloo bloo bloo!
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.
One of them is Ken Thompson, Turing Award winner.