8 ms·
One of the biggest flaws in C#, in my experience, is lack of checked exceptions. As an example, I wrote some very good code, carefully tested it, made it work f
by breadwinner 3y ago
One of the biggest flaws in C#, in my experience, is lack of checked exceptions. As an example, I wrote some very good code, carefully tested it, made it work flawlessly, then suddenly it started crashing. What happened? Someone made a change in a function I was calling, and it started throwing a new exception. This would have caused a compile error in Java, not a crash.
More on checked vs unchecked exceptions here:
https://forum.dlang.org/thread/hxhjcchsulqejwxywfbn@forum.dlang.org https://forum.dlang.org/thread/hxhjcchsulqejwxywfbn@forum.dl...
- skissane 3y ago> Someone made a change in a function I was calling, and it started throwing a new exception. This would have caused a compile error in Java, not a crash. Not necessarily. If it started throwing a new RuntimeException, it wouldn’t have. There are also sneaky ways to throw checked exceptions without declaring them, for example using Lombok’s @SneakyThrows annotation
- breadwinner 3y agoCertainly you can intentionally break it, but then that's on you.
- skissane 3y agoA person can also accidentally break it - by adding some new code which contains a bug which causes an unchecked exception or error to be thrown (e.g. NullPointerException, ArrayIndexOutOfBoundsException, StackOverflowError, etc). Checked exceptions do nothing to protect against those kinds of mistakes, which in my personal experience are vastly more common than whatever mistakes for which they may provide some protection
- deleted 3y ago[deleted]
- kaba0 3y agoEverything can fail at every time — that’s part of the art of programming to discern which error conditions are meaningful to be included in the signature, and which are not.
- skissane 3y ago> Everything can fail at every time — that’s part of the art of programming to discern which error conditions are meaningful to be included in the signature, and which are not. But that's a major flaw with checked exceptions – very often, whether an error is recoverable or not depends, not on the API itself, rather on how it is used. Yet checked exceptions force the API designer to make that decision while designing the API, when they can only guess at how it will be used. A good example of this is FileNotFoundException – whether that is a recoverable error which ought to be handled, or whether there is nothing better to do than crash, depends on what the file is. If we are implementing a File Open dialog box in a GUI app – okay, we better catch the FileNotFoundException and display an error box, not just crash. But, suppose I am writing a micro-service, and the first thing it does on startup is read its config file, and the config file isn't there: is there any point in trying to handle that exception, or should it just crash? Obviously the designers of Java's file IO classes had the first scenario in mind more than the second, but it is an inherent flaw of checked exceptions that they forced them to make this decision at all.
- h4x0rr 3y agoChecked exceptions just always felt annoying to work with. Imo Rust's Result type is more versatile
- wvenable 3y agoChecked exceptions are just another return type with a weird syntax. It's not that Rust's result type is more versatile, it's just less syntax to accomplish what works out to be the same thing.
- jcparkyn 3y agoThe difference is more than just syntax, at least in the case of Java and Rust. The core problem with checked exceptions in Java (as demonstrated in most of the examples from TFA) is that they don't compose properly with generic functions like map. Checked exceptions exist in parallel to the type system, and other parts of the language don't have the capacity to deal with them. On the other hand, a Result type makes errors part of the type system, and all other code can work with them "by default".
- wvenable 3y agoI agree. Checked exceptions are just a worse version of Result types. I did not intend to make them sound better than they are. Unchecked exceptions, however, are a different beast entirely. They're not just checked exceptions without the checking; they fundamentally change how you code and how you think about error handling.
- ben0x539 3y agoWhoa, I've been having opinions about error handling and result types and checked exceptions for like years and I never considered that Java couldn't be "properly" polymorphic over checked exception types. Thanks for pointing that out.
- int_19h 3y agoThat's a specific choice that Java designers made, though, not something inherent to checked exceptions in principle. During Project Lambda, one of the proposals - Neal Gafter's - actually had a full-fledged generic exception specification facility that allowed you to define HOFs along the lines of "I throw everything X does, and also Y". They killed it because it was "too complicated".
- taeric 3y agoI mean, maybe? Fun degenerate cases to consider: Someone throws a Environment.Exit(0) into a random library you are using, instant pain. Someone throws an infinite loop into a library you are using, similar instant pain. There is no magic language trick that can prevent you from having to rerun all tests for your software if you update a dependency. Pretty much period. (I say this as someone that isn't really opposed to checked exceptions.)
- breadwinner 3y agoIf someone added an extra parameter to a function you're calling, when would you want to be alerted about it? At compile time? Or when you rerun all the tests? Hopefully, your answer is compile time. If so can you now understand why you would want to be alerted about a new exception getting thrown at compile-time as well?
- mrkeen 3y ago> If so can you now understand why you would want to be alerted about a new exception getting thrown at compile-time as well? The library code can already throw anything. OutOfHeap, over/underflow, div0, stackoverflow, threadinterrupted. The caller already knows the function can throw, and documenting one more flavour of throw doesn't tell the caller anything.
- twelve40 3y ago> documenting one more flavour of throw doesn't tell the caller anything but it does, in cases when there are errors that can or should be retried. Like a whitelist of documented cases where recovery and retry are possible, and of course all this infinite runtime stuff that can happen unexpectedly, for which there is no immediate solution.
- taeric 3y agoI'd like to think that, as an industry, we would move to libraries being foundational code, such that they don't change in fundamental ways. If a parameter is added, it should be done in a backwards compatible way, such that I don't have to know about it. The old behavior would continue to work. As such, any breakage from calling would be, by definition, a bug. And no, we have not found a way to prevent bugs.
- jabiko 3y agoLets be honest. The more likely thing is that either the coworker would use an unchecked exception or that they would change the callsite to: try { theUpdatedFunction(); } catch (MyNewCheckedException e) { logger.warn("Whoopsie doopsie", e) throw new SomeUncheckedException("Something failed, idk", e) } Which really is a zero sum game. The code still breaks the same way, but the checked exception gets eventually wrapped in an unchecked one. We still would have the situation that someone changed the behavior of the function in a way that is incompatible with your usage. That bug should have been caught by tests, code reviews and good communication.
- __jem 3y agoOr, worse, you're reviewing a large diff, see the logger statement but don't notice that the junior whose code you're reviewing forgot to rethrow. Now you've accidentally entered uncharted territory where way more scarier things can happen to your application than just crashing. Of course this shouldn't happen, but it does.
- twelve40 3y agothat can be somewhat addressed with decent unit test coverage
- doliveira 3y agoIf the Future, Executor, Optional, Streams, etc played nice with checked exceptions it would probably be different. Right now at some point you do have to wrap it into an unchecked exception.
- throwaway2037 3y agoI agree. It is weird that for interface Runnable there isn't a ThrowingRunnable. I always create them in my projects and make my callbacks use them along with ThrowingConsumer and ThrowingProvider.
- phillipcarter 3y agoYep. This is similar to a viewpoint by Anders Hejlsberg and many on the C# team at the time: https://www.artima.com/articles/the-trouble-with-checked-exceptions https://www.artima.com/articles/the-trouble-with-checked-exc... > You see programmers picking up new APIs that have all these throws clauses, and then you see how convoluted their code gets, and you realize the checked exceptions aren't helping them any. And he goes on to elaborate on how this gets more complicated when versioning is introduced into the mix.
- oh_sigh 3y agoYour code might have been very good, but it wasn't future proof. It sounds like your code would have been fine if you locked down the libraries you were calling to a fixed version or something.
- __jem 3y agoOn the other hand, in lots of application code, there's often not much to do other than crash or abort the request, and this can often be safer than trying to proceed along code paths that are almost always under-tested. We've all seen Java code where a junior has decided to swallow an error without much thought. This obviously isn't true for every use case, but failing fast is often a pretty good policy.
- nradov 3y agoThe trouble with checked exceptions is that they prevent you from easily extending classes or implementing interfaces that you don't control. Your new class might need to throw a checked exception not included in the method signature. So then you have to resort to hacks like wrapping the new checked exception inside a runtime exception.
- breadwinner 3y agoNot true. Exception hierarchies solve this problem.
- nradov 3y agoNope. Exception hierarchies don't even come close to solving that problem because the superclass or interface author usually didn't anticipate your need and define the method signature in a way that would be useful with exception hierarchies.
- breadwinner 3y agoSo you're inheriting a class not within your own application, but written outside of your application. Composition might work better in this case.
- nradov 3y agoNope. Composition doesn't help when you have to implement an existing interface in order to make some API work.
- int_19h 3y agoIf you're implementing an existing interface for that reason, it's because something else is going to be calling you. In which case throwing an exception that they don't expect is probably a bad idea, and relying on it just flowing through their code back to you is basically relying on implementation details in many cases.
- sonicgear1 3y agoYou should catch any non specific exception in the last catch block, just in case. Thought that was the standard. Java gets very messy in mixing checked and unchecked exceptions, I've seen a lot of devs just ignore the unchecked ones.
- int_19h 3y agoAnd do what with it? If you don't know what it really is, the best thing is to let things fail fast and the standard logging to kick in and record all the details of the crash.
- yardstick 3y agoDepends on what you are doing. If I’m processing a monthly report for millions of customers, I don’t want to abort processing after running into one problem customer. I want to continue to process the rest of them, and log the problem customers exception for troubleshooting and analysis offline.
- nradov 3y agoDepending on what actually happened, that could be disastrous and result in data corruption or account reconciliation failures or worse. It often would be safer to go ahead and fail even in that circumstance.
- breadwinner 3y agoSo you mean, catch the root Exception class. Pretty much everyone agrees that's a bad idea, C# and Java both advice against that. But in the case of C# you have to do it, to avoid crashing all the time, because there's no reliable way to determine what exceptions can be expected. In Java the compiler will tell you what exceptions can be expected, but in C# you have to rely on documentation which is not reliable.
- yardstick 3y agoNot just Exception. Throwable. I’ve seen too much code were some random problem in an Error - OutOfMemoryError for example when processing too much data (Eg call data records, payment records, analytic records, whatever). If it’s a batch processing job, you don’t want this problem for this specific entity causing the rest of your reports not to be sent. I’ve also seen stupid things like RPC libraries silently swallow Errors and not report them properly to the caller, so we end up wrapping all RPC server endpoint methods in a try-catch-Throwable just so we can see the problem and log it.
- wvenable 3y ago> One of the biggest flaws in C#, in my experience, is lack of checked exceptions. I couldn't disagree more. Checked exceptions in Java have ruined a generation of programmers. The truth is, under checked exceptions, to satisfy the compiler the function that you called would declare that it throws a SomeModuleException and the programmer who wrote that function would put all his code in try/catch block that catches all errors and rethrows a SomeModuleException with the real exception passed as the cause. Checked exceptions just don't work.
- jitix 3y agoYou’re both right and wrong. Checked exceptions slow down development, make code ugly and in my theoretical opinion are an anti-pattern that should never be used. However in small to mid sized enterprise software companies with average developer talent it’s important to keep boundaries (and blame) clear. In the scenario in question, the CTO will blame OP and make them work the weekend to diagnose/fix it, so wrapping the other exception and throwing their own SomeModuleException will cover their ass. Java is popular for all the wrong reasons. Believe it or not there are hundreds of such companies in the US alone heavily using Java in this manner and then there is the whole offshore development segment.
- adamwk 3y agoBut even with unchecked exceptions, the exception value or description will still show up in the logs. I don’t understand how checked exceptions save you from a tyrannical CTO
- jitix 3y agoGood point, but in my experience siloed teams (often if different continents) usually don’t standardize on error messages and codes, and a vague message like “Invalid name” doesn’t indicate which layer the message is coming from. From operations’ point of view the bug will be pinned on the owner of the service that is returning the JSON to the client/app and it’s up to them to trace it down the layers. A hierarchy of wrapped exceptions helps with that. Kind of like saying “I can’t do it because XYZ didn’t do what they were supposed to do”. So low key corporate blame game in code. I know that the usual HN crowd doesn’t work at/know about companies that follow this, but this pattern is way more prevalent in the broader “IT” industry esp among offshore centres.
- joiqj 3y agoOne of the reasons I hate exceptions overall. Someone may add new ones, they break the control flow, they bubble up all over the place, etc etc. They are basically indomitable.
- 29athrowaway 3y agoIn Rust instead of throwing, you have results that can be either successful or errors. And it is all type safe and fast.