That said, I wanted to talk about Go for enterprise development, and I did explicitly made the point that some abstractions are in my opinion detrimental to the reality of enterprise software, and I suspect generics might be one of them. It's a big topic and I honestly don't know the definitive answer, but I just wanted to point that out. I do agree that non-nullable types are good and a big hole in Go's repertoire.
It seems to me like the final section has a lot of bad things to say about the methodlogy of a go developer. Huge, bloated toolchains required to make progress. Code volume rather than appropriate abstraction. To me, your MS Word vs LaTeX graph sure seems like it's damning "easy" languages like Golang in favor of other languages with more powerful abstractions.
You also assert that Golang is easier to learn than other languages. I don't think you've really got a leg to stand on there. It's "easy" for folks doing service development because the arbitrary decisions made by Golang were made from the perspective of someone experienced at writing web services. If you already know C and Python a bit, Golang cherrypicks a lot of good stuff, but if you don't (or you're not writing a bunch of web services that essentially do nothing but punt to C frameworks and validate strings in request headers) then Go's going to struggle, and folks have pointed this out.
It's pretty surprising to me that you write about how Generics are the death of enterprise software when so much quite-usable software uses generics. Why do yo simply get to forget the existence of the huge body of Java work and instead assert it's bad? Android uses generics in many cases where appropriate and it's one of the better UI kids available these days.
Even the Actor methodology you're citing as part of Golang as good is actually a fairly sophisticated abstraction with lots of implications for the runtime and execution order. Why is that specific programming abstraction given a pass because of its benefits, but writing a generic linked list is going to be the death of your programming organization?
You also defend the structuring of many enterprise groups even as you suggest that they lack training, refuse to pay technical debt, and place unreasonable burdens on developers. You seem to have just accepted this and said Golang helps you be more complicit in this mode of operation that you also seem to suggest is somewhat bad. Why would we want to pander to a methodology that asks junior developers to proceed without training and places focus on process and hyper-specialized domain experts rather than clear communication, sound architecture and sustainable velocity?
I'm quite confused how you can hold both these opinions at the same time. It seems like they're contradictory.
Go is easier to learn than C# (I'll omit Java in this comment as I have more experience with the former), but that doesn't make Go an "easy language". The name itself implies that mastery doesn't come immediately.
The powerful abstractions that you mention, including generics, are indeed good things, but pardon me but I suspect you've never seen how badly and easily they can be abused in certain environments. You've probably seen the Factory<Factory<NaturalNumbersFacade>> joke somewhere, real live enterprise software is sometime like this, but unironically.
> Generics are the death of enterprise software
That's an hyperbole, I've never made such strong statement.
>Even the Actor methodology you're citing as part of Golang as good is actually a fairly sophisticated abstraction with lots of implications for the runtime and execution order. Why is that specific programming abstraction given a pass because of its benefits, but writing a generic linked list is going to be the death of your programming organization?
Goroutines and channels are simpler than the threaded multiplexing that C# does with its stackless coroutines. My argument is mainly related to the many ways you can mess up async/await in C# vs the 2-3 ways you can deadlock in Go. I link to a talk, an image and a blog post related to the subject.
> You also defend the structuring of many enterprise groups even as you suggest that they lack training, refuse to pay technical debt, and place unreasonable burdens on developers.
I don't defend nor encourage this, but that's what happens in real life. It's the result of many factors at play, some of which I described in the post, some of which even I don't understand. If I did, I would be way more rich :)
You can refute my assessment of reality, but "being complicit" is not exactly appropriate when 80% of the enterprise consultancy jobs out there are like this.
> Why would we want to pander to a methodology that asks junior developers to proceed without training and places focus on process and hyper-specialized domain experts rather than clear communication, sound architecture and sustainable velocity?
You can't change how things work until you understand why the work the way they do (or at least you can't reliably change the world unless you do). Refusing the current state of things is step 0 of N, and I've learned in my experience that change in big systems comes incrementally, you can't "distrupt all the things", so Go is (in my opinion) a step in the right direction.
I would recommend you talk with somebody you know that has this type of work experience, they will be able to convey to you how these things work better than I can, probably.
Isn't that more of a criticism of enterprise software than generics? If they can write ugly generic code, they sure as hell can write ugly Go code as well. They're just different different programming styles, and while some like one others prefer the other. Even when you code in ASM, you'll probably end up writing generic code down the line. You'll just be managing it manually rather than have a compiler, preprocessor, or templating engine do it for you. It's a preference, and I say to each their own. Also, the whole moralizing holier-than-thou simplicity and anti-abstraction talk is getting kind of annoying, but then again, there's lots of moralizing people in the Rust camp who are just as annoying.
Absolutely yes! My whole argument is restricted to (how I experienced) enterprise development.
People complain that Rustaceans are insistent to the point of being obnoxious about Rust. But I insist that Gophers turn post-hoc rationalization into an art form. Things are bad because Go didn't do them, if they were good Go would have.
I'm sorry, this is a terrible argument. That's definitely not a common usage of Generics.
The common enterprise OOP idiom that is mocked is NaturalNumbersFacadeFactoryFactory, which use no Generics at all, leading to a large number of classes.
Ironically, if one used generics to replace factories like you did in your example, you would be able to replace hundreds or thousands of Factory or FactoryFactory classes in an enterprise application with a single Factory<T> class. Of course, that would probably be pointless. There's a reason multiple factories exist in a program, even though there are ways to replace them with something simpler.
I get it that Go programmers don't want the language to be complex, but Generics themselves don't have to be complex. They solve a lot of problems in a simple way and are MUCH easier to use than ad-hoc generics made using interface{} + Reflection.
new TypeReference<FooResponseCollectionResource<MemberUpdate>>(){})
I like generics, but I don't love this sort of thing and it's not even a mis-use of them using NiceType = TypeReference<FooResponseCollectionResource<MemberUpdate>>
// ....
new NiceType ()Of course you can mix generics with terribly named classes and OOP patterns, but that's hardly a problem with generics themselves.
but then you loose type checking and some people don't like to loose type checking...
The problem in those cases always the abuse of often obsolete OOP patterns and misnamed classes.
It is very common indeed in the three large Enterprise Java houses I've worked in over the past 15 years. FizzBuzzEnterpriseEdition is the reality in all three of those places. Not only is it reality, it is requirement.
You're just making my point for me.
I'm using "easy" in the sense of "easy" vs "simple". I think that the modern "commonly in-use" parts of C# are about the same size as Golang, to my sense of scale.
> , but pardon me but I suspect you've never seen how badly and easily they can be abused in certain environments. You've probably seen the Factory<Factory<NaturalNumbersFacade>> joke somewhere, real live enterprise software is sometime like this, but unironically.
Pardon me if I think it's profoundly disingenuous for you to conflate factory patterns (which can exist in literally any type system and language) with Generics, and let me again positively beg for pardoning if I come across thinking you don't really understand the Golang argument for generics if this is your go-to example.
> That's an hyperbole, I've never made such strong statement.
Perhaps, but you have lumped generics in with a group of features and said, "The Go community regards as anti-patterns many abstractions regularly employed by Java / C#" and essentially intimdated that Generics are almost never good.
Further, your rhetoric carefully partitions everyone else's abstractions as risky anti-patterns while below you carefully rationalize Go's abstractions as in fact good. Your argument there essentially boils down to taste and fear.
> Goroutines and channels are simpler than the threaded multiplexing that C# does with its stackless coroutines. My argument is mainly related to the many ways you can mess up async/await in C# vs the 2-3 ways you can deadlock in Go. I link to a talk, an image and a blog post related to the subject.
No, they're not universally so. Firstly, actors and threads define equivalent systems [0], they simply have different tradeoffs within that space. There are some constructs where actors are easier (e.g., when a process maps well to an individual loop consuming a mailbox) and some where they simply are not (e.g., when spinning over shared memory and hoping to pull out a copy to another space). What's more, there's an awful lot of progress on the shared space model by attacking the memory coherency problem.
It's quite possible to build systems that are as resistant to deadlock as Golang using threaded models. They're also amenable to static analysis. There's also a very large and useful body of research on using structures that are unopinionated about the order that they receive updates in (CRDTs are a good place to start here), making the strict linearization of actors unnecessary and even a performance bottleneck sometimes.
You're either unaware of it, or you're uninterested. I don't know, but if it is the former then you should probably keep up with what's going on there.
> I don't defend nor encourage this, but that's what happens in real life. It's the result of many factors at play, some of which I described in the post, some of which even I don't understand. If I did, I would be way more rich :)
You are encouraging it though. You're saying we should use tools that are designed to accommodate it. That's literally baking this mode of operation into our automation at a fundamental level. And as you've implied, once there it often takes monumental effort to get it dislodged.
> You can't change how things work until you understand why the work the way they do
That's my line. The way it works is people suggesting that there is no other way it could work, and then baking these assumptions deeply into their corporate structure.
> I would recommend you talk with somebody you know that has this type of work experience, they will be able to convey to you how these things work better than I can, probably.
Hi. I'm Dave. I'm a SRM at Google right now but I've also been a Director for Capital One, worked in software at numerous companies including Microsoft and about a dozen startups in technical and advisory capacities, and founded (and sold) my own startup. I've been in management in one capacity or another for nearly a decade.
And a lot of my time spent when I'm not working directly on projects is advocating for developers to be more empowered, receive more training, and have the power to actually set and run a sustainable pace, even if that means a slow start to burn away the technical debt in place.
But thanks, I'll keep that in mind.
[0]: "On the Duality of Operating System Structures", by Lauer & Needham http://web.cecs.pdx.edu/~walpole/class/cs533/papers/duality7...
I suspect the larger factor may simply be familiarity. It is very rare to see a balanced piece about some programming language - let alone one about two programming languages - where the author has an equal and substantial amount of experience with both and is really able to compare them on their merits.
Async and await in C# may be wonderful to some, but they are horribly broken to others.
I disagree they're broken.
C# 7.1 introduced async main. No reason to call Task.Result in console programs.
Even before that feature arrived, visual studio debugger could resolve that literally in a minute. Press F5, reproduce deadlock, you'll see exactly what's locked and why.
We could say it's good for Web and Services Enterprises (probably) but for a different kind of an enterprise it can vary, depending on their business model and requirements. As the significance of it's features can change.
Tldr; All languages have a place(IMHO), Go is a simpler language with reasonable value in the current industry for various reasons.
Too true, I touch on that in a post I made here as well. In Go I often found myself having to reuse logic in functions that, in Rust, could be hidden behind iterators or custom iterator implementations.
While the Rust version is definitely more complex if you look at the implementation of the iterator; in practice you are just using the iterator - and that ends up meaning the problem you're solving keeps locality, and is easier to reason about. In my experience, at least.
The Rust iterator is a pretty simple abstraction in my opinion. It's essentially a lazy list. You see an iterator used, you know what you're getting. You see the map function used, you know it's the same as a for loop over all the elements. A fold/reduce is just a for loop over all elements with a result variable that gets returned. I really don't think using iterators is any more complex than a for loop, and with a for loop, you actually have to look through it to identify the pattern used and find out what it does. I'd say having to manually check for patterns is more work, but I couldn't say which one is more complex; maybe neither is.
I do it in my libraries when warranted.
The pattern is:
v := NewIteratedValue()
for v.Next() {
item := v.Item
// process item
}
if v.Err() != nil {
// process error
}
A variation of this pattern exists in standard library and third part libraries.I know that you meant implementing some sort of iteration protocol natively supported by the language, so that you can write:
for v { ... }
vs: for v.Next() { }
But you ended up making much stronger and untrue claim that one can't write re-usable iteration logic in Go.Your iterator has to be specific to a set of types — "v.Item" in your example is always of some specific type. Using an untyped interface{} would be worse than any alternative because every single use would have to use type switches and casting; you lose out of compile-type type safety.
Because of this, even if you have a local iteration system, it isn't composable beyond your package and its types. Given this (simpler) interface:
type Iterator interface {
Next() (Foo, error)
}
...then I can write a map function: func Map(it Iterator, mapper func(Foo) Foo)) Iterator
...but I cannot write a generic one that works with anything other than Foo. And if I need this: func MapFooToBar(it Iterator, mapper func(Foo) Bar)) Iterator
...then I have to write it just for that purpose.That's why there's no "iterator library" for Go, like there is with Rust.
Not at all. Are you telling me that `v.Next()` can be written in a reusable way for different data structures? Nonsense.
`v.Next()` will have a single, bound return type. So at that point you're re-implementing iterators for anything you want to iterate on.
Furthermore, lets say I want to `v.Next().Map()`. How can I write a Map function in a reusable way? The entirety of your iterator chain would have to be custom implemented for the one data structure you're using. Or you throw your entire type information away and use `interface{}`.
With Go, your limit of reusability is hand writing the entire iterator implementation per type. Which is hardly reusable in my view. I touched on this when I mentioned creating separate functions for these patterns. That's all you're doing here - creating custom methods to make something look like, but not behave like an actual iterator. No Map support, no Filter, no .. anything. All you made was a loop, and a hand written one.
I'm not trying to be overly negative here. However saying Go has an iterator pattern is imo strongly misleading.
Moving 10,000 bricks from point A to B is likely simple or easy, but it takes time. If not careful a client can think “oh it’s just moving 10K bricks 5 feet over, they should automagically be able to do this since John said it was “simple” ”. What I intended to impart was that I know how to do it, no solutioning needed, we have the skills and resources to get it done, and we can probably get moving on this quickly. Now I say something more like … “Moving 10,000 bricks from A to B is straightforward for my team, when do you want us to get started”.
And easy as close by and accessible i.e. npm i latest-framework might be easy but not simple
edit: The "Limits" slide (go to 12:30 in the vid) is one that I really internalized early on. And looking at it again years later, the principles from that slide absolutely guide my app development:
- We can only hope to make reliable those things we can understand
- We can only consider a few things at a time
- Intertwined things must be considered together
- Complexity undermines understanding
Go was designed at Google to be used internally at Google, so frankly it's hard to imagine commonly pushing past the "scale" Go was designed for.
That's not true, expect in a pedantic way. Google was "designed at Google to be used internally at Google" only in that:
(a) a few people at Google, on their own, designed a language (mostly based on an older Plan 9 language some had helped built) - not at the request of Google execs, nor as an explicit company-mandated project to create a language to solve Google's problems. It was almost on of these "20% spare time" things.
(b) these people added the features that they thought, as far as they were concerned that would be nice for programming Google style stuff. Those were based on their ad-hoc (and quite idiosyncratic) intuition and personal experience, and not some special research into programming at scale, or from involving the company at large at it.
Go was designed at Google, but not "from Google", if this makes sense. E.g. a few googler's building a language on their own initiative and among other work is not the same as Google, i.e. some higher-ups, saying "we need a language of our own that's a match for our developers' challenges" and the company devoting resources for this.
Google has 2 language projects they really put money on, Javascript as V8 and Dart. They built a top notch specialist team, spent lots of money for promotion and branding, built an IDE and developer tools for both, etc.
Apple has had Obj-C and now Swift like that, MS has C#, etc. Those are language projects with a big weight of the companies behind them.
Golang was not like that, but, according to all official accounts and recollections, a grassroots project, by a small team:
"Robert Griesemer, Rob Pike and Ken Thompson started sketching the goals for a new language on the white board on September 21, 2007. Within a few days the goals had settled into a plan to do something and a fair idea of what it would be. Design continued part-time in parallel with unrelated work. By January 2008, Ken had started work on a compiler with which to explore ideas; it generated C code as its output. By mid-year the language had become a full-time project and had settled enough to attempt a production compiler. In May 2008, Ian Taylor independently started on a GCC front end for Go using the draft specification. Russ Cox joined in late 2008 and helped move the language and libraries from prototype to reality."
It was never officially intended to be "Google's development language" or had a major push from Google. Since then, and after the first few years, Google seems to have devoted more money and time to Go, and several Google internal projects have adopted it, but Google is fine with C++, Java, and Python as well.
In particular, language designers typically have concrete problems in mind, even if they're inventing a general-purpose language. It's enough to say that Go's designers expected to use their new language at Google, so it needed to work well in Google's environment and solve some problems that weren't currently being solved well in that environment. If it didn't work at Google, then they wouldn't have succeeded at their original goal.
While it's not the most popular language at Google, Go is officially supported for server-side projects (communicating via RPC with many other internal servers), and has been for some time. That's a high bar that few languages meet, and about as official as it gets. If it didn't succeed internally then the Go team probably wouldn't have had consistent management support and stable funding over many years.
The other languages you mention (Dart and JavaScript) are considered client-side only within Google so they mostly don't compete with Go. For example, Node is supported for developer tools and external users, not because Google runs its own servers using Node. (Or at least that was true when I left.)
I've been working at Google for 8 years and have yet to touch a Go codebase in production.
It's just not used that frequently.
But I'm not going to dispute that it's the most-used server-side language, because I've seen the statistics.
Yes.
But it's another thing to have a company's the management, say "we want a team to create a language to solve programming at our scale" and throw full resources and money at it (at C#/Java/V8/Swift scale) and have it adopted by mandate for further development,
(as is often implied),
and another thing to have an independent group of a few devs at a company to sit on their own, and say "you know what would be interesting to try to build? A language to handle what we see as Google scale problems", and then getting some more resources, and seeing some at the company adopting it, as just one more language used along with several other company-approved languages for greenfield stuff...
If anything the trend is towards slowly adding acceptable languages, but the bar is pretty high.
I know Go and Haskell equally well, and I get just as much shit done in Haskell and don't have to be a copy-paste machine sometimes. There are many benefits of Haskell and like-minded languages besides being "beautiful."
Feels like if the Go designers had the sense to include parametric polymorphism and sums (aka proven features from the 70's whose main "downside" is being weird to Go's target audience), it would be a great middleground.
Missing polymorphism is a valid complaint but it hasn’t had the impact I thought it would.
Meanwhile, some of the programs I had written in Haskell I’ve rewritten in Go, e.g. due to problems with the use of finalizers (causing crashes!) in Haskell. You can chalk this up to poor library design but it simply isn’t a problem in Golang, which seems to avoid the finalizers more than other languages.
I’m going to continue using both Go and Haskell, I’m happy with both.
I've never run into finalizer issues (or finalizers at all really) in Haskell but I'm sure they exist. What libraries in particular used finalizers that you had issues with? In general though I find resource management much easier in Haskell thanks to bracket, ResourceT, and the (imo very good!) exception handling stuff. Async exceptions in particular are a surprisingly nice feature when combined with threading!
The biggest place lack of parametric polymorphism hurts is in concurrency. I have to hand-roll so much concurrency in Go and I don't really have a good option for abstracting over various patterns.
> I'm going to continue using both Go and Haskell, I'm happy with both.
Yeah same here. I'm glad to have them both in my toolkit.
None were academia & most were commercial enterprises (that most people have heard of)
It's not scale about number of users or data, it's complexity scale.
Last enterprise app i worked on had (provided) something like 10.000 different api calls (and yes mostly because of bad/wrong initial design decisions), and over 50k different possible queries that it could run against database. (my job was to "make database go faster")
Just running unit tests took longer than 6hrs (of course most unit test went out and connected to the database, because why not \s).
It all could be replaced with dozen or so 10k to 30k lines go projects each, that would be a lot easier to maintain and to scale out.
For me, scale means more micro-services. For another person it means a bigger monolith.
This is not some advanced feature. I would think this should be the default in any language.
My point isn't that this is wrong, but just to suggest that people's mental models incorporate the idea that programming languages actually move pretty slowly. There's a constant froth of undergrowth in the forest, but it takes a long time to establish a tree, and a long time for it to be supplanted by another.
and I would argue there's idea that simplicity of the instrument/tool is not a problem in itself and we shouldn't lose it in pursuit of 'ease'
One thing that gophers say a lot is the count of Go's keywords is minimal. This is true literally, only 25 keywords as of Go 1.13. But it does not reduce the complexity of writing code, it just hides them.
For example, Go does not have the `new` keyword, but it has `make` function, which is so powerful that one of my friends actually thought `make` was a keyword of Go.
Enumeration types in rust can be the usual ones you find in other languages:
enum UserStatus {
Anonymous,
Registered,
Confirmed,
}
But their variants are not limited to names, they can be other things[1]: enum UserStatus {
Anonymous,
Registered{ email: Email, date: Date }, // a struct
Confirmed(date), // a tuple
}
Enum types can also be made generic. For example the Date type could be a parameter in the example above: enum UserStatus<T> {
Anonymous,
Registered{ email: Email, date: T }, // a struct
Confirmed(T), // a tuple
}
Which brings us to the Option type[2] in rust. It is an Enum with a generic type attribute, defined like this: enum Option<T>
{
Some(T), // a tuple with a single item of type T
None,
}
In rust, when a function says it returns an `i32`, it will always return an `i32`. It never returns `i32 or null`.If you want not return an `i32` sometimes, you must specify exactly what else it can be. It is often convenient to return an Option<i32> instead. Then it can return `Some(i32)` or `None`. The difference with the "implicit null" from before is that the compiler will force you to "deconstruct the option safely", in compile time. You will never get a "Null Pointer Exception" in run time because of this.
For me, this is huge, and one of the reasons I like Rust more than Go.
[1]: https://doc.rust-lang.org/rust-by-example/custom_types/enum.... [2]: https://doc.rust-lang.org/std/option/
You can can implement this generically in Go using interface{} types and runtime type checking, but then you have runtime type checking failures.
A java/c++-esque "generics" implementation would be able to type-check at compile time.
Go has the most useful containers built in. Most code I write doesn't need custom containers, even in languages with templates (I write far more C++ and Java than Go) -- so I find that I don't miss them much when I spend time in Gopherspace.
I suppose it's not sorted or concurrent-safe?
`map[T]struct{}` is only a type, it's not an implementation. You still need a handful of methods, that might be named inconsistently in each implementation by different developers. I will be happy when I never have to think about this again
Set storage is not really the concern here, it's the set-theoretic operations which make sets useful (generally speaking, there are cases where all you need is the set being a set e.g. deduplication).
So I wouldn't be surprised that GP found several different implementations of union, intersection, difference, symmetric difference, subset, superset, …
That means for every conceivable input type and output type combination you have to have a new MAP or REDUCE function to handle it.
All Modern languages except golang and elm only require one implementation of MAP that can handle all type combinations.
It's called parametric polymorphism.
I seem to remember an article being written a while ago that found Java's type system, with the addition of generics, was unsound. Maybe that's what you're trying to say.
You can do exactly the same thing in Golang. Just use interface{} everywhere and add few casts where needed. That is Java generics.
That's not true, Java generics are not just syntactic sugar.
> They don't add anything substantial.
They add parametric polymorphism and type safety.
> You can use Object (or another bound type) everywhere you're using generic type.
Only if you go out of your way to do so, and ignore warnings. This has less to do with how generics work in Java and more to do with the fact that Object can be explicitly cast to another type and vice versa.
> You can do exactly the same thing in Golang. Just use interface{} everywhere and add few casts where needed.
No, it's completely different.
One example is to look at how Go handles the Image package vs other languages with generics. https://golang.org/pkg/image/
Traits is a way to solve it. But the problem is that in Go types are "too open". If you wanna constrain it more traits is not an elegant solution.
Generics is better in this case.
BTW: This is how is in Rust. Rust have traits but you fill the rest with generics.
I made a mini-go database command liner that I call from rust (so I get access to drivers that are not yet available in rust). Is much more boilerplate from some stuff that in the rust side is just Value<T>.
And type errors that Go have that rust don't.
I think that Rust & Go sharing some(most?) design principles (about how model types) make easier to see what each side make harder...
The difference is in speed of producing such code, amount of bugs introduced, ability to understand, modify, and evolve the code, etc.
What would you call a language that forces you to write / use a preprocessor to introduce higher-level features?
Low-level. More specifically, low-level for the domain where you're working. C is admittedly low-level because it had to be minimal even for 1970 and close to the metal. Other languages usually enjoy less drastic design constraints.
I let you research when it was released.
Then I ran unit tests, and sometimes a weird error cropped up. Turns out I made an error in an append: instead of concatenating two arrays, I added the second array as the last element of the first. That was a time consuming bummer.
This problem could have been prevented by two approaches: copying every function for every type, or generics.
Go is a nice language, which makes some tasks easy to implement, almost fool proof, but it still lacks in other areas.
Go makes you implement max and min in all your codebases.
In Go, I believe you need to give up type safety to write these implementations, by using Interface{}
Generally it makes more sense to describe an interface that can be satisfied by different types, and writing logic to handle that interface instead. Go may not have traditional generics but the interface pattern is certainly a kind of generic programming that can be used to achieve some powerful results. Because structs can satisfy many interfaces and interfaces can be satisfied with any kind of struct (if you want an in language example check out the io.Writer interface and the many functions that use it) you can end up with highly reusable code despite not having traditional generics.
You can get away without them in many cases, but if you are working on stuff that you want to reuse, generics are the way to go. In combination with Rust’s traits this is especially cool.
I wonder if that is really true, some really complex systems are written in Go and it does seem to scale well with that. Take Kubernetes for example.
Like Google scale?
That's what Go is designed for.
Google's major Go projects appear to be gVisor, Android GPGPU debugger, Fucshia's TCP/IP stack and volume management tools and the download server as it was done by the Go team.
https://commandcenter.blogspot.com/2012/06/less-is-exponenti...
"In this talk we explore the devastating effects using object oriented antipatterns in go while building programs in a monorepo. ... Unknown to most, Kubernetes was originally written in Java. If you have ever looked at the source code, or vendored a library you probably have already noticed a fair amount of factory patterns and singletons littered throughout the code base. "
So basically writing Java in Go lead to clusterfuck codebase.
Most of the codebase problems in Kube are:
1. we depended on half the Go ecosystem at one point (docker, grpc, etcd, a few others) which is hard to do with dependencies in go (few standardized libraries)
2. performance of serialization mattered and JSON and protobuf were still raw at the time
I don't think Kubernetes is any worse than any other large (3M+ LOC), relatively young codebase I've seen on average.
They are not scalable for Google scale and are not maintainable at google scale.
That's why Google C++ guidelines basically disallow everything that is problematic in C++ and enforce a style guide to
> Avoid surprising or dangerous constructs. C++ has features that are more surprising or dangerous than one might think at a glance
> Avoid constructs that our average C++ programmer would find tricky or hard to maintain. C++ has features that may not be generally appropriate because of the complexity they introduce to the code
> Be mindful of our scale. With a codebase of 100+ million lines and thousands of engineers, some mistakes and simplifications for one engineer can become costly for many.
I was replying to
> However, as you start pushing beyond "scale" Go is designed for
Go is designed for Google scale, I don't know anybody pushing beyond Google scale right now.
Anyway, I'm a nice person, you'll find your answer below
Enjoy
----------------
most of those projects are legacy software
did you expect search to be written in a language invented 10 years in the future?
Go is slowly replacing C++ to write the tooling.
C++ is simply not scalable enough for day by day use, Linus knew that 15 years ago.
Even Firefox is replacing it for its engine, it must mean something.
It can be done in C++ doesn't mean it should anymore.
Java is there because it runs in ads systems, you don't replace your core product overnight, just like in some banks you still find Cobol.
Java is the new Cobol.
Fuchsia is written mainly in C and Dart.
You wanna write an Haiku style microkernel?
C++ is fine, it's just a few thousands line of code.
Wanna write millions lines of code in a maintainable way at Google scale?
You don't start the project in C++ today, unless you're a crazy person.
Even ES5 Javascript is more maintainable than C++.
Certain things in Go are not easy (not simple), because Go has few language features (is simple).
Go has definitely a lower barrier to entry and is very productive for certain size and complexity of a project. However, as you start pushing beyond "scale" Go is designed for, Go becomes less simple to use. To appreciate things like generics, non-nullable types, no shared mutable state, you have to be working on a problem that requires these things, and only then these extra features pay off and make development and maintenance easier.