Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.dl...



Lets 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.


Yep. 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-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.


Hejlsberg is a terrific compiler writer. Turbo Pascal was awesome! He is not a good language designer, however. The exception mess in C# is the proof. See my comment at the root of this thread.


Yes, not a good language designer responsible for (checks notes) one of the most-used and most-loved programming languages on the planet.


Do you mean Turbo Pascal? He didn't design it, Niklaus Wirth did. Or do you mean C#? James Gosling designed most of that (C# got its start by copying 90% of Java). Do you mean TypeScript? That's mostly JavaScript.


You're certainly entitled to your opinions.


Those are facts


I would say he is one of the better language designers. Unlike many language designed by theorists and academics, he understands how the engineers are using the language and how a feature is used/abused and develops around it.


I doubt you can evaluate man's skill through a single project. It's not nice to use ad hominem to advertise your own comment.


Their comment was the one that this thread is bases on. They're merely pointing out that the original comment already gave an example why its a mess.

I disagree with their point though, in java the lib would've probably just converted the exception to a runtime exception so the API doesn't change...


> That bug should have been caught by tests, code reviews and good communication.

There's a reason we have compile-time checks. If you think compile-time checks should be replaced with additional tests, code reviews and good communication, then you want a scripting language, not a compiled language.

You do not wrap checked exceptions in an unchecked one... unless you are a really bad Java programmer.


> unless you are a really bad Java programmer.

Here's the thing. You've just described the vast majority of programmers - bad. They're really fucking bad. And language constructs that lead to bad programmers doing stupid things are, unfortunately making things worse for everyone.


I've given a lot of time and benefit of the doubt to 'testing replaces static analysis' and not only do I know this is not true, but I'm also pretty sure that testing is fundamentally broken. It can't fix anything because it can't even fix itself.

Maybe a new testing paradigm will fix it, I certainly keep an eye out for them, but nothing so far. Property based testing is better, but mostly it just reminds me that we had Design By Contract 30 years ago.


> You do not wrap checked exceptions in an unchecked one... unless you are a really bad Java programmer.

I disagree -- this is the correct thing to do if you believe it is not possible for the checked exception to occur. (Catching it is wrong -- what would you do to correct something which you believe not to be possible? Forcing the caller to handle it is wrong -- if you don't know what to do with it, they sure won't!) Wrapping checked as unchecked encodes your belief that should it occur, it is a logic error, akin to out-of-bounds array access or null pointer dereference.

(Of course, swallowing expected exceptions one is simply too lazy to do anything about is poor practice! Not disagreeing with that.)


> if you don't know what to do with it, they sure won't!

Actually no. For instance, the caller at some point up the stack may know if something is worth retrying.


Then you should unwind your state correctly and rethrow as a checked exception stating as much. If there levels up the stack I see a random unexpected "IO Exception" from your library I have no idea whether you are still in a valid state to retry. If I instead see a "Foolib Request Aborted: reason IO Exception" I know you've written logic to handle that case, and I know that retrying is appropriate.


> I disagree -- this is the correct thing to do if you believe it is not possible for the checked exception to occur.

If it is not possible to occur, then it should not be part of the API.

The only time I rethrow a checked exception as an unchecked exception is when the code is still under construction. The default of the eclipse code generator is to log and ignore caught transaction. I think wrapping into an unchecked one is the better default behavior for incomplete code under a "fail fast" policy.


> If it is not possible to occur, then it should not be part of the API.

Ah, but what if it can occur, just never with what you pass in? Suppose a function is documented to throw some checked exception if some parameter is a negative number, but you pass in a positive literal/constant? In such a situation, the checked exception will never occur! With Rust, for example, this is easily done with an `unwrap()` (and, possibly, a comment) to assert said belief, but with checked exceptions, there's no way to force the compiler to squash the required check.


Example: validation of serialized data followed by deserialization. Deserialization properly should throw a checked exception, since invalid serialized data is very much a thing. But in this case the serialized data is known to be correct (because it passed validation). The checked exception will never occur, and were it to, there's nothing we could do about it, because it reflects a logic error, same as out of bounds array access.

The algebraic data type equivalent of this shows up all the time in functional code -- unwrapping a result type you know can't be Error/None/etc. because of logic. You don't rewrap it and force the caller to deal with a case which logically cannot occur; instead you throw an unchecked exception.


stream.filter(p) requires wrapping checked exceptions in p, or altering the language so that they’re no longer checked. Same with Runnable.


You could also alter the langage woth for exceptions transparency (swift has something like that with “rethrows“).

I don’t think that’s close to sufficient to making checked exceptions work tho, let alone good.


or you start doing monads instead, where p operates on a monad (aka, the Optional type).

Unfortunately, this ends up propagating out and your entire code base needs to also do this. Tho i reckon it will make your code better in the future, for a short term/temporary pain converting/adding breaking changes etc.


Yeah, switching to Scala or Kotlin would be the shortest path to do that.


If this were 100% true, everyone would want a language for formal verification (Idris, Coq, etc). Short of those lofty heights we can quibble over the varying strictness of Haskell vs Java vs C and so on.


A good programmer knows the limits of the type system. They lean on that knowledge to determine which tests to write.


> You do not wrap checked exceptions in an unchecked one... unless you are a really bad Java programmer.

That's not a given. It's perfectly fine to handle a checked exception via some custom runtime. The nice part is your your code base was given a chance to handle the key exceptions of this particular api all from your ide without you having to refer to documentation etc.


i do...if i ever get an IOException all i have to do is end the app, no need to deal with it in any way different than an unchecked exception

https://phauer.com/2015/checked-exceptions-are-evil/


A better example for IOException is network errors. If your socket dies, your code should attempt to reopen it! It should report to the user if it can't!

Moreover, it's good that code nearest the site where the exception is thrown handles the error, as only it has context for what's going on at the time. Code further up the stack won't have any clue what this random IOException might relate to.

If you're confident the IOExceptions can't occur under normal conditions -- say, you know the file has correct permissions, isn't being concurrently modified, etc. -- then, encode this belief by catching the IOException near to its origin and rethrowing as an unchecked exception.

This same pattern shows up even in languages without exceptions. In C -- always check errno; don't try to catch SIGSEGV or SIGABRT; raise SIGABRT if errno is something you don't plan to handle. In F# -- you're forced to match Ok/Error; don't try to catch exceptions; raise an exception if you don't expect Error.


But with abstractions you can have many more functions not handling the error before you get to the actual context.

Is it worth it to thread the IOException through the network layer, then the HTTP-client library, then then business serialisation up to the actual context?

I don't want a dozen checked exceptions. (and with this approach socket-limitations and connection loss should get a different exception.) I also don't want catch and rethrow. Unchecked+checked+docs+crashes are a measured approach.


Check the root message of this thread... if you use unchecked exceptions you either crash or you swallow all exceptions. Both are evil, and checked exceptions are the solution.


That's only possible if your code is 100% correct.

Sometimes, your logic is flawed. A condition occurs which you erroneously deduced not to be possible.

Unchecked exceptions are the necessary manifestation of these unforeseen errors. Catching them is pointless, what will you do with them? Dynamically fix the logic of your program? What can be the reasonable response to "index out of bounds", try a different index? [1]

Depending on the domain it may be appropriate to convert them indiscriminately to checked exceptions at module boundaries (e.g. requests within a server) -- but within said module it remains pointless to catch them. (This is a form of swallowing.)

In other domains, crashing is the correct behavior. The code cannot proceed correctly, it must abort.

Checked exceptions are appropriate only when the exception can be anticipated and thus planned for, ideally by code closest to where it is thrown.

[1] Actually there was a paper a long time ago that monkey-patching C code to return fake "null" data in response to out-of-bounds memory accesses actually resulted in the intended (i.e. correct) behavior in the majority of cases. But I digress.


Some errors are recoverable, some are not. When recoverable errors are possible you want to know what those errors are. If you don't then you will crash when you could have recovered. And that's the reason for writing down the list of possible exceptions where recovery and retry are possible (aka checked exceptions).

When recovery should not be attempted (example: "index out of bounds") then you don't want to declare or catch the exception, and that's when you use a subclass of RuntimeException in Java.


If 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.


I 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.


Let's be really honest

  try {
    theUpdatedFunction();
  } catch (MyNewCheckedException e) {
    logger.warn("Whoopsie doopsie", e);
    throw new SomeUncheckedException(e.message);
  }
There's a special place in Hell for these people, but at least they'll get to see all of their friends again.


Oooo, lookit you working with professionals; I've seen waaaaaaay too many instances of this shit

  try {
    somethingImportant();
  } catch (Exception e) {
    // can't happen
  }
IJ finally started chirping about this other anti-pattern

  try {
    doit();
  } catch (Exception e) {
    System.err.println("something didn't work");
    // failure to reference "e" is now a yellowbox
  }
I have to constantly ask for them to either log it, or rename the variable to `ignored` to get IJ and future readers on the same page that it was intentionally swallowed


>The code still breaks the same way

That's not a valid argument. This is an explicit decision, so they reap what they sowed.

One could just as well make the same argument for Optionals, that you can just do:

  let val = foo.unwrap();


Or, 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.


that can be somewhat addressed with decent unit test coverage


> That bug should have been caught by tests, code reviews and good communication

This is such an unproductive cop out response. The problem could be completely solved by functional error handling, which leverages the compiler to force you to handle exactly the errors than can happen, no more, no less.


> throw new SomeUncheckedException("Something failed, idk", e)

Who does that? It looks like an anti-pattern.


> 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.


From a type theory perspective, an exception is another type of return value that a function can have, and not documenting those in the function signature is absurd.

I have seen libraries that make network calls that do the following:

* Throw an exception on certain types of network errors * Return an HTTP error code for other types of network errors * Return an object with error set to true and an error string for other types of network errors

NONE of those was documented, it is crazy. This actually bite me in prod when an undocumented exception that we hadn't seen in over a year of uptime was finally thrown. I had to look at network logs to try and figure out why it was being thrown (no checked exceptions in JS!), which itself is absurd.


I think the issue is with checked exceptions as a solution to how to declare this signature, or at least with how they are designed in Java: if you add a new exception to the signature, every downstream call site needs to change, recursively.

This means it is impossible to add new exceptions to library methods without breakage, and even in your own code it may mean hundreds of changes throughout your code to add a new exception type.

Rusts solution seems to have improved this conundrum, where you can describe how to map the new error into an existing hierarchy in a single or a few places instead of at every call site


> This means it is impossible to add new exceptions to library methods without breakage

That’s a feature, not a bug. If it wouldn’t, an exception of that kind could bubble up at a place you didn’t expect.

Also, it doesn’t require that many changes, it only has to be changed up to the point where you intend handling it.

I don’t see how rust would be immune to that.

Though it is true that polymorphism with respect to checked exceptions would be great. The new generation of languages might have that (e.g. Koka)


> If it wouldn’t, an exception of that kind could bubble up at a place you didn’t expect.

You should always be able to handle an unknown exception. Sure you can't do much about it, but it shouldn't be a big deal. An exception only occurs if the code can't continue as expected.

Libraries are supposed to be a point of abstraction. They should be allowed to change their implementation fundamentally as long as they continue to respect the same interface. Exceptions should not be part of the interface explicitly because are they implementation-detail related.


If you don't control the code where the superclass or interface is defined then you're stuck. You can't change that code even if it would be only a tiny edit. Checked exceptions are absolutely not a "feature".


From a type theory perspective if you want to track exceptions as return values you want a composable container class (best case, a full monad) so that you aren't writing a lot of ad hoc code every time to handle them.

That's a big problem with Java's checked exceptions, it's not very composable and flow control can just stack up into deeper and deeper "waterfalls" of code rather than simpler pattern matching. (To be fair that's a big problem with exceptions in general as "flow control", they aren't very composable.)

Something like an Either monad in a language with sum types can be a great way to describe errors/exceptions usefully as return types. From a type theory perspective, checked exceptions can be seen as a hack for a language that doesn't support sum types and doesn't have great native monad binding.


> and not documenting those in the function signature is absurd

Exceptions can be thrown from anywhere. That's the documentation.


That's how you do it in C#... See InnerException [1]. What a good programmer would do is to throw a new exception, and fix the callers to handle the new error.

https://learn.microsoft.com/en-us/dotnet/api/system.exceptio...


That assumes you have control over the callers. If you're writing a library, you don't. Checked assumptions are incompatible with polymorphism and even just basic abstraction afforded by functions. One can't arbitrarily change an implementation without changing the signature and breaking all the callers.

The vast majority of cases, direct callers can't properly the handle the new error anyway. Callers either have to eat that exception, convert it (thus defeating the purpose of checked exceptions), or change their signature. Good programmers will change the signature and now every one of the callers of those methods have do the same thing. Ad infinitum.


You can have multiple implementations of database drivers, but they all throw a subclass of SQLException. So yes, polymorphism is certainly possible. And when you get a SQLException (for example SQLIntegrityConstraintViolationException), you don't want to just crash, or display whatever error message the database gave you... you want to, at the very least, provide guidance to the user on what corrective action to take. And in some cases the program can automatically take corrective action. And that's the reason for writing down a list of possible exceptions where recovery and retry are possible (aka checked exceptions).


That's an extremely contrived example. What if now you want to do a NoSQL solution? Or a flat file?

But really the problem is actually far worse than that. Checked exceptions are brittle under any kind of implementation change. If you have a function that calculates a rate just using arithmetic. But tomorrow it loads a flat file. And next week it uses SQL. And in a month it calls a web service. As a consumer, this is none of your concern -- that's the whole point of abstraction. It's even worse when dynamic (polymorhphism) or functional (external code calls you).

What's the recovery from a SQLException anyway? How does that give you enough detail to do anything? You say SQLIntegrityConstraintViolationException but that's not the same type. Why does it matter that you declared it a SQLException over just Exception in that case?


You’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.


I put Java's popularity down to good support for Corporate-Oriented Programming ;)


Coproporate-Oriented would be a better description


But 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


Good 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.


On 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.


Checked exceptions just always felt annoying to work with. Imo Rust's Result type is more versatile


Checked 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.


The 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".


That'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".


I 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.


Whoa, 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.


You can use checked exceptions equivalently to Result types in streams e.g. https://github.com/unruly/control/blob/master/src/test/java/...


I 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.)


> 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.

Of course, but having a checked exception (even better, having the errors be part of the return type via Result<T, E> or Option<T>) solved an entire class of problem. Doesn't mean that there aren't others of course. But surely this is a win?

>inb4 it makes the code very complex with nested return type Result<Result<Result<T, FileNotFoundError>,ReadError>, ParseError>

Then it was just hidden from you before. The complexity had always been there, it just never occurred to you that it can happen.


> But surely this is a win?

Not when it creates more issues than it solves, which checked exceptions do.

Checked exceptions are an entire side channel to the type system which breaks any sort of composition or genericity.

Maybe this is solvable, but Java seriously poisoned that well because its implementation is so shit, and if you’re looking for this static safety, first the rest of a Java-style type system does not justify it (there’s so many low hanging fruits), and second a result-style things will already give you the same benefits in a form which is known to work.


Ish? It is all too often that, at the point where you would be forced to catch the exception, the best path forward is to signal it to the user and see if they have a plan. Unfortunately, the only mechanisms we allow for that in modern systems is to unwind the entire stack to get to the call that came from a user. And whether done with chained return values, or with exceptions, the code will get to be a mess.

Signals that don't unwind can have their own problems, of course. I don't mean that as a silver bullet. But all too often the exceptions and error conditions that we use to teach these ideas are far more difficult because of our insistence on unwinding the stack. Neither return values nor exceptions change that.


If 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?


I'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.


> 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.


> 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.


The 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.


Not true. Exception hierarchies solve this problem.


Nope. 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.


So you're inheriting a class not within your own application, but written outside of your application. Composition might work better in this case.


Nope. Composition doesn't help when you have to implement an existing interface in order to make some API work.


If 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.


No, that's not how it usually works in practice. Typically where this becomes a problem is in using an existing library as a middle layer in your application code. So you could catch your own additional exceptions, but the interface method signatures don't allow for that. Hence the need for ugly hacks like wrapping the checked exception in a runtime exception, then catching that and extracting the original exception. A real mess, but still better than the alternative of forking and modifying the library.


That is exactly my point - when you have a middle layer written by someone else sandwiched between parts of your code, assuming that any random exception you can throw will flow through (or, for that matter, that there even is a "through" in many cases - e.g. if the library makes some part async) - is relying on implementation details of said code.


You 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.


So 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.


Not 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.


You can’t necessarily recover from errors.


And 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.


Depends 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.


Depending 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.


> 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


Certainly you can intentionally break it, but then that's on you.


A 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


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.


> 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.


Your 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.


In Rust instead of throwing, you have results that can be either successful or errors. And it is all type safe and fast.


One 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.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: