Problems using interactive debuggers with Java 8 s

2020-07-06 08:02发布

I love Java 8 streams. They are intuitive, powerful and elegant. But they do have one major drawback IMO: they make debugging much harder (unless you can solve your problem by just debugging lambda expressions, which is answered here).

Consider the following two equivalent fragments:

int smallElementBitCount = intList.stream()
    .filter(n -> n < 50)
    .mapToInt(Integer::bitCount)
    .sum();

and

int smallElementBitCount = 0; 
for (int n: intList) {
    if (n < 50) {
        smallElementBitCount += Integer.bitCount(n);
    }
}

I find the first one much clearer and more succinct. However consider the situation in which the result is not what you were expecting. What do you do?

In the traditional iterative style, you put a breakpoint on the totalBitCount += Integer.bitCount(n); line and step through each value in the list. You can see what the current list element is (watch n), the current total (watch totalBitCount) and, depending on the debugger, what the return value of Integer.bitCount is.

In the new stream style all of this is impossible. You can put a breakpoint on the entire statement and step through to the sum method. But in general this is close to useless. In this situation in my test my call stack was 11 deep of which 10 were java.util methods that I had no interest in. It is impossible to step through the code testing predicates or performing the mapping.

It is noted in the answers to Debugging streams question that iteractive debuggers work fairly well for breaking inside lambda expressions (such as the n < 50 predicate). But in many situations the most appropriate breakpoint is not within a lambda.

Clearly this is a simple piece of code to debug. But once custom reductions and collections are added, or more complex chains of filters and maps, it can become a nightmare to debug.

I have tried this on NetBeans and Eclipse and both seem to have the same issues.

Over the last few months I've got used to debugging using .peek calls to log interim values or moving interim steps into their own named methods or, in extreme cases, refactoring as iteration until any bugs are sorted out. This works but it reminds me a lot of the bad old days before modern IDEs with integrated interactive debuggers when you had to scatter printf statements through code.

Surely there's a better way.

Specifically I would like to know:

  • have others experienced this same issue?
  • are there any 'stream aware' interactive debuggers available?
  • are there better techniques for debugging this style of code?
  • is this a reason to restrict the use of streams to simple cases?

Any techniques that you have found successful would be much appreciated.

2条回答
不美不萌又怎样
2楼-- · 2020-07-06 08:42

I'm not entirely certain there is a viable work around for this problem. By using streams you are effectively delegating iteration (and the associated code) to the VM as far as I understand it, thus shoving the process into a black box that is the stream itself.

At least from what I've read about them. This is sort of what's happened around lambda code for me (if they're complex enough, it's very difficult to track what's happening around them). I'd be very interested in any debugging options out there, but I haven't personally found any.

查看更多
走好不送
3楼-- · 2020-07-06 08:43

have others experienced this same issue?

Yes.

is this a reason to restrict the use of streams to simple cases?

Yes. I'm basically not using streams for this reason. Even simple cases sometimes need debugging. We first need a good way to debug this before we can use it in real code.

查看更多
登录 后发表回答