Hacker News new | ask | show | jobs
by bognition 1743 days ago
Agree 100%. Let me build stuff using simple easy to understand tools.

Cleverness in code starts working against you as a code base and organizations scale.

1 comments

Doesn’t Java tend to have a lot of cleverness/magic? Pretty crazy runtime codegen, reflection, etc? Spring and other DI frameworks seem overly clever to me, as do many ORMs. I guess my standard for a simple, minimal-magic language is Go.
Is DI really being clever? I'd rather work with people who understand DI than a big ball of procedural mud.
"DI" and "DI frameworks" are different things. For example, in Go we inject our dependencies (we assemble our object graph in the main function rather than in thousands of constructors), but we're not using XML/etc and a magical translation layer.
That DI layer (speaking for Spring) is pretty handy for large projects, though, because I can switch out implementations based on configurations centrally, I can use factories without change of injection patterns, ...
I've heard that, but I don't understand it. Is the idea "I don't have to deal with static typing"? In vanilla Go code I can change out implementations super easily as well (via interfaces, which are less tedious than Java interfaces due to structural subtyping). We can also use factories, but this is precisely the kind of stuff I prefer to avoid except in certain very rare cases ("but with Spring, no one is forcing you to use factories!"--maybe not technically, but if it's idiomatic or otherwise culturally accepted then you can guarantee that your coworkers will reach for them).
What I am referring to with factories in particular is that I can have a bean injected in Spring automagically using an annotation, have an interface and an implementation behind it.

Now, I can go ahead and make the concrete implementation dependent on configuration, e.g. by providing multiple implementations annotating them with a condition that is evaluated using the application configuration. I can also switch to a factory method that creates and sets up the concrete implementation for the interface, I have not to change any place in the application that is using that piece, though.

The same should be possible in Go with reflect and an IoC container, but I am not sure if there's a solid implementation for that out there.

The code base at work has 100s of thousands of lines in several services based on Spring Boot. There's not a single factory in our code.

We also do not use XML to configure DI. Just add a annotation to a class and you can inject it automatically in any constructor without any additional config/annotations.

If this is too much "magic" for someone's taste, I will not argue even if I have a different view. But please do not make absolute statements based on a bad experience in some companies.

Modern Java and frameworks have evolved a lot in recent years.

How do you maintain a single main function for large projects? I personally don't think @Singleton and @Inject are that magical.
Pull the graph construction logic out into helper functions if necessary. Pretty straightforward.
Oh interesting! Would you have an example you could link, I’m curious what this looks like
Yes but idiomatic java doesn't require you to use those thing.
What does “cleverness” refer to then? Couldn’t I equally argue that idiomatic Python (or any other language) doesn’t require I use cleverness? And if so, what sense is there in praising Java for its lack of cleverness?
The aspects mentioned (Spring, DI containers, ...) are tool on top, though. If we just look at the language, I'd argue Java is boring - which is a good thing for certain types of applications. In languages like JavaScript, you have a number of ways to do things with all kinds of different syntaxes. In Java, there are fewer, but well understood options, that sometimes require more and sometimes require less actual code to achieve the same thing. This is highly subjective, if course.
Ok, I understand your meaning, and I agree that gratuitous syntax sugar is a bummer; however, I've never found it to be particularly problematic. On the other hand, I have found a culture of magic/cleverness and a magical ecosystem to be problematic--this is one of my bigger grievances with Python after a decade and a half of experience.
I am leading a transition from TypeScript to Java + Spring Boot and comparing those side-by-side, Java is significantly easier to follow. In TypeScript, we have a (from my point of view) too complex type system and features like destructuring and spreading, that sound smart but actually make it difficult to understand if you are not the author of the piece in question.

What do you find magical in terms of Python in particular?

> sometimes require less actual code to achieve the same thing

What are some things thing you can do in Java with less code than in Javascript?

I will choose one “magic” annotation over repeating the same buggy code thousands of times. Not having abstraction is just as bad as too much.
This is transparently a false dichotomy. No Go programmers are "repeating buggy code thousands of times". Go has a bit more boilerplate than Java until generics land, but I maintain that it's a non-problem 99% of the time (mostly it's just the simplest of conditionals and for loops). And anyway, a generic type system is a much nicer solution than an annotation or other magic.
How about eg. transaction management, resource handling, db connection pooling? Could Go implement “generic” solutions for that? The declarative/meta-programming aspects can make common use cases extremely easy to implement (and web frameworks are full of already implemented 100x times functionalities)
Yeah, Go has generic db connection pooling in the standard library. I'm not sure what transaction management or resource handling you're referring to specifically, but for resource management we usually just do `defer foo.Close()` or similar, although I prefer to make `func withFile(fileName string, callback func(*os.File) error) error` helpers that open a file, pass it to the callback, and then close it. To your point, we have to write one of these helpers per resource type, but it's such a small, simple amount of boilerplate that I don't even bother putting that into a package to reuse. Not a problem in practice.
And yet in Go you don't have NPE that plagues Java code.
They haven't really been a plague for a while, not like they used to be. A lot of forces are behind it, my top three reasons would be 1) because of the Optional type and Optional mapping makes them easy to avoid in chained calls and is less ugly than a nest of null checks or the equivalent err != nil mess in Go, 2) because of better development practices becoming more common like SOLID and especially https://en.wikipedia.org/wiki/Law_of_Demeter and 3) because of functional programming styles becoming more widely adopted, with Clojure/Scala/Kotlin showing what's possible on the same JVM.
> less ugly than a nest of null checks or the equivalent err != nil mess in Go

"null checks are ugly and error handling is messy" are your opinions, not objective facts. I actually prefer Rust-like enums, but short of that, I think Go's error and nil handling seems quite a lot nicer in practice than Java's `Optional` facilities. Note that these are my opinions, and I'm not posing them as objective facts.

Using the word "ugly" marks the opinion as inherently subjective.