Go Is a Well-Designed Language
mattjhall.co.uk
mattjhall.co.uk
And pointing out missing features in a programming language is just about the weakest criticism possible.
Piling on lots of features is easy and fun. That's why almost all language designers do it. Most popular languages destroy themselves with features.
Rust is in the process of gaining every single feature anyone can imagine. Following C++ right off the complexity cliff.
Go is one of the very few languages to show incredible restraint in adding features because its designers understood the combinatorial complexity problem, among other things.
Not having unnecessary features is one of the best features of Go.
The results are in for Go. It's already one of the most successful programming languages in history any way you measure it.
Of course Go should continue to improve and add features where the value outweighs the increased complexity. That's how it eventually got generic functions and other features.
In a business environment, this is a obvious win.
And if I had to guess, it doesn't have enums so that it can remain flexible when serializing/deserializing enum-like types over the wire. Imagine you can't parse an incoming payload containing an enum field because your service is one version older than the one that extended the enum type (or the enum type is defined in a package dep... you get the idea). Enums are actually a terrible idea now that I think about it.
To me, golang symbolizes the shift of philosophy of Google as a company. It changed from "it's a smart nerd company for smart nerd people" to "golang will allow us to hire cheaper devs because golang will prevent them from making mistakes". I mean, this makes sense from business perspective, I won't deny this fact, but it's the programming equivalent of Ferrari making a SUV: tremendously profitable, but sad to see.
BTW
> golang doesn't support overloading because overloading is bad. Having said that, it's 'go', not 'golang', like the verb 'go', which already has a thousand meanings depending on the context
I find that hilarious
There are some "design-by-committee" weirdnesses, but if you squint a bit it's perfect.
I know that many people love Go, and I respect that, but I was never able to grasp its appeal (despite my two-year stint as a professional Go programmer). To me, the philosophy of Go seems to prioritize simplicity in the language at the cost of making software written in it more complex. Writing in Go often felt like a constant exercise in haveing to reinvent even the simplest things.
I worked in Java / C# with tons of interfaces all over the place, getter / setter in every files.
One of the tools I work on in my spare time is a Poker calculator that uses enums extensively for things like suits and hand strengths (flush, full house, etc.). This program doesn't connect to the internet in any way. But I hadn't considered that someone might come along and force me to serialize these poker hands and send them over the wire and change the number of suits and ranks in a deck of cards so that it creates incompatibilities. I guess I should go back and rewrite my code to remove those enums, since that's going to be a problem.
So you'd define `type Suit int` and have constants of type Suit that are `const DIAMONDS Suit = 0` ... `const SPADES Suit = 3`.
Go just doesn't have first-class enums with cardinality less than an int, like Java would. Which means you could have a Suit type variable with a value of 4 or 9000, because Suit serializes/deserializes as an int, not some custom string representation as in other languages.
The constant gotchas in golang indicate that it does not fit in people's heads within a few months.
import "fmt"
// Define an enumeration for status type Status int
const ( Pending Status = iota InProgress Completed Failed )
func (s Status) String() string { return [...]string{"Pending", "InProgress", "Completed", "Failed"}[s] }
func main() { var s Status = InProgress fmt.Println(s) // Output: InProgress }
In your suggested method it doesn’t even catch that case and provide an error if the value isn’t in the range.
I think parent should be using `stringer` [2] instead.
I didn't say anything along those lines, in fact I was commenting generally about this issue that you guys and the other commenters mentioned.
I was just suggesting a solution to the the problem stated.
const ( Pending Status = iota InProgress Completed Cancelled Failed )
func (s Status) String() string { return [...]string{"Pending", "InProgress", "Completed", "Failed"}[s] }
func main() { var s Status = Cancelled fmt.Println(s) // Output: ??
If the compiler - not some linter - protests that the list of names is different from the number of items in the enum, then I think this is at least a half-decent design of an enum type. Not a great one (because the author still had to repeat the names), but at least something that isn't a fundamentally broken design of an enum type.
But if the compiler is silent, and the output of the Println(Cancelled) is "Failed" then I'm not angry, I'm disappointed.
Only one step better than Assembly or Fortran.
I think this is a basic tradeoff with defer and not a problem per se. Are users more likely to need to clean up resources every time through a loop, or more likely to acquire resources that outlive the loop? And which is more confusing if you expected the other? I think it's a tough call. (I understand why a systems programming language would insist on block scoping to avoid allocating.)
There's a good explanation somewhere, ah here it is: https://go.dev/blog/maps
But you get used to it, and move on, especially with the sort of TDD workflow that go test encourages
You can write a func Set[k, v](map map[k]v, k k, v v) map[k]v yourself that returns a map reference if you want this style of “maybe return a new pointer”.
Could you please elaborate?
I can't really evaluate/judge them without understanding that first.
Furthermore, it’s fine to not like the design. It might not be to your taste or your requirements. But that doesn’t mean it wasn’t well designed. I can point to lots of things that I don’t like the design of that work great for others.
Go is super productive, has a great ecosystem, has great concurrency, produces good machine code, compiles as fast as anything out there, has great tools, and is growing in popularity. Flaws? Of course. Perfect? No.
But, it’s well designed.
Go certainly has its fair share of these.
Go also has countless own-goals where a serious concession was made with zero or almost-zero upside. I won’t repeat them here.
You can like go and you can feel that go is a productive language. But given the sheer number of outright mistakes which should have been avoided in the early days of the language’s design and which are now frozen for all eternity, you can’t in good faith call it well-designed.
A great ecosystem is one where there is good 1 library to do a thing and you just pick that one.
A bad ecosystem is one where there are 25 libraries to do some things and 0 to do some other things, and none of the 25 libraries are very good.
I needed to write some code recently that did DSP. There is not any equivalent for this like SciPy in Go that bundles together lots of the common DSP operations you need. There are lots of partial implementations in different libraries of for e.g. filters, all of which use different interfaces.
A lot of this I think is because of the lack of SIMD, you can't get brilliant performance out of the box with a Go implementation because the standard compiler doesn't support it.
Uh, no they're actually not. A tradeoff means you accept some downside and get some upside in return.
Initially go was designed without generics. The tradeoff here was you got a single pass compiler which was faster. Well, actually, you could have a single-pass compiler with backpatching, but I get it, that's hard. It's a crap tradeoff, but okay, I can accept it's a tradeoff. And everybody at the time said, "Go doesn't need generics, they complicate the language." So we casted our nested types in and out of object and lost all type safety in that code, but hey, tradeoffs.
Except everyone kinda realized it was a crap tradeoff and so `go generate` was proposed as a solution. In a 1984-esque revision of the past, suddenly, having a second pass is okay! Never mind how important it was that go is a single-pass compiler. Nevermind that this is basically macros with all their pitfalls. But now we can generate 3 different versions of the parameterized type, one for each nested type, so we get type safety. 2 passes, macros, downsides, type safety, upside, and a pretty big upside at that.
But, here's the problem: now you have two different systems that are used for doing a lot of the same things that don't interoperate with each other very well. And that's not a tradeoff, because there's no upside. It's not "I don't like this but it works great for others" because it doesn't work great for anyone. It's not a tradeoff, it's a side effect of having chosen wrong the first time.
And... now Go has generics. And yeah, generics aren't really a tradeoff, they're just obviously pretty good. But, now we've got 3 different systems for doing a lot of the same things, which again, don't interoperate all that well with each other.
And here's the thing: from a language design perspective, this was extremely obvious that Go was going to need generics. Generics are pretty much the clearest win from the ML-family languages which had already made it into C# and Java by the time Go was conceived. It's absurd that this feature wasn't included in Go from the beginning.
All of them have significant design flaws and quirks, many of which people here write pages of complaints about. But in most cases, it's easy to learn how to avoid them (granted, that's more of a stretch for C, but plenty of people manage, and that's enough).
I'm not against pointing out issues, especially for actively developed languages like Go. However, it's important not to turn criticism into bashing the language or the people using it. That just comes off as snobbish and isn't constructive.
Each of these languages is well-designed for specific needs, and not so much for others. And that's okay.
I think people mostly try to scare others away from using these technologies to push something they believe is better. But instead of that, you could perhaps write a competing article praising and comparing your favorite stack and that would be much more useful, in my opinion.
I have some fondness for C, but I would not say it is well designed compared to modern alternatives. The reason I use it is largely portability which is more a function of its age and ubiquity than its design. You can of course make the argument that it was well-designed compared to the languages of the time when it was designed, and I don't really know enough to disagree with you there.
TypeScript, I will say, I think is actually very well-designed. It has it's problems but it's a massive step forward from anything that existed before it.
> I think people mostly try to scare others away from using these technologies to push something they believe is better. But instead of that, you could perhaps write a competing article praising and comparing your favorite stack and that would be much more useful, in my opinion.
Strong disagree. Polyanna programming where we pretend all the tools in the world are so great results in horrible codebases, and then the programmer who thinks every tool is awesome becomes frustrated, doesn't know why, and moves on blissfully to a new project/company where they begin to plant the seeds of chaos anew. But someone has to deal with that crap codebase they left behind, and at this point in my career that person is often me. Work on codebases for more than a few years and the only way to stay sane is to recognize problems in code so that they can be fixed before they become bigger problems, and doing that requires you to be critical.
Working on a team, the biggest uphill battle is often preventing people from inserting every random tool they get excited about. Usually it's libraries, not languages, that are the problem, but I've seen both.
The fact that a programmer with no Go experience can look at a codebase and quickly get a sense of what's going on is a massive advantage for large teams, code handoffs or long term maintainability.
Go is nobody's favorite language but that's why it's my favorite language.
I don't even need to look. Error checking is going on :D
And no exceptions make it really easy to ignore errors instead. Which is much worse.
Goroutines mean the programmer doesn't have to struggle to avoid blocking, or decide what should be "sync" and what should be "async".
Garbage collection means the programmer doesn't have to think much about ownership.
The error handling is a bit verbose. If Go had generics from the beginning, it could have benefited from something like Rust's Result type and "?" operator. That's effectively the same thing as Go's usual error handling, but more concise.
I've worked directly with hundreds of highly paid programmers over decades. I can count on my fingers the number that would be at all constrained by using Go where it's a suitable choice (an important caveat).
The rest would have their work highly improved by using Go as much as possible, precisely because it's harder to write bad code in Go compared with most languages.
I have worked in many, many codebases and I just can’t say I agree. There are some ways in which go programmers don’t write code as badly as they might have in other ecosystems, but there are plenty of other ways in which they’re actively encouraged to by the language.
And it’s not particularly maintainable. Yes, I can always “just read the code” to figure out what something is doing. But I often have to read more than five times the amount of code to understand what’s being accomplished. On top of that, due to the lack of abstraction power, I have to mentally decompile algorithmic implementations into the actual high-level task they’re actually performing. The (minor, but obvious and) classic example is having to mentally parse every single loop to determine if it’s a map, a reduce, filter, or something else.
Go is certainly more verbose than the more expressive languages but the code is also usually much more robust and reliable in my experience, due to strong types, error handling, the well tested standard library.
On your example, the for loop verbosity is a bit annoying and I'm happy that go 1.23 got user defined iterators to help with this https://tip.golang.org/doc/go1.23#iterators
The loop verbosity thing was just single obvious example and it's good that golang has finally gotten iterators (though their design is… weird IMO). But it's also only one of many, many issues underlying why go code is much more tedious to comprehend than code written in other HLLs.
To be fair, Go almost go the "?" operator like Rust. The proposal was well received. It wasn't the lack of "Result" that held it back, it was that nobody could figure out how to deal with the handling problem. It's not entirely clear what the Rust-equivalent is for all the other moving pieces associated with "?" (e.g. its defined traits). The latest "?" proposal forces a handling body on each use to address that issue. But at that point all you've done is added another way to write "if". Is that a win?
I want sum types. I want non-nullable types. I want enums (preferably, based on sum types).
I'm okay with the explicit error handling, but would be fine with Rust style error handling too.
Having these features would change the language so much that you'd probably want to re-implement the stdlib, so it probably won't happen.
Surprisingly though, while I absolutely love Go's concurrency model, being able to kick off new goroutines with a dedicated "go" keyword and special channel operators have not been a big deal at all. It's usually abstracted away by some library, or you set it up once in your application and never touch it again.
I believe this is true for anyone and any language. Spend a few years in a language on stuff that you have to do (which is the main differentiator between work and hobby projects) and two things happen: the benefits are taken for granted while the warts are magnified fivefold.
The corollary to this is the quote "I won't use something until you tell me why it sucks" (can't find source). If you use something long enough you know the ways it sucks. This makes your criticism more valuable than enthusiastic praise from a hobbyist.
If you're using something only rarely and not touching it again, there isn't much of a difference between go.New(func(){}) from some "go" package, and go func(){}.
To reiterate, I'm not against the "go" keyword. It's whatever ¯\_(ツ)_/¯
Sorry for nit-picking, but 2 days is a duration of time, and you can add one using Add, like this:
t.Add(time.Hour * 24 * 2)
I agree about FFI being a weak point in go when you need to interact with other languages, but it can hopefully be mitigated by using tinygo, which is still being developed.Go is a bunch of preferences and aesthetics. It clearly appeals to a bunch of folks, others have a hard time putting up with it.
Everyone is free to both love and/or hate go. The language's spirit is not going to change. I'd much rather see more realism, honesty, and acceptance. Instead, this class of discussions always seems to become incessant "no u" ping-ponging.
Certainly not what I'd consider curiosity and critical thinking.
Go in comparison seems unnecessarily pedantic, occasionally too verbose, and occasionally unnecessarily terse. But it never makes me lolsob minus the lol
I swear I can't tell if this phrase is supposed to be positive or negative? Then again, I'm not a native speaker...
If you have two files in go or any other language, circular dependencies are usually permitted which also makes sense. It’s only packages that don’t permit this.
But guess what? You create a package in go simply by putting everything in a folder. So people when writing a go project they often make dozens of packages just by the is ocd need of organizing things into folders.
But when you organize things into folders the files in those folders aren’t project files anymore. They are different packages and you can’t have circular dependencies.
And that’s a big problem as this design conflicts with programmer instincts on how to organize things.
We organize things by meaning and name and subject matter. But simply using folders in go we are forced to organize things via dependencies and this causes a clash and makes organizing things 10 times harder than it should be in go.
Imagine I have a chicken and an egg. Chicken goes in the animal package and egg goes in the food package. It is now fundamentally impossible to make a chicken lay an egg and an egg hatch a chicken at the same time. Our tendencies to organize things via meaning has clashed with this requirement of organizing things via dependencies.
Does it make any sense at all? We probably don’t want packages to be interdependent in this way. But the stupid thing is just be creating a freaking folder we create this problem in go and it doesn’t make sense because if the files are outside of a folder you don’t hit this problem.
I would say go is actually poorly designed. I can’t think of any popular language that has this strange inconsistent problem.
It actually causes huge headaches in organizing things in go but idiot developers when they hit this circular dependency problem they literally believe the documentation when it tells them that they aren’t organizing things correctly and they need to think harder about design. So in a lot of go shops they spend an inordinate amount of time naming folders and organizing shit just to get their ocd need of organizing things by name to jive with gos requirement to organize things by dependency.
In the real world things can be in different categories and also interdependent at the same time. Go’s universe doesn’t allow this.
I’m here to tell you that It does make sense to put eggs in the food folder and chickens in the animal folder. Go is just stupid in this area, not you. Don’t believe the idiot who wrote that in the docs.
I am familiar with people eating unfertilized eggs. I am even familiar with balut. But I am not familiar with the delicacy of eating an egg as it hatches. If you eat an egg as it hatches, wouldn't that actually be eating a chick (the chicken) rather than an egg?
I suspect the real problem here is that your domain model is ill-conceived. In fact, while Go has no lack of faults, I suspect the vast majority of the complaints directed towards it come down to the limited feature set not enabling papering over bad design decisions.
_This_ is what OC was complaining about. Considering where the borders of the domain should be is a second handed issue that is only due to the bad choice of having packages be determined by folder structure, which is the complaint of OC.
They naturally create folders based off of semantic meaning instead of packaging. Then they hit the dependency issue and they think creating the folder was a domain modeling problem when really they just created the folder based off of semantic meaning and not dependencies.
The whole thing has confused go developers. They think putting things into the right folders actually has some deeper design meaning when really it’s just to make it easier for humans to find shit.
It makes 100 percent sense to make two folders. Food and animals.
‘I suspect your “domain model” is ill conceived’??? You buy into that? Don’t follow what those idiots tell you.
You realize that we use these actual separations between food and animals in the real world? And now because of some design philosophy the domain model is ill conceived? The result is a more convoluted domain model to make things flow.
Actually I think this is an easier way to explain it to you. The “domain model” or aka categorization of food and animals works in reality and every other language because it’s not a bad design decision. The reason why it doesn’t work in go is because go is the bad design decision.
The distinction between "animal" and "food" isn't necessarily faulty, but an egg considered to be for food and an egg considered to be for hatching are deemed distinct types in the real world. An egg from the grocery store is not going to hatch no matter how hard you coddle it. This is not the same type of egg that will hatch a chicken. Reusing the same model for both types of eggs does not work in the real world, and thus it stands to reason that it doesn't work in software either.
Revisit the model in a way that actually reflects the real world and your circular dependencies are bound to disappear.
https://www.youtube.com/watch?v=bMQ99Y64t90 and many more such examples
Makes sense in the real world. Why doesn’t it make sense in go? You literally think there’s some elegant underlying principle here having to do with categorization do you? It’s mind boggling to me how arbitrary decisions made by the designers of go are thought of as fundamental design rules.
When people use the word “domain model” I know the complex nomenclature has actually led them astray. These are just folders and categories. The term domain model doesn’t change a thing other than making it sound smarter.
Exactly. Like you describe, this domain model has at least two different types of eggs. This matches what was described in the original comment. But, in the original comment, the author was trying to shoehorn all functionality into a single egg type. And, unsurprisingly, it didn't work for him. It wouldn't work in the real world either.
Revisit the model in a way that actually reflects the real world and your circular dependencies are bound to disappear.
Now you have HatchingEgg, and EatableEgg types. Let's be real about where the shoe horning is happening.
You are going to do that type of thing regardless of the language (and especially in languages with more advanced type systems!), even where circular imports and other code structure methodologies are supported. You wouldn't want your `scrambleEgg` function to accept an egg intended to be hatched. The bones are unpleasant.
No I'm not. I'm going to make one Type and it's going to be called EGG. You're going to make an Egg interface and divide it into two subtypes EatableEgg and HatchableEgg.
I don't think you picked up on it but I have one thing, you created 3. And you created 3 just to avoid an orthogonal issue of dependencies. You aren't getting it. In my statement above I was implying that YOU are shoehorning things, NOT me.
Also you have to create a new package now called Types and that's where you put your Egg interface. So go forces you to create a third meta category while other languages I can choose whether I want that or not.
Or I just don't use folders at all in go. Then this issue doesn't even exist. It only exists because I happened to want to use folders, but I can get rid of circular dependencies simply by organizing everything by files.
Enjoy your scrambled bones, then, I guess. But even if you want to model the world as having only one egg type, that type decidedly does not cleanly fit into "animal" or "food", so your taxonomy doesn't work.
> You're going to make an Egg interface and divide it into two subtypes EatableEgg and HatchableEgg.
This seems like a poor assumption. In the real world, if an "EatableEgg" is fertilized into a "HatchableEgg" there will be a type conversion. What was an "EatableEgg" is now a "HatchableEgg" and the "EatableEgg" no longer exists. What do you need the interface for?
> I don't think you picked up on it but I have one thing
Right, but we're talking about types, not things. Things can come in many different types. You even told us a story about how you consider eggs, which are the same thing, to be of different types, so this didn't go unnoticed by you earlier.
> Also you have to create a new package now called Types and that's where you put your Egg interface.
If you were to have a such a thing, wouldn't it go in something like "reproductive structure"? "types" is a strange fit alongside "animal" and "food".
Revisit the model in a way that actually reflects the real world and your circular dependencies are bound to disappear.
scrambled bones my ass. There's no instance where that happens. I have a scramble function that takes an egg type as a parameter I don't know where you're pulling the scrambled bones from.
I don't model the world that way, that's not my objective. I live on a farm, I'm just modelling things the way I modelled it on the farm. I organize my eggs into the food box and my hens into the chicken pen. I do this in the real world. I want to do the same thing in my programming and I can't. Understand?
>Right, but we're talking about types, not things. Things can come in many different types. You even told us a story about how you consider eggs, which are the same thing, to be of different types, so this didn't go unnoticed by you earlier.
If you make two different types, and you instantiate those two types you have two different things. I don't think you're getting it. Those two different things cannot represent the real world thing because the real world thing can hatch OR be eaten. The things you created can only do one or the other.
>If you were to have a such a thing, wouldn't it go in something like "reproductive structure"? "types" is a strange fit alongside "animal" and "food".
You have to do this because your Egg interface can't go into animal or food becuase it will cause a circular dependency.
On my farm I don't have a bin or a box (aka type) called reproductive structure or types. I don't need to do this in the real world but I need to do this in go because Go is poorly designed.
Look, I can make a folder called animals and food and I can put anything I want in those folders in other languages. In go, I can't. It's that simple. Go makes this arbitrary restriction out of nowhere and it makes things worse. You're shoehorning new nomenclature and reproductive concepts into your organizational scheme while I can run with what's done in REALITY with significantly less primitives.
>Revisit the model in a way that actually reflects the real world and your circular dependencies are bound to disappear.
I think you're out of touch with the real world. When you type things in the real world (aka putting things into a box or a pen) you don't care about dependencies. I don't put my eggs in the chicken pen because they came from the chickens. I put it in the food pantry. Understand? Folders are designed to do the activity I just mentioned, they are not designed to form some complicated taxonomy of unnecessary concepts.
Then you're in for an educational treat. Believe it or not, inside a "hatching egg" is a little chicken! And chickens have bones. As your scramble function accepts any type of egg, it may accept an egg that contains said bones.
But it need not be that way. With consideration about your types, you can have the compiler ensure that you only allow "eating eggs" into your scramble function. Just like we normally take care in the real world to ensure the same kind of separation because most people would not be amused if their scrambled eggs contained bones. Apparently you roll the dice, but know that is unusual.
> and you instantiate those two types you have two different things.
I don't disagree, but I don't know of any programming language that has a concept for things. Certainly none that you would actually use for production software. Types are the pinnacle of popular computer science thus far. At some point you are going to have to accept that software and the real world aren't exactly the same.
But revisit the model in a way that actually reflects the real world and your circular dependencies are bound to disappear.
> I organize my eggs into the food box and my hens into the chicken pen.
And the roosters? Those eggs aren't hatching otherwise...
> You have to do this because your Egg interface can't go into animal or food becuase it will cause a circular dependency.
But, most importantly, because it would otherwise be confusing for anyone else who has to work on the code. "Why is a chick about to leave its shell considered food? I don't know anyone who considers that to be food." is the first thing they are going to say upon encountering this codebase.
Code is written for humans. Remember that.
> I put it in the food pantry.
You may put "eating eggs" in the pantry, but I can't imagine you put "hatching eggs" in the pantry. So why are you putting "hatching eggs" in "the food pantry" when taken to the computer? Again, this model you have doesn't even work on a human level, never mind any technical constraints when applied to software.
Except the egg I modeled doesn’t do this and I generally don’t encounter this in the real world so I don’t have to account for it. So your example is meaningless pedantic pointlessness.
> I don't disagree, but I don't know of any programming language that has a concept for things.
The concept of type is a categorization. Instantiating a type is creating a thing that goes in that same category. You’re not getting it because this statement is categorically incorrect.
> Types are the pinnacle of popular computer science thus far. At some point you are going to have to accept that software and the real world aren't exactly the same.
Of course not. The food category and the animal category are not part of the real world. The universe doesn’t categorize things. Categories are made up bullshit by human beings. They aren’t real in any sense. You’re just not seeing this.
What’s going on with golang is that it’s saying your arbitrary categorization of things IS REAL. When you don’t categorize it the go way you actually did something fundamentally wrong is what go tells you. And your issue is, you believe in this go philosophy. You think that what go tells you is real.
Go says a chicken cannot go into the animal category and exist with an egg that goes into a food category. Go says if you try to model things this way something is fundamentally wrong with your understanding of categories and reality and it throws a compiler error to tell you so.
The problem here is that you believe this bullshit. You think golang actually said something profound when really the designer was just an idiot.
> But, most importantly, because it would otherwise be confusing for anyone else who has to work on the code. "Why is a chick about to leave its shell considered food? I don't know anyone who considers that to be food." is the first thing they are going to say upon encountering this codebase. Code is written for humans. Remember that.
You’re out of touch with humans and you don’t see it. As a human I don’t think about accidentally finding chicken bones in my eggs so I categorize eggs as food and chickens as animals. What’s inhuman and overly pedantic is to consider every possible case and permutation of chickens and eggs and try to model everything in your program. How detailed do you want to go? Everything is made out of atoms so everything belongs in the same category? Be real. You are arbitrarily increasing the resolution of what is encompassed in your model to make some arbitrary and false point about domain modeling.
What you’re not seeing is The model is made up by me to simplify things. In go I can’t make the model simple because I have to model things according to golang.
> You may put "eating eggs" in the pantry, but I can't imagine you put "hatching eggs" in the pantry. So why are you putting "hatching eggs" in "the food pantry" when taken to the computer? Again, this model you have doesn't even work on a human level, never mind any technical constraints when applied to software.
I put eggs in the pantry. I can still hatch those eggs if I want. I can eat them too. You put technical constraints on reality for no other arbitrary reason other then to satisfy gos dependency rules. In your model you restricted your abilities. I changed my mind and I want to use half of my eggs in the pantry for hatching. Your “domain modeling” doesn’t allow for this.
Look this is all arbitrary bullshit. You’re just going to keep making up cases where your model is right but for every case there is also a case where mine is right and yours is wrong. This is because there’s an infinite amount of perspectives to look at this subject. How we choose to look at that subject is an arbitrary choice. You want to make two types of eggs and categorize it that way?be my fucking guest. Most people who farm don’t do it this way but go ahead.
Golang takes this arbitrary choice away. It tells everyone that eggs don’t exist. It’s either a hatching egg or an eatable egg. You live in a universe of golang that tells you that this is the only fundamental way to categorize things: by a dependency tree.
Because we don’t know which came first, the chicken or the egg. In your universe of golang that you live in your saying it’s a fundamental truth that we can therefore never categorize chickens as animals and eggs as food. We must destroy the concept of an egg and realize there are two types of eggs. One that is eatable food and Another that is a hatchable animal.
That’s how your brain sees reality. If this is the case so be it. Can’t be fixed. I can’t change you. More likely you’re just stubborn.
Okay, but it is not my example. It was the example given in the very first comment. It what was asserted to have created the circular dependency.
> The concept of type is a categorization.
Okay, sure, but it remains that things are decidedly not categorizations. And, as before, there is no known programming language that has a concept for things. It's an interesting idea! I can see that programming languages could benefit from having such a concept. If you happen to be looking for a CS research project, this just might be it. But if you just want to write production software using existing tools you're not going to find it.
> I can still hatch those eggs if I want.
Your pantry has suitable environmental conditions for incubation, does it? The environment necessary for incubation is not ideal for other foods normally kept in a pantry, so this doesn't really add up. But, hell, let's pretend you do. Do you still not want some way to identify those eggs being incubated and those eggs ready to eat for others, if not yourself, looking into your pantry?
> Golang takes this arbitrary choice away.
Okay, but we're not even talking about Go. We are barely even talking about programming. This model of yours doesn't even fit the real world. Come back with a better model and you won't leave other humans scratching their heads and you might even find the technology won't fight you so hard at the same time.
Right and I'm saying I don't like your example. I prefer my example. And any other language is ok with my example except golang. That's my entire point. That's the problem, to get rid of that dependency I have to go with your example and that's bad because I don't have the choice.
>Okay, sure, but it remains that things are decidedly not categorizations. And, as before, there is no known programming language that has a concept for things. It's an interesting idea! I can see that programming languages could benefit from having such a concept. If you happen to be looking for a CS research project, this just might be it. But if you just want to write production software using existing tools you're not going to find it.
You said things don't exist in programming languages and I literally gave you an example of how it does exist. When you use a type to instantiate an object you are creating a Thing. The point of what I said here is that what you said is categorically wrong and you missed it.
>Your pantry has suitable environmental conditions for incubation, does it? The environment necessary for incubation is not ideal for other foods normally kept in a pantry, so this doesn't really add up. But, hell, let's pretend you do. Do you still not want some way to identify those eggs being incubated and those eggs ready to eat for others, if not yourself, looking into your pantry?
Again you're just choosing arbitrary perspectives out of an infinite amount of perspectives. My perspective is another arbitrary perspective. We can model the problem however the way we want at however resolution and detail from talking about incubation in pantries. I can easily just say I can take the egg in the pantry and put it back under the chicken. There. But this is BESIDES the point.
The PROBLEM, again, with golang is that whatever perspective you choose, golang says it has to fit with their rules while the rest of the world and all other languages don't SAY this AT ALL. Other languages give you freedom, go gives you arbitrary restriction.
>Okay, but we're not even talking about Go. We are barely even talking about programming. This model of yours doesn't even fit the real world. Come back with a better model and you won't leave other humans scratching their heads and you might even find the technology won't fight you so hard at the same time.
We are. I think your out of touch with reality. I'm literally talking about what you can't do with golang. You can't simplify the model of the world to my specific model that I originally mentioned because it causes a circular dependency. But you can in other languages.
I'm not fighting technology. The only thing I'm fighting is golang. No other language makes you do this, so it's a problem with golang.
Models are arbitrary choices and they are useful depending on context. For example Newtons laws of motions are actually wrong but they are still useful for modelling things so in a computer program we can still choose to use it. We don't have to use the most "correct" model involving relativity.
The thing with golang is that it's doing something like this. It's saying your model and it's dependencies has to match how I see the universe. You cannot choose the model.
And I don't like road bridges made of concrete and steel, preferring popsicle sticks. But engineering is not driven by feelings, so this is irrelevant.
> When you use a type to instantiate an object you are creating a Thing.
No, like you said earlier, you are creating a classification. The thing being classified isn't expressible. The languages we have are too limited for that. As a workaround we carry an implied assumption that underneath a classification lies a thing, but as you are finding out that quickly breaks down when there is not a clean association between the thing and the classification.
I like what you are thinking, though. I can see how our programming languages could be a lot better if there was such expressibility. But what that looks like is an unsolved computer science problem, I'm afraid.
> We are
No. Definitely not. We touched on it briefly at the beginning, which is perhaps what is confusing you, but we long moved past that to discuss how one might model the world, namely with expression in natural language, but to a lesser extent all programming languages.
> No other language makes you do this, so it's a problem with golang.
English also makes you do this. Failure to will result in an "aktshually, ..." error.
The issue is you think I'm saying it has to be made out of popsicle sticks. I'm saying it you can use wood or steel and your saying something along the lines of ONLY use steel. Steel is the only way! That's go.
You're just going off into complete fantasy land thinking that my examples don't work. My examples work. Your examples also work. But my examples are MORE intuitive and simple. Your examples are convoluted and strange.
>No, like you said earlier, you are creating a classification. The thing being classified isn't expressible. The languages we have are too limited for that. As a workaround we carry an implied assumption that underneath a classification lies a thing, but as you are finding out that quickly breaks down when there is not a clean association between the thing and the classification.
When YOU instantiate the class, you are creating a THING. The instantiation is an expression of THAT thing.
Maybe you're referring to a type so narrow that it can only contain One thing? I believe TS has some ability to do this and Idris as well. Basically dependently typed languages allow this.
>I like what you are thinking, though. I can see how our programming languages could be a lot better if there was such expressibility. But what that looks like is an unsolved computer science problem, I'm afraid.
I don't think you have a clue about what you're talking about.
>No. Definitely not. We touched on it briefly at the beginning, which is perhaps what is confusing you, but we long moved past that to discuss how one might model the world, namely with expression in natural language, but to a lesser extent all programming languages.
No you moved on. I stayed on topic. I'm just using models of the world to show you how stupid go is. There are certain models of the world Go can't handle. That's all I'm doing. But I guess the details are getting too complicated for you that you inadverdantly lost track and moved on.
>English also makes you do this. Failure to will result in an "aktshually, ..." error.
Is this supposed to be a joke? English supports circular dependencies. Your equivalent of an error in english is a grammar error. Circular dependencies don't trigger a grammar error.
If you're trying to make a joke it's not even funny.
No. You are instantiating a type of a thing. But, in the real world, a thing can be more than one type. Often it is sufficient to approximate the real world by assuming that one thing is only one type, but as you have found out that doesn't work when your program needs to see one thing as more than one type (e.g. an egg can be both a type of animal and a type of food).
A programming language that has a concept for both "things" and "types" is interesting, but no language that you would write production software in today has such a concept I'm afraid.
> I'm just using models of the world to show you how stupid go is.
Go has its faults. Many of them. But it lacking features to allow you to paper over stupid design decisions I'm not sure is one of them. The problem here – and the problem with a lot of Go criticisms you find here, it seems – is that you haven't fully thought through what you are trying to do. A well conceived model of the world would not leave you with a circular dependency here.
But I understand the appeal of trying to take the lazy route, minimizing the model to the smallest thing that makes sense to your internal monologue. It is certainly less typing to have just one egg type that you use for every conceivable egg use, whether it contains a chick, what you scramble for breakfast, and the chocolate delivered by the Easter Bunny. The good news is that there are many languages that are chock full of features to allow you to hack that in. If Go is not the right tool for the job: Great!
I inherited an app from a coworker, and he just put every file in the main package. This was actually fine and caused no problems.
These days, I still have an addiction to making tiny packages, which the Go team would likely not approve. But I'm a big girl and I do what I want! The workarounds I use are:
1) A `types` package. type Chicken struct { Children []Egg }; type Egg struct { Mom, Dad Chicken }.
2) A `utils` package. Let me explain. I would never name a package `utils`. That's illegal and I'm told that the compiler will explode into a thousand pieces if you try. All the king's horses and all the king's men wouldn't be able to put it back together again. So don't test it. Just trust me. But, you can definitely have something like `package hatchery` with a functions related to collecting eggs from chickens. Or you can have `package eggutil` for packing eggs, frying eggs, and shipping eggs.
I would say that I typically have a `types` package (called `types` if I'm hand-writing the code, or myapppb if it's protobufs like at Google), and then structure the rest of my app as model/view/controller. The model puts these types into the database. (Call it `eggdb`.) The controller does whatever business logic you care about (getting chickens together to fertilize eggs, call it `husbandry`). The view calls into the controller to implement your API / gRPC endpoint / beautiful Gtk+ GUI / whatever.
And then you don't really have problems with circular dependencies.
Having said all that if someone puts it all in main.go I'd probably approve the PR. The Go team let my coworker do it in 2014 and nobody died.
This restriction makes sense for packages. But not for folders. The problem arises in go because folders and packages are the same thing and that’s stupid because people don’t typically use folders as if they were packages. Folders are typically used just to categorize things of similar meaning or naming or whatever you want.
No other popular language has this issue. I can choose to organize things how I want in any other language. I have the freedom. I can make a folder A and B and C and D and just organize things in ABC order. But can’t with go. If I dont want to do this in any other language I have the option to create a package.
Creating a package and creating a folder are completely orthogonal tools and concepts and the genius who invented go just made a horrible design decision here.
You might do that. That does not mean everyone organizes things like this.
What your chicken/egg example fails to realize is that things rarely just belong in one category. Chickens can be animals or food.
To me it just sounds like you are trying to write another language while using Go. Of course you will see problems. Just as you would see problems trying to write Go code when using Java.
In the end it comes down to this: if the language does not fit your mental model on how to do things, don't use it. But don't go around shitting on the language just because you are used to using folders differently than other people. Using a language also means learning and using the conventions of that language.
Most people do organize things arbitrarily based off of their own categorizations and opinions. In fact such an overwhelming majority of people who use operating systems do this that it's pretty much universal.
>What your chicken/egg example fails to realize is that things rarely just belong in one category. Chickens can be animals or food.
So what. I choose not to eat my chickens. So I Choose to categorize it in a way that makes sense to me. Why should I conform to your point of view? Why should I conform to golangs point of view? Why can't you and I choose how to do it?
>To me it just sounds like you are trying to write another language while using Go. Of course you will see problems. Just as you would see problems trying to write Go code when using Java.
No. I'm complaining about go. I think it's dumb. I'm not doing OOP here, I hate OOP. Go is waaay better. This is orthogonal to that problem.
>In the end it comes down to this: if the language does not fit your mental model on how to do things, don't use it. But don't go around shitting on the language just because you are used to using folders differently than other people. Using a language also means learning and using the conventions of that language.
Yeah that's how all complaints and criticisms are sidelined. "You don't like it, don't use it. Use something you like." Obviously.
I'm doing exactly that, while saying that go packages are a poor and horrible design decision.
I think a lot of people hated java, and found go and they think because go is so much better then java then that means go can do no wrong.
I don't think you realize there's stuff way better then go out there. But that's besides my point. What I use is separate from my point: Golang packages are poorly designed.
Every language makes trade-offs. For you the trade-off Go makes is bad. I disagree. I like some languages better in certain parts, while I prefer Go's solution in other parts. It's all preference, there is almost never an objective "better" or "worse" like you seem to think.
> Golang packages are poorly designed
Agree to disagree.
I'm looking forward to trying out your own perfect language that is objectively the best for any use case ever and no one can find any faults with it. Because surely such a language exists.
Just look at reality and how people organize things everywhere. It's arbitrary. Adults are placed in offices and kids are placed in schools. Then on family trees kids and Adults are organized by dependency. How people organize things in the REAL world is AN arbitrary choice. But in GO you have NO CHOICE. It has to be by family tree. That is NOT an opinion. Everything I said is FACT.
The metric is basically the entire world and everything in it and how we fundamentally type (aka organize) things within it. But if you want to get more specific just look at every other programming language except go and look at how the entire world uses folders in operating systems. These are tools to allow people to arbitrarily organize things.
>I'm looking forward to trying out your own perfect language that is objectively the best for any use case ever and no one can find any faults with it. Because surely such a language exists.
Don't have one. I'm just commenting on what I hate about golang.
> I'm just commenting on what I hate about golang.
No you're not. You say Go (or a part of Go) is bad, which is vastly different. If you stuck to "I don't like it", you would not have gotten so much push back, but you insist in being right and everyone else is stupid and wrong.
No you don’t. You’re not an idiot. It’s common sense that allows you to do this and it’s common sense that will allow you to see that the entire world works the way I said it does above. You don’t need science for this. At least most people don’t. Maybe you’re different and you need science to do an analysis for you before you open any door to make sure reality still exists behind it.
> No you're not. You say Go (or a part of Go) is bad, which is vastly different. If you stuck to "I don't like it", you would not have gotten so much push back, but you insist in being right and everyone else is stupid and wrong.
You’re right. Let me rephrase what I meant. I’m commenting about what is fundamentally terrible about golang and if you disagree then you're wrong because the overwhelming majority of the world would disagree.
I mean, I don't. I've been doing Go for well over 10 years, so I'm well aware of the limitation and intentionally avoid running into it. "Well I hate that!" Yeah, we know. What can you do about it? Use something else.
In a past life, I taught Perl classes at companies that use Perl. One time I was explaining the advanced parts of @INC (the module lookup configuration/resolution order variable). One student hated it to the point of tears. They could not live with a programming language that had an @INC variable. Nothing I can do about that. It does. Get over it, or don't get over it. Those are the only two options.
Go appears to encourage large source files, since it allows you to define multiple "classes" (types with methods) within the same file. This is probably intentional to combat the complexity of having otherwise cohesive logic littered across several small files in OOP-first languages.
But I like your example and get your point. What if chicken.go and egg.go diverged a few levels earlier in the dir path. All the Go repos that I've seen in production have very flat folder structures, so I guess this is just how Go is written.
People simply just didn't understand what I was saying that there was no inherent meaning in trying to make our naming conventions with folders work with go's dependency rules. They thought the work of untangling all the dependencies, folders and the naming was some quest towards a perfect design.
You can also see in this thread, that a lot of people debating the issue with me believe the same thing. They think this arbitrary rule is speaking to a fundamental design axiom and they use big words to call it "domain modelling"
To be fair, Go modules provide such indirect mechanism, by rewriting source files, with redirection information, really? Yet another hack.
I would instead say that Go had big things right, while smaller things might be not as right---but not exactly "incorrect". For example explicit error handling is the big idea that Go got right, but both Rust and Zig did a lot to refine error handling experiences; comparing them with Go is just a non-starter.
I mean if you're analyzing languages in a void, I guess, but we're not. We're looking at languages as tools to solve programming programs. And almost nobody has the problems that Go was designed around, so the original design goals are kind of irrelevant.
I mean, how many of you have worked on a million line codebase? If you haven't, why would you care that compiling millions of lines quickly was a design goal for Go? Yes, smaller codebases are also faster to compile, but partial compilation works just fine for that in other languages.
Again, if Go had stayed an internal language to Google, I wouldn't have any criticisms of Go. But when they decided to release and market it as a general purpose language, the fact that it wasn't originally designed as a general-purpose language becomes relevant only as the cause of a whole bunch of problems. And more to the point, people considering using it as a general-purpose language should not consider its original purpose, they should consider the purpose they intend to use it for.
I really don't get where you're coming from here. Are you just trying to be nice to the original devs or something? I'm not criticizing them for choices that probably made sense in the context of Google, I'm criticizing the language itself as it is used today, and I'm criticizing the choice to use Go for purposes it was never designed for.
I mean take a step back for a second and look at the big picture of this discussion. Would you really Go for a project if a modern language like Gleam or Rust was a better fit for your needs out of some sense of "fairness" to Go? If not, why is "fairness" even part of this conversation?
What if designers chose to not think about something because that would be irrelevant to their goals however? ;-)
It is worthwhile to look back why we criticize programming languages in the first place. You are absolutely true that current users of given programming language have rights to criticize languages for the current uses, regardless of original intents or designs. But we also want to see better programming languages that are more suitable for given domain---this does assume that we will never be able to build a single language for all uses but that should be reasonable enough. Both criticisms matter and by not distinguishing them you are possibly filtering at least one of them out. Maybe I'm thinking in this way because I'm a programming language designer in my heart and so sympathic to Go developers---that doesn't change my claim that both criticisms should be treated equally.
Again, I'm not criticizing the designers, I'm criticizing the language as it applies to the goals of pretty much everyone in this thread.
And, I'll add, I very much doubt that the original designers' goals were benefitted by not having generics in the language initially. That's just such an obvious mistake.
[1] Assuming that you are not going all in specific design patterns from functional programming, of course.
No, I'm just pointing out that even a "fair" analysis of Go is going to catch some pretty serious design mistakes.
> Assuming that you are not going all in specific design patterns from functional programming, of course.
Eh, Java cerca 2008 (before Go was released) lacked most of the functional programming features it has today, and it was abundantly clear that generics were a critical feature for Java then--I was writing Java professionally at the time, albeit as a paid intern. While template types aren't exactly the same thing, they fulfill many of the same needs and were pretty obviously a critical feature for C++ before that. Go's developers were abundantly familiar with C++, and likely abandoned templates because they're slow to compile, but they literally could have Googled "how to compile template types faster" and found generics pretty easily.
At a high level, my big criticism of Go is that they simply did not stand on the shoulders of the giants before them. Their goals make a lot of sense. But they pretty much only tried ideas from C++ and Python which indicates to me that they didn't know any other languages, and where C++ and Python have problems, they reinvented the wheel, mostly badly. I guess garbage collection was one exception as it's in neither C++ nor Python, but that's sort of a surface-level feature of a ton of languages. If they had any deeper experience with other languages I think they'd have stolen a lot more ideas, and better ideas. There are a dozen textbooks on comparative programming languages that would have easily given better solutions to some of Go's problems.
The one thing they did right initially was trying to keep few features, but if you do that you need to choose more powerful features than they did, otherwise "simple" just means the simple language results in complex code to work around the language's neutered feature set. Now they're filling in those gaps with the features they needed initially, and I'd predict that within a few years Go will no longer be a simple language--arguably it already isn't.
I’ll say that it isn’t well designed. I draw a line at treating an unused import as an error.
The creators of Go had opinions, and so do I. That’s how it works.
1. Go's error handling has four possible cases (result and no error, no result and error, result and error, no result and no error) when only two make sense.
2. A consequence of the above is that it requires null values (nil in Go)
I don't see how this can be considered good design.
A partial success, that returns both a result and an error definitely makes sense. For example, you’re streaming data over a network, receive some bytes, but get disconnected before it’s complete. It would make sense to have both the data received so far as well as the error, in order to resume.
I’m sure there’s an appropriate situation for the 4th case as well.
A justification for Go's deficient type-system I've seen repeatedly over the years, along the lines of "Well actually the occasional function can legitimately return both a result AND an error" is nonsensical. It's basically excusing giving 98% of error-returning functions a semantically wrong return type (a tuple) because it happens to be correct in 2% of cases.
Every function should have a semantically correct return type! If a product-type is occasionally correct for an error-returning function, use one just for those functions! And use a sum-type for the other 98% where that is correct for them!
Also, the design requires null values. A point which the replies have ignored.
result+no error: data returned, no error happened. - Typical function call where you want some data.
no Result+error: nothing to return, error happened. - That typical call failed.
result+error: partial result, up to error. - Wrote x bytes to disk, then ran out of space.
no result+no error: nothing to return, no error. - Data for a passed query. Nothing came back, but that's not an error.
If you consider (T, error) to be logically coupled, there is actually effectively an infinite number of cases. (T, U, error), (T, U, V, error), (T, U, V, W, error), etc. Although I suspect this take is suffering from trying to project idioms from another language onto Go.
I don't "like" go (or any other programming language), but I'm able to get more done in it in a shorter amount of time than any other language, the created thing consumes tiny amounts of resources, and most importantly when I _or anyone else_ goes back to change something weeks, months, years later it's easy to re-establish or gain contextual understanding.
I think "being simple" doesn't necessarily mean "must have subtle sharp edges and papercuts everywhere". Just like javascript I think it falls into a pit of being superficially "low complexity" or "simple", but all the subtle gotchas and unhelpful tooling (The fact a compiler can't help me realize I had added an entry to the enum, but forgotten to update a name list just means the whole feature is no better than just plain ints that we had in C since before I was born).
That's because you know it better than other languages. It's certainly not a universal experience.
I also find that stack traces are generally more useful than plain wrapped errors. I want to know the code path in most cases where I am looking at an error.
More generally, it's disappointing that golang has so many idiosyncrasies given its philosophy of straightforwardness. I struggle to think of any useful, general purpose language that has fared better, though.
The other week, I was working on two APIs: one for a team that uses Java primarily and another where I was free to pick the technology as needed and was looking in the direction of making it easy to deploy, even without containers, where I picked Go.
With Go's standard library (and a few packages here or there), I had something up and running within a few days and it mostly just worked. Things that I wanted to do were just an additional method away, which, while not as sophisticated as the various convention over configuration options out there, kept everything very discoverable and observable.
With Java, I had to setup a Spring Boot project and its security configuration mechanism (SecurityFilterChain, WebSecurityFilterConfig and lots of Filter implementations) and ran into issues where calculating file checksums for uploaded files broke because Files.exists() returned that files exist for ones with UTF-8 symbols in the name, whereas internally FileUtils.checksumCRC32() used file.isFile() which returned false. Then, I also needed -Dfile.encoding=UTF-8 -Dsun.jnu.encoding=UTF-8 and for another mechanism had to look into JCE briefly, as well as configuring Maven to push packages to Nexus, as well as how to disable Spring Boot trying to connect to PostgreSQL and RabbitMQ while bringing up the app context for tests, or to connect to a special instance for tests (and hopefully eventually figure out how to automate a separate schema per CI run). Oh also deciding between OkHttp and Apache HttpComponents. Oh and also adding Apache Tika to the project, yet realizing that I can't add tika-parsers-standard due to some related dependencies.
Now, this isn't a completely fair comparison because the APIs didn't need to do 1:1 the same things and I could have chosen not to go with Spring Boot, but it's so entrenched that it's most likely the majority of projects that you'll see out there (I also like Dropwizard, but I haven't seen many people use it at all). That might sour some people's perception of using that language professionally: https://earthly.dev/blog/brown-green-language/
There are things about Go that are annoying, but it not having all of those levels of abstraction that you need to think about was comparably nicer. Plus, not needing to think about JDK versions and instead just getting a single file that you can ship. Oh, and on a related note, projects like Wails are also really nice (I tried Tauri, the builds were too long and the Electron bundles are a bit too big for my needs in some other projects): https://wails.io/
I like many of the languages out there (even Java and ones like .NET) but the projects and ecosystems around them can make them harder to use sometimes.
The thing is, you don't always get that choice, at least in a way that wouldn't generate much opposition. If Spring Boot is what people use in most of the projects, opting for something else would generate lots of questions about that "inconsistency", especially as a part of the same team/project where services already exist that are written with it.
Same as not opting for ASP.NET with .NET, Laravel with PHP, Django with Python, Ruby on Rails, Express.js with Node.js and so on. Same as for picking .NET in a "Java shop" or Java in a ".NET shop" or even wanting to use Vue when most other projects are made with React etc. I'm not saying that Spring Boot is always the right choice (god no, honestly everything from Dropwizard to the likes of Javalin and Quarkus etc. have their use cases), just that you'd have to be able to make that choice yourself in the first place, which isn't always the case.
Same for Tika and other dependencies, which are often necessary evils: if you need to handle files and get their MIME types, will you just estimate what a file could be by looking at its extension, or do you actually want to look at the contents? And if you look at the contents, will you attempt the lazier approach ("Huh, this is an MS Office container type, no idea what's inside of it") or will you actually look for something more accurate ("Oh hey, this is an MS Office container type, with a Word document inside of it")? Then it becomes, do you want to spend the time reinventing that yourself or just have it done by the end of the day with an external dependency?
Not that Go is immune from that, you'd still probably want to just use https://github.com/gabriel-vasile/mimetype instead of building your own.
Aside from that, it's nice that Go isn't so "enterprise" oriented (yet?) and that the standard library is so strong (whereas Java only got a built in server around JDK 21 IIRC).
Edit: oh, also on my silly little list of jagged edges of Java, there's also the whole JUL, Log4j, Apache Commons Logging, Logback and SLF4J situation, whereas Go just has https://pkg.go.dev/log and it's nice. Not a horrible indictment or anything, I still use Java more or less daily, alongside other languages.
I see Boot as a major risk for long term maintainability unless you are willing to keep Boot experts on staff. This is relevant: https://news.ycombinator.com/item?id=33185010
I wouldn't say the same for Tika, which I have used many times. It has a relatively small and readable codebase, excluding the parsers it depends on, and does not impose much upon its host code. While complex, it is significantly more feasible to fork or adopt it, if necessary.
To your point, the typical golang library, from what I have seen, is closer to being something manageable.
The Java community is too accepting of hacks (or hack-like things) such as bytecode manipulation, annotation processing, classpath scanning, classloader tricks, reflection, excessive reliance on not-quite-code configuration, etc. There are legitimate use cases for these techniques, but they should be used sparingly, and with great care.
The logging situation is emblematic of Java's tendency to on third-party solutions even for basic, primary concerns. The development and build tooling is another major example. Whereas golang provides tools that are standard, powerful and beloved, the JDK only provides low-level tools that are fairly half-baked and require significant orchestration for typical tasks. Same situation with JUnit instead of having a sensible, built-in approach like go test. The list goes on...
Having used Java and .NET ecosystem since their early days to this day, I fail to see why the team wasn't able to deliver in the same timeframe, also Spring Boot isn't the answer for everything.
...unless it's a method on an interface type. I get the point being made about overloading, but the above statement of it is a bit too strong.
> But secondly designing errors as explicit values has been a trend-(re)setter. Go, Rust and Zig have all chosen to use this approach.
Just because Go treats errors as values, you can't compare Go's error handling with Rust's. Rust provides clean abstractions with Algebraic Data Types, ? operator over Result type to reduce polluting code with if-else nonsense in every single function. I bet atleast 30% of Go code in a project is error handling for the sake of handling.
Further Go's syntax inconsistency irritates me more. I still don't understand if I should put a comma or semicolon or nothing between members in a struct definition. Also
a := 10
...
a := 20 // error. a is defined.
a, b := 20, 30 // accepted. why ??
* Easy to crossbuild and you distribute a single executable (although of course someone managed to pull some dependency that was dynamically loaded and made our CLI depending on a running Gnome session)
* C developers who can't write C code that doesn't segfault every 3 minutes (because they never understood how threads work) can still have their ego but also produce somewhat useful code.
A lot of that is from lack of async, or rather that async is hidden behind go routines.
That’s an interesting take, but one that I’ve seen in multiple places in this thread. I find that introducing goroutines into a program immediately increases the cognitive load as now you need to think about joining, data races, and need to think about error handling differently (golang.org/x/sync/errgroup is an excellent library by the way).
Oh and did I mention deadlocks?
I guess it is a dept of error handling. Otherwise you have to write "foo,err1 := Foo();bar,err2 := Bar(),.."
I've always been opinionated about tools and languages. And we probably all have our biases and preferences. What I've realized doing consultancy is that it's rarely advisable for me to tell my clients to change whatever the hell they are doing. It's actively harmful to do so. And I encounter pretty much everything you can name. Including Go.
At the same time, it's taught me to steer clear of certain tech stacks for my own things. I know I don't like them by virtue of having seen others do things with them. Go, is one of those things that's arguably alright and widely used. But I wouldn't prefer it based on what I've seen.
The reasons are mostly non technical:
1) it's a transition language for people. They might start with something like Javascript or python and they might crave something a bit more rigorous. Go is an easy choice. It's there, it's widely used. It has great libraries, and so on. But I've seen people progress to other languages as well. It's a phase people go through as they develop themselves. And there's a definite "if you have hammer, every problem looks like a nail" thing going on with it. For me Go would be a step backwards, not forwards. I have some languages I'm interested in learning. Go is not one of them.
2) it's a Google language and Google has this big "not invented here" syndrome that often translates into them reinventing wheels. In this case C++. They embarked on their journey around the same time several other languages kicked off. Rust, Swift, Kotlin, etc. all emerged roughly around the same time (15-20 years ago). Swift is Apple's language and it suffers from the same issue. So, even though Google's take on this is interesting. It's not the only one or necessarily the best one (subjective of course). Rust and Kotlin are a bit less tied to one company. And both are widely used across most/all big tech companies and less entrenched in just one of them. Full disclosure, I prefer Kotlin out of these languages and actually use it for a lot of things people might think Go is good at.
3) It's a conservative language community. Despite being relatively new, the community seems to resist change a lot. Endless debates about exception handling, package management, generics, etc. and other aspects of the language seem to progress at a snails pace with people bickering endlessly about whether that's good or bad design. That stuff just bores me. Just fix it already. I don't want to read/hear endless reasons why something cannot/must not/should not be a thing because reasons that boil down "we don't like change".
So, no Go for me. But some of my clients use it and it's of course fine for them. I consult them on what they should build, not how to build it. A lot of that stuff works just about the same regardless of the language. Use what works for you, what you are familiar with, etc. Not what others tell you to use.
Cleverness makes for macho code golfing and aha moments in small codebases
Cleverness makes you quit your job when the codebases pass 10k lines
> Cleverness makes you quit your job when the codebases pass 10k lines
I've very much felt that in codebases that are hundreds of thousands of lines of code.
Essentially it becomes: "Is it easier to track down this bug and fix it over a week or two (hopefully the solution isn't needed by tomorrow night), or is it easier to quit my job?"
Sometimes the answer unironically is the second option.
They should have hired someone that knew what he was doing.
When on the clock, make stuff that's readable and easy to manage.
So, not go.
https://groups.google.com/g/golang-dev/c/r4rdPdsH1Fg/m/JG3Wl...
> I can think of two examples of major aesthetic shifts in Go code that seemed ugly at the time but were adopted because they simplified software engineering and now look completely natural.
https://research.swtch.com/vgo-principles
> type Duration int64
https://github.com/golang/go/blob/519c0a2323700934cbec97b75d...
> default values