3 ms·
I work on a program that is 100+ KLOC. It's quite atrocious honestly. I can't help but think that if it were written in Java, it would have been much shorter, p
by apta 6y ago
I work on a program that is 100+ KLOC. It's quite atrocious honestly. I can't help but think that if it were written in Java, it would have been much shorter, probably at least 50% if not even more.
- philosopher1234 6y agoCan you go into more detail about how java would make the program shorter?
- apta 6y agoSome examples: * Exceptions for error handling * Streams (map/filter/etc.) * Generics * Superior collections library (Set, ConcurrentHashMap, LinkedHashMap, etc.) * Records * Enums * Upcoming feature: pattern matching
- philosopher1234 6y agoThis is not really what I had in mind. I know java has these features, but how would they make your program shorter? Could you be more specific? Maybe share an example of a real problem that is made easier by these features? Often people will give made up examples, but the trouble with made up examples is you can't tell if they actually matter in practice or not. Sure, if we invented a way to turn popcorn into gum, itd be easier to turn popcorn into gum, but no one wants to do that in reality. I'm after real examples.
- curryst 6y agoI can give some concrete examples. Exceptions create drastically shorter applications versus Go's explicit returns. I'll have to see if I can run an analysis later; I would wager that 20-30% of the lines in our monorepo are related to error handling. This is in large part due to the style of having the if err return err take 3 lines, but that's the prevailing style. Additionally, it requires an explicit handling block everywhere that might return an error, even if you don't want to handle that error there (i.e. in a web app, not handling an exception defaults to a 503 error. It takes 0 code to return a 503 on an error, which is usually the default error path). For generics, I needed to write a map merge for two map[string]interface{} maps. Fairly basic, for scalar values prefer the value from the first map on conflict, for slices append the two arrays, for maps do a recursive merge. I used a type switch to get the type of each value, when I realized that []string and []interface{} were different, and that I wouldn't be able to append to a []interface{} unless I convert the []string. Fine, so I need a function that creates an []interface{} and converts each index in the []string to an interface{} and puts it in the array. Except because of the type signatures, I now also need one for every scalar type in Go. Is it an overwhelming amount of effort? No, but it did turn 6 lines of code with 12 lines of unit tests into something like 50 lines of code with 100 lines of unit tests (more type switches). Generics should have allowed me to say that I don't care what type the input is (and interface{} doesn't work here). Worse, now if I adjust a unit test, I have to do it in several places, not just one. Those are the things that frustrate me in Go. It ends up feeling like I'm brute forcing software development; it's not the fastest way to do it, but it'll work if we're just willing to rewrite the same function 10 times with different type signatures, or write if err statements 10 times so that we can get the error up to the 11th caller in the stacktrace, who's actually going to do something useful about the error.