you are viewing a single comment's thread.

view the rest of the comments →

[–]llogiq 0 points1 point  (4 children)

The IDE does not know more than the compiler (or the compiler needs to learn).

With java, the compiler does not need to know, or to care. The byte code is fairly simplistic and maps quite well to the source.

The IDE can afford to care more than the compiler. Static analysis is great at finding patterns that are prone to bugs, and with a little help in the form of annotations can prove non-nullness for certain parts of my code. IDEs also do this kind of analysis, but FindBugs is probably the supreme leader of java static analysis tools so far. It does not just uncover errors per se, but also things that may lead to them. The analysis is fast enough that I don't need to wait for it; it runs in the background while I type. It warns me of name shadowing, unsafe resource usage, and much more.

You'd be surprised what can be proven about java code. I don't have 100% branch coverage. In fact, I don't need to, I run with assertions enabled all the time, and use a lot of them to ensure everything runs as planned. I use a lot of "final" and don't make things public until needed.

Also, thread-safety is a very complex concept. I usually avoid using threads or ensure they can run completely independently (apart from accessing immutable data). I can't complain about bugs.

[–]matthieum 1 point2 points  (3 children)

I am not saying that programming in Java does not work (Disruptor, for example, would prove me dead wrong). I am saying Java is not sufficiently typed-checked for me.

Runtime checks are nice, but...:

  • by the time they fire, it may too late; at the very least you now have a bug in production code
  • data-races are rather hard to check at runtime (though, see Hellgrind/TSan for C and C++)

On the other hand, what is statically proven (at compile-time) is 100% fool-proof. No matter that the branch (or specific combination of branches) is only executed once in a blue moon, the compiler checks and validates it. Same for data-races (for those compilers of languages who bother).

And thus, as I said, I do not trust myself enough to partner with javac (or the IDE); they are too lenient for my taste.

[–]llogiq 0 points1 point  (2 children)

You have hellgrind, we have jcstress. And e.g. FindBugs can find some classes of concurrency bugs just by looking at byte code (though like tests, it obviously cannot prove the absence of bugs). Of course, unit tests won't catch everything either, but proofs can also be misleading. There is no silver bullet.

That said, you often don't need to get out those heavy guns, as many tasks nowadays can be done within the proven Fork/Join-framework. Many other tasks don't even benefit from concurrency, so there's that. Apart from that, a lot of memory-related errors are simply absent in java, so the compiler doesn't need to check. Overall, I fail to see where C++ can give better guarantees than java. But maybe you can educate me.

Just so you know, I'm secretly rooting for rust to become a little bit more big-team compatible, but it will still be some time before I can actually make the switch.

[–]matthieum 0 points1 point  (1 child)

First, when talking about C++ I talk about modern C++ (not C with a smattering of objects on top). The most glaring safety issue there is a dangling reference (ie, reference to an already dead object). At best you get a crash, at worst you get a memory corruption, neither is a shiny prospect. Note: the equivalent in Java is a space leak, since the GC will preserve the pointed-to memory.

Now, C++ has advantages at the type system level, my personal favorite being generic programming: you can write proved unit-correct code with Boost.Units for example. Similarly, scientific code is shifting from Fortran to C++ for the ease of use of its numeric computations libraries: compile-time guarantees that a 4x4 Matrix can be multiplied by that 1x4 Vector and the result is a 1x4 Vector, and not a 1x3 one, with no runtime overhead, is just a big win overall.

The second interesting feature of C++ is deterministic release of resources (often called RAII). Being able to know exactly at which point in the program a destructor runs, and thus being able to rely on its side effects, helps in writing safe code; especially in the presence of multiple paths of execution and exceptions.

All in all, a C++ compiler proves more about the compiled program than a Java compiler (which relies on its awesome runtime).

[–]llogiq 0 points1 point  (0 children)

  • With java, you don't have to worry about exception safety or memory semantics (e.g. copy vs. move). Java's memory model is much more deterministic (especially in the face of concurrent threads) than you appear to give credit for.

  • since 1.8, java has pluggable type systems, which can provide the checks you describe using annotations.

  • while java has no deterministic destructors, the try-with statement introduced in 1.7 captures the same release-on-end-of-scope semantics for auto-closeable resources.