[HN Gopher] Unchecked Java: Say goodbye to checked exceptions
___________________________________________________________________
Unchecked Java: Say goodbye to checked exceptions
Author : rogerkeays
Score : 74 points
Date : 2023-07-13 13:25 UTC (9 hours ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| invalidname wrote:
| FYI this too is already supported by Manifold...
| jsight wrote:
| That is referenced in the readme. :) TBH, I hadn't seen either
| one before today, and they both look really nice.
|
| The type-safe JSON feature in Manifold could be incredibly
| useful.
| invalidname wrote:
| Yes. It's fantastic see:
| https://debugagent.com/series/manifold
| dgellow wrote:
| What is manifold in this context?
| invalidname wrote:
| See my reply to jsight...
| darklycan51 wrote:
| Certified Try catch moment
| nepthar wrote:
| Reading through the comments, I realize this may be a minority
| view - but I like coding for the happy path and letting
| exceptional states crash. I find languages like go a bit harder
| to parse quickly because I always have to "unwrap" the happy path
| from all of the mixed in error handling.
|
| I'm sure I'd get used to it eventually, but I like that unchecked
| exceptions in Java are now an option!
| raincole wrote:
| > I like coding for the happy path and letting exceptional
| states crash.
|
| Of course everyone like coding like this. No one likes to code
| error handling code.
|
| Just like no one likes to code tests and documentations.
|
| It turns out not every important thing is joyful.
| the_gipsy wrote:
| In practice there is a lot of paths that you don't want to
| "crash" on, but try the next thing etc.
| asddubs wrote:
| I'm fine with doing this on purpose, but without a system like
| checked exceptions, you do it without even really realizing
| you're doing it. Checked exceptions point out the errors and
| then let you decide whether it's something you should handle or
| let it crash. It makes for more stable software.
| code_runner wrote:
| its only an illusion of stability. so many things can go
| wrong outside of exceptions and all it does is add mandatory
| lines of code (probably rethrowing as a runtime exception) to
| every single consumer. It pollutes everything it touches.
| ziml77 wrote:
| If you actually let it crash that's fine, but a lot of people
| just toss a catch (Exception ex) in there because they don't
| know what exceptions will happen and don't want errors that
| aren't actually a big deal to bring the system down. But the
| issue with catching everything, even if you log before
| continuing, is that the process may be in a bad state and
| you're just continuing with it like that. I got to see a couple
| database tables get absolutely mangled by a process that had an
| exception that was caught and logged. That was a pain to clean
| up since doing nothing more than restoring a backup would have
| lost the entire day's worth of business operations in that
| system.
| edpichler wrote:
| This dependency removes one of the best features of Java, which
| make others think about what happens if things go wrong.
| code_runner wrote:
| only the things that someone acknowledges may go wrong
| dtech wrote:
| Most other languages agree that checked exceptions are not good
| by not having them.
|
| As for alternatives, Try/Result and similar monads have decent
| adoption even in Java, but personally I quite like the Kotlin
| philosophy [1] to not have generic error containers and either
| use runtime exceptions for errors that cannot be reasonably
| handled by the caller and encapsulate failures in the return type
| if they can.
|
| [1]
| https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/...
| clownvorld wrote:
| The Trouble with Checked Exceptions A Conversation with Anders
| Hejlsberg, Part II
|
| https://www.artima.com/articles/the-trouble-with-checked-exc...
| taftster wrote:
| This is an insightful interview, thank you for the link. I'm
| well read up on the topic, but this interview was still great
| and is a good perspective on the checked/unchecked debate.
|
| The fact that Java has introduced UncheckedIOException, in my
| opinion, shows how _some_ people in the Java community have
| come to believe that checked exceptions were a mistake
| (understanding that lambda forced the issue). There 's probably
| not too much to be easily done at this point, but consideration
| for changing checked exceptions in the JDK to extend
| RuntimeException sure would be interesting.
| rogerkeays wrote:
| I think this project shows how trivial it is to convert
| checked exception errors to compiler warnings.
|
| https://github.com/rogerkeays/unchecked/blob/f22c8cde3557de0.
| ..
|
| No need to change the type hierarchy. Just make it a compiler
| option.
| dejj wrote:
| Lombok's @SneakyThrows
|
| https://projectlombok.org/features/SneakyThrows
| phoe-krk wrote:
| _> Unchecked does not make any changes to your bytecode. This is
| possible because the JVM does not know about checked exceptions.
| It 's been the compiler holding you back all this time._
|
| TIL!
| asddubs wrote:
| I actually quite like checked exceptions, and miss them in other
| languages. My biggest gripe with exceptions is that a few calls
| deep, you can no longer tell whether calling something might
| throw or not, and what type of exception it might potentially
| throw.
| josephcsible wrote:
| I like checked exceptions in principle too, but Java's
| implementation of them is so bad that I hate them there. The
| first major mistake is that it doesn't support exception
| polymorphism (e.g., I wish you could pass a comparator to
| Arrays.sort that throws checked exceptions, and have that call
| to Arrays.sort itself then throw the same checked exceptions),
| and the second is that the standard library makes a bunch of
| exceptions checked that should be unchecked (e.g., IOException
| from close()).
| sam_lowry_ wrote:
| Which is now commonly wrapped into UncheckedIOException.
| _old_dude_ wrote:
| Checked exceptions are a leaky concept in Java. The Java type
| system has no union types so you can not have a generics method
| that abstract more than one exception.
|
| That's why Stream::map can not capture the checked exceptions
| properly.
| pharmakom wrote:
| Yes! The problem with the Java implementation of checked
| exceptions is the inheritance hierarchy, which encourages
| catching the broadest possible error.
| code_runner wrote:
| To the credit of the die-hard Java community, you all really seem
| to love and support even the most horrific syntax and awful
| language features. You love the pain. The rest of us are here for
| you... I promise software can be fun.
|
| Checked exceptions are far and away the worst part of java and
| I'm glad that no other language I've _personally_ encountered
| have them!
|
| On a more real note, I work at a shop with a fair amount of java
| now and checked exceptions are definitely my biggest complaint. I
| hope we adopt this, I'm sure we won't, but I'm glad to see a
| little movement on what I feel is the crusty status quo in the
| java world.
| rho4 wrote:
| Still hoping that Java will support such a choice (compiler
| option) out of the box in the future.
|
| Also, without IDE support such a plugin will never gain a lot of
| traction.
| zeedude wrote:
| Manifold provides this feature and supports IntelliJ IDEA and
| Android Studio.
|
| https://github.com/manifold-systems/manifold/tree/master/man...
| rho4 wrote:
| I am using Eclipse. But will definitely keep this in mind,
| should I every switch to IntelliJ.
| lucasyvas wrote:
| This is a mistake - checked exceptions and functional equivalents
| are the clear path forward.
| breadwinner wrote:
| 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...
| skissane wrote:
| > 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 wrote:
| Certainly you can intentionally break it, but then that's on
| you.
| skissane wrote:
| 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
| oh_sigh wrote:
| 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.
| __jem wrote:
| 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.
| taeric wrote:
| 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.)
| nradov wrote:
| 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.
| h4x0rr wrote:
| Checked exceptions just always felt annoying to work with. Imo
| Rust's Result type is more versatile
| jabiko wrote:
| 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.
| __jem wrote:
| 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.
| flerchin wrote:
| Neat. Reminds me of lombok in that it really only affects
| compile-time and makes for cleaner code in a way that many (but
| not all) developers would want.
| jameslars wrote:
| Ugh Lombok! Literally everything it does is replaced by any
| competent IDE with auto-generated methods, with the added
| benefit of not requiring special build handling steps because
| the library can't play by the normal annotation processing
| rules.
|
| There was maybe a time Lombok made sense. It does not anymore.
| Death to Lombok.
| nabogh wrote:
| Disagree. Just because the ide wrote a bunch of boilerplate
| for me at some point doesn't mean I can know the boilerplate
| is unchanged without reading a bunch of getters and setters.
| The mental burden of tiny classes is so much nicer to read.
| never_inline wrote:
| That's usually one time concern. Much better than having
| boilerplate lying around IMO.
| alanfranz wrote:
| I don't want to remove all checked exceptions in random code,
| btw. Even though they're not common, it can be bad if they're
| unhandled in some cases. How can I know beforehand?
|
| I'd like something to soften some of them, probably in a
| configurable way, in some builtin interfaces btw. E.g. many
| IOExceptions should really be unchecked.
| macpete42 wrote:
| catch them at the source and return sealed classes (used like sum
| types) instead
| watwut wrote:
| Nothing better then having to guess whether the method may throw
| an exception or not.
| dtech wrote:
| You already have to guess. All method calls van throw Runtime
| exceptions.
| code_runner wrote:
| nothing better than someone assuming a method can only throw a
| specific type of exception or won't throw at all.
| nnnnico wrote:
| Allowing unchecked exceptions in languages without explicit error
| handling or the return of error values is a mistake IMO! Makes it
| impossible to call a function safely
| RGBCube wrote:
| Exactly, C++ exceptions are horrible, you never know what
| throws what. Java made C++ exceptions better by making them
| explicit, so you always know what throws. Then Kotlin came and
| made everything a unusable mess (don't get me wrong, I love
| Kotlin, just hate that it doesn't have explicit exceptions).
|
| I honestly think the best error handling strategy is employed
| by Zig, then Rust. they're very explicit while not getting in
| the way, you always know what throws what and what doesn't.
|
| Rust has a little issue though, which is that people can make
| their functions return `Result<T, Box<dyn Error>>` which makes
| handling the returned error difficult, Zig does not have any of
| that.
| menaerus wrote:
| It is true that C++ exceptions are not checked and are rarely
| part of the interface signature but C++ exceptions are not a
| tool for general error handling in the context you're
| comparing them against Java, Zig and Rust. Java uses
| exceptions for control flow while C++ doesn't. Zig and Rust
| don't have exceptions at all. What Zig and Rust have as error
| handling mechanisms C++ has them too. C++ exceptions are of a
| literal meaning - exceptional cases which you don't expect to
| happen and you usually don't know how to recover from. In
| this case propagating the exception up to the thread entry
| point and doing whatever in that case (logging, terminating,
| restarting, etc.) is vastly better than language forcing me
| to check for each possible exception all the way through the
| whole function callstack - it's useless.
| AnimalMuppet wrote:
| In what way are Java exceptions part of flow control and
| C++ exceptions aren't?
| SomeRndName11 wrote:
| I think the way Go does error handling is the best
| compromise.
| vips7L wrote:
| It's honestly the worst compromise. You can trivially
| ignore any error in Go and they don't contain contextual
| information about the call stack.
| the_gipsy wrote:
| It's a successful compromise, but probably not the best. It
| has a huge amount of little issues that simply aren't there
| with FP error handling.
| ziml77 wrote:
| It's one of the worst I've seen. It adds so much cognitive
| overhead to reading the code and makes it more annoying to
| write. Every function call that can fail is followed by 3
| lines to check for and return the error. The actual logic
| quickly gets lost in there.
|
| If it wasn't for the error handling, I would probably use
| Go for some of my projects. It compiles to a native, self-
| contained binary and still has the convenience of a garbage
| collector, and of course has plenty of libs available due
| to popularity.
| kaba0 wrote:
| Go's solution is like people couldn't decide on if they
| should turn left, or turn right to avoid the cliff, so to
| make everyone happy they drove straight into it.
|
| It is literally the compiler enforced shitty error handling
| from C's errno that is not even a sum type.
| [deleted]
| ziml77 wrote:
| As far as I know Zig errors can't carry any data along. It's
| nice having more details available about the failure.
|
| Take for example if you call to an OS API and it returns an
| error that wasn't documented to be returned from that
| function. You can't return that code up the callstack using
| Zig's error mechanism. Instead there's functions such as
| unexpectedErrno and unexpectedError. Those call the
| appropriate method to get a string representation of the
| error and call std.debug.print to display it, then they just
| return error.Unexpected.
|
| That means that the caller doesn't have any control over how
| the error is displayed. Meanwhile in Rust, you can add an
| UnexpectedError variant to your enum and let it carry an
| error code with it. The caller can then display that error
| however they want.
|
| I don't hate Zig's error handling by any means, but
| personally I think Rust does it better. I'm happy to see that
| C++ seems to be going with the Rust way by adding a Result-
| like type std::expected.
| RGBCube wrote:
| Both have their issues, but I think the perfect error
| handling mechanism would be something like the Zig way with
| additional data.
| nnnnico wrote:
| (To clarify, i'm against exception based error handling, but
| removing checked exceptions in languages without other
| mechanisms of explicit error handling makes things more
| brittle. For example in kotlin, where some Java practices to
| throw all over the place are still in use, unchecked exceptions
| makes things worse. You wont know if a function will throw
| unless you inspect the code or trust it's documentation)
| ars wrote:
| Java just needs one small change to fix exceptions.
|
| Instead of the exception _type_ being checked or unchecked, the
| _throw_ should specify checked or unchecked.
|
| So for example the first time the exception happens it can
| throw a checked exception for the caller to deal with.
|
| If the caller doesn't deal with it it can simply throw its way
| all the way to the top without every single function needing to
| declare they handle it.
| kaba0 wrote:
| The proper solution is effectful type systems that are
| polymorphic to effects, like exceptions.
| tasubotadas wrote:
| The entire community disagrees with you[1]
|
| >Makes it impossible to call a function safely ??
| catch(Exception e) anyone?
|
| [1] https://literatejava.com/exceptions/checked-exceptions-
| javas...
| ivanche wrote:
| A lot of people, probably. But definitely not the entire
| community [0].
|
| [0] https://www.yegor256.com/2015/07/28/checked-vs-unchecked-
| exc...
| _old_dude_ wrote:
| In Java, no method call is safe (Haskell/Rust safe), the
| langage will hapilly throws a null pointer exception or an out
| of memory error.
|
| But I don't think C#, Kotlin or Scala are less safe than Java
| even if they do not have the concept of checked exceptions.
| rogerkeays wrote:
| Good point...
| kaba0 wrote:
| Haskell and Rust can also throw exception/panic respectively
| in any call, ever.
| _old_dude_ wrote:
| Technically true, but Haskell forces you to use the IO
| monad and you can not really recover from a panic in Rust,
| the thread is dead.
| kaba0 wrote:
| head []
|
| No monad, nothing, just an exception.
| rho4 wrote:
| I would argue the opposite: Junior and senior developers alike
| catch and swallow or log checked exceptions all over the place,
| making it impossible to know if your method call was successful
| or not.
| Supermancho wrote:
| At some point you want to swallow all checked exceptions at
| some point, because letting the application leak stack traces
| to the client is generally a bad idea.
| misja111 wrote:
| This is why you should have code reviews. Letting juniors
| submit code without reviewing it is a recipe for disaster, no
| matter which language you are programming in. And if it's a
| senior, well someone should have a word with him ..
| _ZeD_ wrote:
| please, for the love of what's good in the world... NO. checked
| exceptions ARE A FEATURE, a CORE ONE.
| lesuorac wrote:
| There's a reason using streams with checked exceptions is so
| painful; checked exceptions are going the way of the dodo.
| dtech wrote:
| They are so bad even Java stdlib stopped using them for the
| most part. Nearly all Java 8+ APIs like streams use unchecked
| exceptions.
| bedobi wrote:
| Exception based error handling is so bad and unsafe that adopting
| functional error handling with Either, Try etc as implemented by
| functional addon libraries for many languages, while not yet
| common, in time it will become the new default even in OO
| languages. (just like it's been the default in functional
| languages for decades)
|
| Functional error handling types are much simpler, safer and more
| powerful.
|
| Simpler because they don't rely on dedicated syntax- they're just
| regular objects no different to any other object.
|
| Safer because unlike exceptions, they force callers to handle all
| potential outcomes, but no more. (no risk of ignoring errors and
| no risk of catching a higher level of error than desired,
| ubiquitous bugs in exception based error handling)
|
| Powerful because they support map, flatmap, applicative etc,
| making it easy to eg chain multiple computations together in
| desired ways, which is unwieldy and bug prone when using
| exceptions.
|
| > What is wrong about dedicated syntax
|
| It adds complexity to the language! It could be that, when
| learning Java, Kotlin and any other language, we learn that
| methods return what they say they do... and that's that. No weird
| dedicated syntax and magic, special treatment for returning
| anything other than the happy path, and the HUGE complexity that
| comes with it, eg the dedicated syntax itself and how it behaves,
| differences between checked and unchecked exceptions, hierarchies
| of exceptions etc etc.
|
| > Exceptions are easier
|
| But that's the point, they're not.
|
| Exceptions based error handling is unnecessary, hugely complex,
| doesn't compose at all, obfuscates or straight up hides what can
| go wrong with any given call, so leads to countless trivially
| preventable bugs... I could go on. And after decades of use,
| there's still no consensus about what exceptions should be or how
| they should be used. Exceptions are a failed experiment and I
| have no doubt that in ten years, Java, Kotlin and many other
| languages will acknowledge as much and move away from it the same
| way Joda Time outcompeted and replaced the horrible Java date and
| time library.
| misja111 wrote:
| Exception based error handling is unsafe when they are
| unchecked exceptions. Checked exceptions however are as safe as
| Either, Try, Monads, Applicatives or whatever. You are forced
| to declare them in your method signature, the caller is forced
| to either handle them or rethrow them + declare them as well.
| And I guess this is precisely why so many developers hate them;
| they don't like the extra work they have to do to catch all
| those edge conditions. This is why we see so many empty catch
| blocks or upcasting to Exception or even Throwable; it is
| laziness.
|
| I would also argue that checked exceptions are no more complex
| than Eithers, Try or Applicatives. Actually passing Eithers or
| Applicatives around everywhere can easily clutter your code as
| well, IMO it can be worse than checked Exceptions.
| renewiltord wrote:
| Ultimately, core language features that make things ergonomic
| enough for lazy developers to get it right will result in
| better software.
|
| And ones that are so unergonomic that only industrious
| developers will get them right will result in worse software.
|
| Modern languages allow these to be zero cost abstractions so
| there is little tradeoff.
| misja111 wrote:
| I have been coding in Scala/ Haskell for the last 10 years,
| before that 15 years in Java. What I'm seeing is that there
| are as many lazy developers in FP as there are in Java,
| maybe even more. And despite all the nice safeguards that
| FP provides, there are still plenty of ways for lazy devs
| to work around them.
|
| For instance the IO monad, which is used everywhere
| Scala/Cats. It can contain a result or an error. If you
| don't feel like checking for an error after you called some
| method, you can just pass it up and return the IO from your
| method. Does that sound familiar? It behaves just like a
| checked exception, the only difference is that methods
| don't need to declare any error or exception in their IO
| signature.
| skissane wrote:
| > And I guess this is precisely why so many developers hate
| them; they don't like the extra work they have to do to catch
| all those edge conditions
|
| I hate checked exceptions when they force me to handle an
| exception which I know is impossible given the arguments
| passed to the method. For example, some older Java APIs take
| an encoding name and force you to handle the checked
| UnsupportedEncodingException - even when the encoding name
| was something like "US-ASCII" or "UTF-8" which is required to
| exist by the Java standard, and if somehow they didn't there
| is often no possible error recovery than just crashing. This
| has been fixed in newer versions by introducing new APIs
| which throw an unchecked exception instead, and also by
| introducing constant objects for the standard charsets
| bedobi wrote:
| > Checked exceptions however are as safe
|
| No. If I add a checked UserNotFound exception to a getUser db
| call, you can bet someone higher up the stack will do try
| catch Exception e, so now they're catching OutOfMemory and
| who knows what else.
| kaba0 wrote:
| As opposed to force unwrapping a Result type? Also,
| OutOfMemory is an error, exceptions won't catch it.
| DarkNova6 wrote:
| What about DivisionByZero, NumberFormatexception,
| ArrayStoreException?
|
| There are countless examples of rare but legit non-
| obscure use-cases. And even if your code is fine, you
| can't expect the same for the libraries you are using.
|
| And some exceptions make frequent non-happy paths more
| visible. Most of all IOException, because IO can _always_
| fail for all the wrong reasons (because the failing of
| this exception is outside of the JVM's influence, it is
| rightfully a checked exception). And often you simply
| don't want to do the error handling at call-site but
| propagate to the code which is controlling the use-case.
| kaba0 wrote:
| Was that comment meant for me?
| vips7L wrote:
| OutOfMemoryError isn't an exception so they would not be
| catching it.
| bedobi wrote:
| whatever, the point is exception allows and encourages
| people to catch less and more than they should
| vips7L wrote:
| Sometimes top level code needs to do this. It's not
| always wrong. It's not always black and white in
| programming.
| bedobi wrote:
| > Sometimes top level code needs to do this
|
| come on, it's not as if anyone disagrees with that, but
| that's extremely, extremely rare, overwhelmingly, callers
| are just dealing things like with UserNotFoundException,
| NullPointerException, what have you, and there's no
| reason why the compiler should happily let you catch
| Exception (or nothing) when it could just be giving you
| an honest object back
| wewtyflakes wrote:
| It has not been extremely/overwhelmingly rare in my
| experience. Further still, within the context of Java,
| exceptions are objects.
| jbverschoor wrote:
| Then that person should get a Java 101 class. Or is this
| the current "staff software engineer" level of skills?
| bedobi wrote:
| This is the most common reply I see whenever anyone
| proposes a safer, better way of coding, and it's not a
| good one. "Just get better" like oh ok except that in the
| real world people are gonna people and even the best
| programmers in the world make mistakes and do dumb, lazy
| shit. Our tools should be designed such that the safest,
| most correct way to do anything has the path of least the
| resistance. Not happily allow you to ignore exceptions,
| or catch less or more exceptions than required.
| Functional error handling corrects this, exceptions do
| not. Anyway, it's clear we're not going to agree, and you
| win, since so far the industry is still stubbornly
| clinging to exceptions, despite them being a failed
| feature in every language they're in.
| twelve40 wrote:
| > you can bet someone higher up the stack will do try catch
| Exception e
|
| But that's laziness on the caller's part. If I offer a
| method but the caller decides to do reckless lazy crap with
| it, there are many different ways to get there in any
| language. I typically call those out (Exception e) at code
| reviews.
| bedobi wrote:
| This is the most common reply I see whenever anyone
| proposes a safer, better way of coding, and it's not a
| good one
| esafak wrote:
| ...because doing the right thing should be convenient,
| for its own good.
| kaba0 wrote:
| Checked exceptions are exactly analogous of Result/Either
| types. They are just built into the language with syntactic
| sugar, automatically unwrap by default (the most common
| operation), can be handled on as narrow or wide scope as needed
| (try-catch blocks), does the correct thing by default (bubbling
| up), and _stores stack traces_!
|
| In my book, if anything, they are much much better!
| Unfortunately they don't have a flawless implementation, but
| hopefully languages with first-class effects will change that.
| titzer wrote:
| > Checked exceptions are exactly analogous of Result/Either
| types.
|
| No, they aren't.
|
| They are not compositional. You may want to write `f(g())`
| but there's no way to write the parameter type of `f` to make
| this work (in Java). That's because checked exceptions are an
| "effect" that would require extending the Java type system.
| kaba0 wrote:
| Read my last paragraph.
| the_gipsy wrote:
| So they are better... in a theoretical implementation.
| [deleted]
| The_Colonel wrote:
| > Safer because unlike exceptions, they force callers to handle
| all potential outcomes, but no more.
|
| Which is bad. In 99% of cases I have no specific error handling
| for the given problem. Just let the process crash or be handled
| by application server / framework.
|
| Representing these kinds of error conditions in the code which
| I have no interest in handling is just noise.
| sam_lowry_ wrote:
| Especially because Kubernetes and the like work as if the
| application must die anyway. Pretty much like PHP CGI
| invocations always worked since 1998 or so.
| bedobi wrote:
| The problem with that is you lose all ability to understand
| or control what a given endpoint will return in any given
| situation
|
| > I have no specific error handling for the given problem
|
| then pass it on and decide what to do with it at the system
| boundary
|
| in the db:
|
| fun getUser(uuid: UUID) : Either<Error, User>
|
| middle layers: pass around the either, you can map, flatmap
| etc on it to chain it with other computations there
|
| then in the resource layer
|
| return user .fold({ error -> when(error) { is DbError ->
| HttpResponse.InternalServerError() is UserNotFoundError ->
| HttpResponse.NotFound } }, { user -> HttpResponse.ok(user) })
|
| Then, at every layer, each method explicitly says in the
| method signature what it returns. There's no need to look
| around each and every line of every method in every layer, or
| in the framework, or some global exception handler, to figure
| out what will happen. Developers can tell from a glance at
| the resource method what the endpoint will return for each
| outcome, in context.
|
| Things like being unable to communicate with the DB or not
| finding a user are not exceptional, they're entirely
| expectable when you're calling a db to look for a user and
| should be modeled accordingly.
| The_Colonel wrote:
| > The problem with that is you lose all ability to
| understand or control what a given endpoint will return in
| any given situation
|
| Why? In the simplest (but pretty common) case it's:
|
| - successful response
|
| - generic error message
|
| > then pass it on and decide what to do with it at the
| system boundary
|
| Yes, but if in 99% of cases I only pass it on, it's just
| visual noise. Noise you become blind to, and it loses
| meaning.
|
| > Developers can tell from a glance at the resource method
| what the endpoint will return for each outcome, in context.
|
| They can't really because you end up with some very generic
| error type anyway. Any typical service can have IOError,
| DBConnectionError, SQLError, OutOfMemoryError,
| StackOverflowError plus many other application specific
| errors. You end up with bulk of your methods returning
| Either<Error, Something> which is then meaningless.
| bedobi wrote:
| You're describing the problems with exception, not using
| Either... When using Either, you never fold on those
| errors individually... Anyway, it's clear we're not going
| to agree, and you win, since so far the industry is still
| stubbornly clinging to exceptions, despite them being a
| failed feature in every language they're in.
| WirelessGigabit wrote:
| It feels like you're trying to use Exceptions as a way to
| steer your logic, otherwise why would you need to know
| why an operation failed to such detail?
|
| Your controller method cannot act differently on a
| DBConnectionerror or OutOfMemory error.
|
| Not to mention that exceptions cause developers to use
| them as control flow mechanisms.
|
| For example searching a user by id. If the database
| returns 0 records, is that reason to throw an exception?
| Well, doing result[0] results in IndexOutOfBounds due to
| result being [].
|
| But the reality is that the user not being there isn't
| exceptional. Typos are common. By using Result<T, E> or
| Either you enforce the developers to think more about
| their flow. One can write the method like this:
| fn find_by_id(id: usize) ->Result<UserResult, Error> -> {
| let raw_db_result = db.search("SELECT id, first_name,
| last_name FROM user WHERE id = ?", id)?;
| match raw_db_result { None =>
| Ok(UserResult::NotFound), Some(r) => {
| let only_user = r.ensureOneResult()?;
| let user = mapDBUserToUser(only_user);
| Ok(UserResult::User(user)) } }
| }
|
| What about Error? Reality is that I don't really care.
| Error doesn't contain anything that is actionable. Either
| the whole chain succeeds and returns a valid result or
| the Error. The caller wants to find a user by id. There
| is one, or there isn't. All the rest is just errors that
| they'll pass on too. And in the end they get logged and
| result into Error 500.
|
| A 404 is actually a valid result.
|
| Now, if I were to use a throw new UserNotFoundException()
| for no user found you end up with generic try catches
| catching too much. And now someone needs to go and take
| it all apart to identify that single Exception that they
| want to deal with separately.
|
| Whereas if I want to add a state in my enum the callers
| _MUST_ update their code due to how Rust works.
| smallerfish wrote:
| Or Kotlin's approach: fun
| getUser(userId: UUID): User?
|
| You cannot treat this result value as a `User` in code
| that calls this; though, once you null check it in your
| service layer, you can pass it on as `User` in the not-
| null branch. Null is nothing to be afraid of if the
| language forces you to declare nullability and deal with
| it.
| andrekandre wrote:
| this is a very common in obj-c too
|
| on the other hand, depending on what your trying to do
| you might want to provide more context about what
| happened to the user/programmer
|
| in swift you can change a throwing function to a nullable
| with `try?` so even if `getUser()` throws, you can keep
| it simple if thats what is appropriate
| guard let user = try? getUser(id: someUUID) else {
| return "user not found" }
|
| as an aside, swift "throws" exceptions but these are just
| sugar for returning basically an Result<T,E> and compose
| much better than traditional stack unwinding exceptions
| imo
| The_Colonel wrote:
| > otherwise why would you need to know why an operation
| failed to such detail?
|
| I'm not defining the errors like DBConnectionError or
| OutOfMemoryError - it's the framework/platform which
| defines them and throws/returns them.
|
| > But the reality is that the user not being there isn't
| exceptional.
|
| That depends. In some contexts it is not exceptional
| (getting user by ID given as an argument to webservice),
| in that case using Maybe type is great. In other contexts
| it is very much exceptional (e.g. signed JWT refers to a
| non-existing user) and throwing Exception makes more
| sense.
|
| > What about Error? Reality is that I don't really care.
| Error doesn't contain anything that is actionable. Either
| the whole chain succeeds and returns a valid result or
| the Error. The caller wants to find a user by id. There
| is one, or there isn't. All the rest is just errors that
| they'll pass on too. And in the end they get logged and
| result into Error 500.
|
| Which is the same as exception. But now you have this
| visual noise of something you claim you don't care about.
| An unchecked exception makes this irrelevant noise go
| away.
|
| > Now, if I were to use a throw new
| UserNotFoundException() for no user found you end up with
| generic try catches catching too much. And now someone
| needs to go and take it all apart to identify that single
| Exception that they want to deal with separately.
| try { ... } catch
| (UserNotFoundException e) { // handle ...
| }
|
| I'm catching exactly the exception I want. Where I'm
| catching too much? Where do I need to take it apart?
|
| (This particular example of exception usage is bad,
| though, as it smells of exception control flow. Here
| using Maybe type would be better)
|
| > Whereas if I want to add a state in my enum the callers
| _MUST_ update their code due to how Rust works.
|
| Which is good for some cases where the enum describes
| "business" cases.
|
| But it is pretty bad for truly exceptional cases (which
| are unlikely to be handled anyway). Library adding a new
| exceptional case will break its clients, which seems like
| a bad trade-off (again, for truly exceptional cases).
| bedobi wrote:
| WirelessGigabit gets it. That last fact is huge -
| functional error handling forces callers to handle (or
| pass on) exactly what can go wrong, no more, no less.
| (unlike exceptions)
| titzer wrote:
| Situations vary a lot. In general there's no guarantee that a
| far outer scope is going to know how to deal with errors from
| deep inside some nested calls except for some very general
| logic, like failing a whole operation.
| kaba0 wrote:
| Even more commonly, there is no meaningful error handling
| to be done at a layer above the call.
| spion wrote:
| Exceptions are not unsafe.
|
| That said, not modelling them in the type system is a mistake.
| But the model has to be useful - knowing what functions can or
| cannot throw is useful, knowing what they throw, less so.
|
| (They are safe because of try-finally / try-with-resources)
| bedobi wrote:
| They are unsafe because they invariably result in people
| either ignoring them or catching more than they should, or
| less than they should, and the compiler happily lets you do
| that, EVEN when you're using checked exceptions.
| AnimalMuppet wrote:
| How will the compiler let you catch less than you should
| when using checked exceptions?
| asddubs wrote:
| You mean something like C++ Optional, right? Those are nice, if
| the language supports generics.
| zeedude wrote:
| Manifold already provides this feature:
|
| https://github.com/manifold-systems/manifold/tree/master/man...
|
| With an article having the same name as your post:
|
| https://github.com/manifold-systems/manifold/blob/master/doc...
| galaxyLogic wrote:
| This seems like a major improvement for Java readability as shown
| by the examples.
|
| What I don't like is the examples of having to use Maven or
| Gradle to install it. Why does that have to be so verbose, and
| something written in another language?
| rogerkeays wrote:
| Ha, yeh. At first I had the command line examples first, but
| everyone was going on and on about maven and stuff, so I
| figured that's what the audience wants.
|
| [EDIT] I moved the command line stuff back to the top of the
| README. XML makes my eyes bleed too...
| [deleted]
| edpichler wrote:
| Later you detect an error, more expensive it is to fix it.
| ars wrote:
| That's not really true.
|
| Vast majority of the time either the caller will fix it, or it
| simply does not need to be fixed at all it's simply passed all
| the way up to the top.
___________________________________________________________________
(page generated 2023-07-13 23:01 UTC)