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.