4 ms·
How is the design too simple? Why is complexity better?
by philosopher1234 5y ago
How is the design too simple? Why is complexity better?
- azth 5y agoGolang is anemic when it comes to modeling ability and expressiveness. I have come across so many code sections where having something like sealed classes and pattern matching or switch expressions would have made code so much readable and safer. The world is complex, it's just reality. The question becomes where do you want to push that complexity, on the user or on the language? golang decides the former, which is why you end up with messes like the kubernetes code base.
- philosopher1234 5y ago>Golang is anemic when it comes to modeling ability and expressiveness. I have come across so many code sections where having something like sealed classes and pattern matching or switch expressions would have made code so much readable and safer. But there is a tradeoff. Every complex feature you add has a continuous cost applied to every reader, in that they have to become expert with the new feature. C++ is a great example of the failure mode here, and even Java has too many features such that using many of them will confuse most of my coworkers. Having few features means that the code is more accessible, and you can spend your cognitive load on the genuine complexity of your problem rather than fiddling with language knobs.
- azth 5y agoThere's a balance. Reading golang code is very burdensome because the reader has to read many lines of code which can amount to nothing more than a map/filter/reduce, and those are littered everywhere. Same with error handling, which is pervasive and distracts from what the code is trying to do. Obviously, it can be taken too far as with the case of C++, but golang is in the opposite extreme where a lot of code does very little and is not clear what it's doing. > and you can spend your cognitive load on the genuine complexity of your problem Which is what a good language lets you do. In golang, a lot of time and cognitive load is spent trying to express your ideas in very unsafe and verbose manners.
- philosopher1234 5y ago>Reading golang code is very burdensome because the reader has to read many lines of code which can amount to nothing more than a map/filter/reduce, and those are littered everywhere. I think my problem is that this simply doesn't match my real life experience. In reality, the Java code (which is the majority at my company) is much more likely to overabstract and thus be very challenging to read than the go code I interact with. So, while your argument is compelling at a distance, it seems to me it must be flawed as it simply doesn't match my experience. I would suggest the flaw might be something like number of lines of code not being equivalent to reading difficulty. Like, yes go code hasa more lines dedicated to if err != nil {}, but those lines are low density and easy to scan. Its a bit like reading light fiction as opposed to Dostoyevsky.
- azth 5y agoThere's no reason to have to write overly abstract code in Java. Modern Java has many features that let you write even more clear code (e.g. interfaces with default methods). Even things like mocking, you don't need to write a dedicated interface, unlike golang, which clutters up the code base and makes it difficult to follow. As for error handling, compare var res = foo(bar(), baz()); and f, err := bar() if err != nil { return err } b, err := baz() if err != nil { return err } res, err := foo(f, b) if err != nil { return err } It's much harder to follow what's going on. Now add more complex code (e.g. code that does validation or something and you'll see how it blows up) var res = foo(items.map(i -> i.validate()), bar()); and validatedItems := make([]Item, len(items) for i, item := range items { var err error validatedItems[i], err = item.Validate() if err != nil { return err } } f, err := foo() if err != nil { return err } res, err := foo(validatedItems, f) if err != nil { return err } 1 line maps to over 15 lines, I don't think that's readable.
- Mawr 5y ago> There's no reason to have to write overly abstract code in Java. Whether that's true is beyond the point, which is that the majority of Java code out there is in fact overabstracted. That should serve as a hint that there may just be something about Java that does lead to overabstraction. > As for error handling, compare [...] It's much harder to follow what's going on. Making error-handling code paths happen implicitly in the background isn't a straight win. For example, you lose the ability to tell a fallible function from a non-fallible one, especially at the point of calling it. Overall, I find that being able to see all code paths explicitly laid out is better, Go syntax aside. Keep in mind that these examples are tiny, and therefore it's trivial to see how exceptions would implicitly propagate. They become much harder to track as the size and complexity of a codebase grows. > Now add more complex code (e.g. code that does validation or something and you'll see how it blows up) [...] 1 line maps to over 15 lines, I don't think that's readable. I wouldn't say the amount of lines is a great indicator of readability, not unless the difference was in the ballpark of maybe 1 to 100. --- I don't think this code sample is representative: - what would happen to this pretty one-liner if I wanted to catch the exception raised in `i.validate()` and raise a different exception instead? - you can create the .map() function in Go too - the Go code size will not grow linearly as you add more .map()/.filter() calls I feel like a good example of Java code would be much denser, leveraging several expressive language features that Go doesn't have.
- zozbot234 5y agoThere's plenty of "surprise" complexity hidden in the Golang stdlib. https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-ride https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-... It's kind of a necessary evil (though I guess it should be better documented, so users are less likely to bitten by it) because Go does not have good ways of expressing complexity in interfaces. This is exactly how enforced "simplicity" can backfire in practice.
- philosopher1234 5y agoBut this is a good thing not a bad thing. The mistake in that article is thinking that the existence of surprising edge cases means the whole design was a mistake. Its better to design for the 99% not the 1%. I don't want to live in Amos' perfectionist world.
- zozbot234 5y agoIt is a mistake when the "1% of the time" edge cases are "surprising"; it's not a problem if they aren't, because the interface accounts for them properly. "Surprising edge-cases" in general is a recipe for very hard to debug software defects.
- philosopher1234 5y agoI think I disagree. I think its ok to make the 1% use case require extra work to debug (it was probably hard anyways) rather than making the 99% pay a tax on every use of the API to save them. But this is perhaps also where the question becomes more of a question of values and use cases than something generalizable. I don't think Go is the ideal language for writing code that demandingly low-level.
- Yoric 5y agoInteresting. I come from the opposite point of view, i.e. that surprising behavior should be avoided like the plague, using combinations of type-checking or linting or dynamic detection (in debug builds). I wonder why our points of view on this are so different. May I ask what your background is? Perhaps you come from more dynamic languages than I do? Until a few years ago, I used to write C++ and JavaScript for a living and OCaml for fun.