Hacker News new | ask | show | jobs
by p2t2p 3031 days ago
Like what is the problem? Thanks to this stack trace your business logic fits into two lines. Do you want to have lines and lines of your own transaction manager? Lines and lines of your own DI framework? Do you suggest to write a HTTP web server yourself? Why don't we count stack frames in kernel space? And what do we have in JVM underneath us? And libc/Win32 layer?

I'm really annoyed by this kind of things like they have any meaning except "I don't know shit about how abstractions work in computers"

%s/you/picture author

6 comments

Why don't we count stack frames in kernel space? And what do we have in JVM underneath us? And libc/Win32 layer?

You even mentioned it yourself: this is the additional overhead added by the Java ecosystem on top of what may already be a pretty large stack of stuff. (From what I've seen, libc is pretty shallow. The majority are either leaf functions like strlen(), or <5 levels from a leaf/before execution disappears into a system call.)

Even if the overhead is not much to a machine, it is certainly going to have an effect on the human who has to figure out what's going on. When you write such code you may not think this way, but every piece of code is a possible place for a bug to be.

From my short experience with Enterprise Java years ago, such deep callstacks are usually symptomatic of code that does far more indirection than actual work --- hundreds of tiny methods that have maybe a statement or two and then call another one. I understand that it might feel good and even better to write code like this, but that simplicity is deceptive: you aren't looking at the whole picture and just focusing on micro-simplicity, when it's macro-simplicity that really counts.

This means that when you're trying to track down a bug, the "interesting parts" are scattered in tiny pieces across dozens of files, and it increases the cognitive overhead significantly in having to piece everything back together. You might not realise that something is wrong until you're deep inside, and then you have to jump back several levels above in the callstack to figure out where things went wrong, seeking in and between files to trace out the execution flow.

I'm glad I don't work on code like this anymore.

"From my short experience with Enterprise Java years ago, such deep callstacks are usually symptomatic of code that does far more indirection than actual work --- hundreds of tiny methods that have maybe a statement or two and then call another one. "

This isn't just a Java thing. I've seen plenty of TDD code written with other languages where there is basically the same thing (so the code can be "testable"), but also with heaps of redundant tests that just add dependencies and maintenance overhead without providing much value.

> libc is pretty shallow. The majority are either leaf functions like strlen(), or <5 levels from a leaf/before execution disappears into a system call.)

I'd wager that the JIT in OP's picture will transform that huge call stack into something <5 levels from CPU instructions/syscalls.

What remains though, is the huge context carried around such a stack; and I'm quite excited at what the Loom project [1] could yield to tackle that.

Such huge call stacks are a good sign to me. I don't want to implement all the corner cases of all the RFC I rely on; and it is great that library implementors can organise their code well. In the end the JIT will adapt my program to the currently used corner case, and prune everything else.

[1] http://cr.openjdk.java.net/~rpressler/loom/Loom-Proposal.htm...

I think you're reading into this too much. It's posted because it's interesting, and something Java developers might not think about very often. The author doesn't imply there's any problem.

"Java EE is a lot about abstractions which I have grown to appreciate over the years. However, I find this very difficult to explain to my colleague who sits just across the room – he is a Mainframe veteran with tons of experience :)"

Debugging these stack traces is easy, just look for your packages and classes, and “caused by”. You should never be debugging framework code. Well almost never.
"Almost never."

In my experience, the code you write within a third-party framework rarely works the way you expect it to the first time. So you often do "debug" the framework, just to see where your code goes while in the framework to see what you missed.

That's not a bad thing, though--it's a great way to learn any new framework!

Agreed and I will piggyback on top of it. I think we forget how reliable some of the code in this stack is. It's battle-hardened and works extremely well with lots of documentation/help.

There is no sane alternative to this...that is the power of open source where we collectively pool our capacity into something worthwhile to everyone. I want more of it.

Using jBPM, you get a NullPointerException when you use a WorkItemHandler with a BPMN <serviceTask>. Not a nice one with a message, either. Had to debug into jBPM to figure out that one.
> Thanks to this stack trace your business logic fits into two lines.

Two function calls for business logic, a hundred dependency libraries, thousands of lines of stateful "initialization" code outside of the stack and a fuckzillion XML files for configuration. But yeah, everything's great!

In one case you have to write two lines, because you care about the business logic. That's two lines to maintain, debug, explain to other programmers, staticaly check, etc. etc.

And instead you want to switch to what? Writing 20 lines of that same logic + implementing stuff that has already been better implemented in all of the libraries, by better programmers (because they were able to focus and specialize on that one thing)?

For what? A 2% increase in wall-clock speed? There is a 1 in a hundred applications where this matters. What matters in others is maintainability and development speed. Development is expensive. CPU time is cheap.

It's called abstraction for a reason. Do you write your system code from scratch each time?
It's called abstraction, but at some point that becomes a euphemism for obfuscation.
I'm genuinely curious here: what would you propose as an alternative?
Spring Boot uses Java configuration. Using the default in memory database, you can create a crazy callstack like that by adding a single line to enable the transaction annotation.
Expressiveness is not always a pure ideal; reducing your business logic to two lines of code comes with tradeoffs. The ways your code work within the frameworks you choose is not always obvious, and when something goes wrong, it's harder to point at one place within your two lines of code and say: this must be it.

The opposite is not, and should not, necessarily that you write your own versions of things; after all, if you did so, you'd still have as deep of a stack trace as you do with the third-party code!

I guess, if I encountered this stack trace, I might say there's something of a code smell there. But, hey--this is also fairly old code, and I suspect things are Better Now, so let's not be so quick to cast stones!

The problem is it can be extremely difficult to map those stack trace calls to concrete Java code, because so much of a Spring application is generated from an annotation, or code getting called simply because a dependency exists on the classpath. Sometimes classes appear out of thin air, with no Java code existing for them at all, summoned into existence by some annotation somewhere.

Object Oriented Programming has been mostly replaced by Annotation Oriented Programming in modern Java web development.

Java has annotations, C/C++ has macros, every language from Lisp to Rust to Scala has some form of macros or annotations.

These can often be very powerful, but of course debugging is more complicated.

Lisp macros are still just code, code that executes at compile time instead of run time. Code generated in response to an Java annotation is only loosely associated with the annotation itself.

Annotation Oriented Programming arose out of Java's lack of powerful and general abstractions like first class functions. Lambda syntax for "single abstract method" interfaces has largely addressed that problem, but the use of annotations as the primary higher order abstraction mechanism remains in Spring and similar frameworks.

Lisp macros are part of the day to day tool set of every day Lisp programmers. They should be used with restraint, but can be debugged and analyzed with the same development tools and skill sets as other Lisp code. Java annotations belong much more firmly in the realm of framework authors. Writing Java applications often involves using annotations, but very rarely involves creating annotation and generating code or new behavior based on them.

> debugging is more complicated

Only if macros are second-class relative to built-in primitives in terms of debug support.

E.g. typical C compiler and debugger not knowing anything about C macros; if we make a loop construct entirely in a C LOOP(...) macro (spanning many lines of the source file), any error within the statements and expressions enclosed in LOOP will be reported against LOOP's line number. Moreover, we will not be able to step through those individual expressions in the debugger.

Before annotations, it was XML so you have to go back a while before it gets simpler.