6 ms·
> This makes Go easy to learn and, even more importantly, it ensures that Go projects remain understandable even when growing in size. Do people actually find
by stouset 4y ago
> This makes Go easy to learn and, even more importantly, it ensures that Go projects remain understandable even when growing in size.
Do people actually find this to be true? Honest question.
In my personal experience, as Go programs grow in size, they become increasingly unmaintainable. Avoiding abstraction and keeping everything explicit sounds great in theory, but what actually happens in a larger project is that they just become a mess. Functions start taking on seemingly-unrelated extra parameters just to shove a tiny bit of extra necessary information four layers down. Details related to one coherent idea are strewn about everywhere because it was easier to add the logic in situ to a dozen places rather than have one part of the code responsible for it. Internal idioms are copy-pasted across dozens of files, many of them missing improvements made to later copies.
I've seen this happen in every large go project I've jumped into, and it (subjectively to me) seems to be significantly worse than in other languages. Maybe it's because—on average—gophers tend to trend more junior in their careers and there haven't been enough senior mentors keeping things well-factored. But at this point it makes me instinctively dread any time I have to switch to a new project that's written in the language.
The whole "complexity is bad, explicit is good" mindset vaguely reminds me in some way of the hype around "schemaless" databases. No database is schemaless. All this means is that you still have a schema, it's just implicit, inconsistent, and now you lack tools to understand and/or manipulate it. The complexity in your go program still exists, it's just scattered absolutely everywhere and you lack good tools to wrangle the worst parts of it.
- simoncion 4y agoI don't find this to be true. Go projects of a particular size are generally more comprehensible than Ruby projects of the same size, but that's _only_ because Go uses explicitly-declared typing. Large Ruby projects and large Go projects are both pretty terrible to try to understand.
- bryanlarsen 4y agoLarge Ruby projects are by the far the worst in my experience, far worse than Python, Java, C++, or JavaScript. So if Go is just as bad as Ruby than IMO you are agreeing with the parent, not disagreeing. Of course team quality matters much more than language choice, so my experience will differ from others.
- jdkdodpdm 4y agoI've been writing exclusively Go professionally for six or seven years now so I think I'm qualified to contradict your experience The types of problems I get hired to solve are messy, real world problems. There's not much that you can do with fancy abstractions when everything you deal with is a corner case, so explicitness is extremely appreciated. I do find Go projects remain understandable even when they get large, and in my field -- distributed systems -- they are often used in tandem with a service oriented architecture that limits the size of any particular component, anyway. I used to write Python. I don't miss it. Go keeps my coworkers from writing code that's too clever for me to understand, and keeps me from doing the same to my team.. and to myself, in the future.
- BreakfastB0b 4y ago> Go keeps my coworkers from writing code that's too clever for me to understand Until someone whips out `import “reflect”`. I’ve always found it confusing that people claim Go is simple when it contains runtime type reflection, which is hiding just under the surface of lots of the standard library like `json.Marshall`. Trying to explain to a new Go developer why []int isn’t assignable to []interface{} is always fun.
- ikiris 4y agoif you let people use reflect in non testing, that's on your reviewers and training.
- BreakfastB0b 4y agoHow do you square that away with its use in the standard library, and many third party libraries? Someone’s gotta write the reflection code.
- dhosek 4y agoI’ve not worked with Go (although I did read The Go Programming Language), but use in a library is a different beast than use in an application. In Java-land (where I make my money these days), I would reject any pull request for application code that used reflection but at the same time, I happily use libraries that push reflection to its limits. To take one common use case for reflection, Json marshalling/unmarshalling, if I were writing an application that needed to do this, and for some reason I could not use a library, I absolutely would not use reflection, but would write code that specifically targeted the classes that I was marshalling/unmarshalling. Conversely, if I’m writing a library, I don’t know what those classes are so I must use reflection to manage this. That’s the key difference here.
- shakow 4y agoI have been using Go at work for a few years after having used rust for years before that (and still using it for other things), and I agree with you. In my experience, you can not make complexity disappear: it can be reduced to a point, but there's a domain specific lower bound on any project. So the question is: where do you move it? Rust decided to move it into the language, whereas Go prefers to move it into your code. I guess that beauty is in the eye of the beholder, but I stand strongly in the first camp, for the simple reason that I find more complex, but more concise and specific forms, to better carry over the thoughts of the people whose code I'm reading. On top of that Go is pretty silly on many more minor aspects (unused import & variables are a no-go, but dead functions are A-OK; I have to change my session-wide git settings to access private repos; the formatter is very poor; ...).
- saghm 4y ago> Rust decided to move it into the language, whereas Go prefers to move it into your code. I guess that beauty is in the eye of the beholder, but I stand strongly in the first camp, for the simple reason that I find more complex, but more concise and specific forms, to better carry over the thoughts of the people whose code I'm reading. Put another way, language complexity will be consistent in every project that uses the language, but complexity in the project code can vary drastically from one project to the next. The tradeoff here is that once something gets put into the language (or the standard library, since effectively they share the same compatibility concerns), you can't easily change it later. People coming to Rust from other languages often are surprised that the standard library seems to be "missing" things like a HTTP client or regexes, and to a certain extent, they're right that it appears to be inconsistent with the philosophy of being willing to allow complexity in the language. I think the misunderstanding stems from the fact that decisions driving the development of the Rust language tend to be a lot more conservative than realize due to the (not entirely undeserved) reputation of the Rust community at large. As a somewhat orthogonal example, there's a common perception that the Rust community wants to rewrite the entire world of C/C++ code in Rust, and while the idea of that certain does excite a lot of Rust programmers, the people who are in charge of Rust have actually put a _lot_ of effort into making interop between Rust and C a viable way of working, and there's a surprising amount of tooling in the ecosystem to help bootstrap wrapping C libraries. On the other had, it's fairly well accepted that Go is not intended to be a replacement for 100% of the use cases of C, and yet almost everyone I've talked to who is passionate about Go has had a fairly negative view of the experience of using cgo. I've really come to appreciate how well-thought language-level decisions are made for Rust, and it's unfortunate that strong emotions in discussions comparing languages (generally on both sides) tend to drown out the higher-level insights that would probably be more useful given the low likelihood of succesfully convincing someone online that your language of choice is in fact superior in some way.
- amscanne 4y agoI don’t think any language can prevent poor structure or poor design. And it certainly can’t prevent debt like parameter inflation (rather than refactoring layers, using encapsulation, etc.). I think that the simplicity and explicitness actually help to make these kinds of flaws more obvious. This is probably better than hiding poor structure with clever language features. (I’m not saying that those features are always bad — Go is certainly missing some upsides from these — but they can also be used to mask deeper issues.) In other words, Go’s simple language won’t stop you from seeing bad code, but hopefully means it’s easier to see and understand structure (and improve it?). Maybe the awfulness of a codebase is more a consequence of the team culture and history, but the language can influence how difficult it is to see the problems. I certainly think this would apply to C/C++ (with C being less powerful and harder to hide).
- jmaker 4y agoI think some languages are better suited than others for certain styles of code and specific kinds of software. Go’s syntax might appear simple as opposed to Scala say, but Go’s semantics have enough idiosyncrasies to seed a lot of bugs which an inexperienced gopher wouldn’t manage to detect even after all linters having been applied and the compiler being satisfied. Considering older languages such as C++ and Java, the latter has several frameworks for web apps where a lot of complexity is opaque to the user as long as the user sticks to the defaults. For C++ there are few. In either case, over time certain patterns of design emerge. Typical concepts such as dependency injection makes sense with some semantics but but less so with others, e.g., with Scala’s implicits, while still being applicable in another style of Scala. With Go, one can alter receivers on a per-package base. The complexity never goes away, it’s always somewhere and there’s always some cost to managing it. With Spring, you pay with performance as long as you stick to the defaults and they don’t match exactly your model of execution. With Scala and Haskell, it’s on the type level and mostly implicit but the compiler has got more skin in the game and gets to help you a lot—you sort of ask the compiler to assist you more the more explicit you are about your types. With Go, the complexity is scattered and smeared across your code structure. And it’s on you to properly chase it. With generics it’s a bit simpler now. And abstractions are a boon in the hands of an experienced developer. And sometimes they really should be discovered and not imposed—depends on whether the problem is formulated beforehand or in the process of an agile cycle.
- schmichael 4y agoYou can look at any of HashiCorp’s old projects to test this hypothesis. Vault, Nomad, Terraform, and Consul are all over 7 years old at this point. With the exception of perhaps Terraform they’re fairly monolithic as well: significant network services that have steadily accreted features for almost a decade. I’ve worked on Nomad and so am far too biased for my opinion to carry much weight but: Nomad and Consul share a lot of code structure so I find both fairly trivial to navigate and find what I’m looking for most of the time.
- jmaker 4y agoI believe there’s always a cost to managing the complexity and the complexity never diminishes. It depends on whether you are efficient at managing it that way or another. Go doesn’t give you many tools to control it but forces you to certain behavior while developing the code. If you are strict about your code structure and the use of certain perhaps untypical design patterns, then you’ll be fine when the code base grows large. There are well-managed C projects that thrive. But they solve specific problems and to them they do lend well. For other kinds of projects the cost of managing the complexity that way might become prohibitive. Go is well-suited for such problems that the HashiCorp stack solves. But Vagrant has a Ruby code base. Many similar tools were written in Python. Gentoo Linux package manager is Python. Homebrew is Ruby. The complexity of your specific problem distributes itself unevenly across all knobs and bolts of the development process. Some teams tend to manage Python code bases more efficiently, while others might prefer C++ over Rust. Some languages have libraries available that take away a lot of complexity from your immediate control by solving some part of your problem—you delegate it in the hope that it’s managed properly over there.
- papito 4y agoHashi repos was my go-to place to study Go best practices and good patterns, but there are not that many places out there can pull off a large Go codebase without it being a complete mess. It requires a LOT of discipline and coordination. People who claim that "you can learn Go in a couple of days and just run with it" have no idea what they are talking about. Writing Go is easy. Writing good Go is very hard. My personal impression is that the Go fans simply assume that Rob Pike and Ken Thompson are language design gods and can do no wrong, but what I saw instead was language decisions and inconsistencies stuck in the decades past, incompatible with the complexities and scale of modern software development.
- vgchh 4y ago<rant>Having recently started writing Java once again, i am horrified at the mess that it has become. Between reactive streams, completable futures, guice wiring, it feels like voodoo. It’s hard to even reason which code is running synchronously and what is asynchronous. Not to mention the upcoming lightweight threads. </rant> I crave for the simplicity of go routines and explicitness.
- kaba0 4y agoWhy would you need all of these things at the same time? That just sounds like incompetency on someone’s part.
- apatheticonion 4y agoI agree. There are things about Go that are outstanding, like the ease of cross compilation and self contained static binaries making the development of CLI tools trivial. However the language design itself is too rigid and inconsistent to allow for software engineering paradigms that even the community itself advocates for. Without highly skilled software engineers at the start, my experience has been that Go projects tend to devolve into spaghetti reasonably quickly. I feel there are a lot of similarities with the JavaScript ecosystem where we see a lot of bad advice in community posts that people feel passionate about. I think Go was an innovative language back in the early 2010s when they started thinking of design patterns to improve the ergonomics of concurrency. Since then I have felt continually frustrated by how close it is to my perfect language but how they just keep missing the mark. I still use it to write CLI tools though. I would use Rust more but it's annoying to cross compile (particularly for MacOS)
- Shorel 4y agoI agree with you. It's not about surface level complexity. Some modern language complexity is abstraction that allow us more complexity while still being manageable. You are spot on about "schemaless" databases. I would also add "Patterns". When the compiler doesn't provide enough features, the programmer has to do some of the work the compiler would do. This extra work are the software patterns. Functions are a software pattern in assembler. They are a language feature in structured programming languages. ADT are a software pattern in structured programming languages. ADT are a language feature in object-oriented programming languages. And so on...
- karmakurtisaani 4y ago> No database is schemaless. You can certainly have a database of JSON files without imposing any schema on them, apart from perhaps some form of id. Whether it makes sense is a different story.
- majewsky 4y agoThat's only technically true. As soon as you need to implement anything beyond func (db* Database) GetEmployee(id int) (string, error) func (db* Database) SetEmployee(id int, value string) error even just something like func (db* Database) ListByDepartment(departmentID int) ([]string, error) You have some builtin assumptions about the shape of your data, i.e. a schema.
- karmakurtisaani 4y agoNope. That's a problem of your code, not a schema on the database. You could have those functions return something optional, where a None is returned when the data doesn't conform to the assumed schema, and you're safe again. You could have cats and dogs and cars in your employee database and not run into problems. In fact, this is how you have to work when you're dealing with large amounts of unstructured data.
- stouset 4y ago> You could have those functions return something optional, where a None is returned when the data doesn't conform to the assumed schema Again, you have a schema. It's just invisible and implicit, and you have no tools to manage, enforce, or deal with it.
- karmakurtisaani 4y agoIf you define it like that, what's the point of the concept of schema at all? When I say no schema, I mean the user has no control over what's in the data. They can query for employee id's, pet names, birthdays, etc., and get back meaningful data or just Nones. Thinking about a schema this way is useful.
- zozbot234 4y agoRust has better features for programming "in the large" compared to Go, but they still require proper discipline and awareness to use. You simply can't entrust a large, complex project to a horde of code monkeys and cowboy coders, regardless of the language. Modularity boundaries must be part of the high-level design, either at the outset or refactored-in at some point as the project grows.
- sidlls 4y agoGo's allure is that is dead simple to get a basic http service off the ground: practically "hello world" level simple. Then complexity sets in. Go has the same problem with interfaces, mocks, and work arounds that enterprise Java does, for one thing. Then there's go's concurrency: deceptive simplicity is not good. Go makes it trivial to write concurrent code. Concurrent code that's resilient and efficient? Not so much. And with all the other little footguns and antipatterns/antifeatures in the language, it also makes it trivial to write regular code that is fragile and prone to error.
- flippinburgers 4y agoExtra parameters is the complaint you come up with? That happens across all languages. Copy paste? Again all languages. You are complaining about the authors not the programming language.
- stouset 4y agoI am very, very sorry I didn't exhaustively enumerate every possible way I've seen large golang projects become unmaintainable?
- tooltitude 4y agoYou could write unmaintainable code in any language. Rust have more opportunities for it than for Go, C++ has even more of them.