https://apple-swift.readthedocs.io/en/latest/ErrorHandlingRa...
Note that people now consider that as a mistake, people prefer having Exceptions be hidden instead of explicit and requiring handling like that.
What the client should know is how an error affects what it is doing, it wants answers to questions like
* Is it likely this error will recur if I retry immediately? in 1 minute? in 1 day?
* What is the scope of this error? Does it affect the entire system? Does it affect a particular database record?
* What do I tell the user?
* What do I tell the system administrator?
Actual improvement in this area won't come from information hiding but it could come out of attaching some kind of ontology to exceptions where exceptions are tagged with information of the above sort, that it is not about having names for them and a hierarchy, but in about having rather arbitrary attributes that help the exception management framework (somewhere high in the call stack!) do the best it can in a bad situation.People want error handling to vanish so that they can follow the "normal" flow, but in fact error handling is one of the critical things code does.
The server doesn't know what you want to do with a 400 but it knows that the problem is with the request for example.
For instance, you can have a SQL error because the syntax of your SQL is wrong. If you're not doing "dynamic SQL" you know this is a programming error (it doesn't matter what input was supplied to the functions.) One common error is "attempted to insert a row with a duplicate key", frequently you want to catch that SQLException and rethrow all the rest.
The ideal SQL library for Java would expose the hierarchy implicit in SQL errors as a class hierarchy.
The issue is that sometimes the solution to the issue would require the client to get up and get out a shovel, and go dig somewhere or something. When the abstractions break down that hard there isn't really a way for the developer of the code to handle that unless they somehow stuff a full blown AGI into their program, and even then it would be a stretch.
0. https://github.com/tgbugs/idlib/blob/master/docs/identifiers...
Note that most newer languages are choosing explicit errors. This includes at least Go, Rust, Swift, Zig, and Odin.
Meyer + functional core nets you a great deal of code with no exception declarations and an easy path for unit tests. If it hurts to do stuff it might not be the language that sucks, it might be you. Pain is information. Adapt.
Often we mix glue code and IO code with our business logic, and that makes for tough testing situations. Especially in languages that allow parallel tests. If you fetch data in one function and act upon it in another, you have an easy imperative code structure that provides most of the benefits of Dependency Injection. Your stack traces are also half as deep, and aren’t crowded with objects calling themselves four times in a row before delegating.
if (this.shouldDoTheThing()) {
this.doTheThing();
}
Importantly with this, structure, growth in complexity of the yes/no decision doesn’t increase the complexity of the action code tests, and growth in glue code (auth headers, talking to multiple backends, etc) doesn’t increase the complexity of the logic tests.A big part of scaling an application is finding ways to make complexity additive or logarithmic, rather than multiplicative. But people miss this because they start off with four tests checking it the wrong way, and it takes four tests to do it the right way. But then later it’s 6 vs 8, and then 8 vs 16, and then it’s straight to the moon after that.
> Remember that Eiffel, unlike other programming languages, is not just a programming language. Instead, it is a full life-cycle framework for software development. As a consequence, learning Eiffel implies learning the Eiffel Method and the Eiffel programming Language. Additionally, the Eiffel development environment EiffelStudio is specifically designed to support the method and language. So having an understanding of the method and language helps you to appreciate the capabilities and behavior of EiffelStudio.
I read "Object-Oriented Software Construction" to do so, but it was long enough ago that I googled "The Eiffel Programming Language" because my brain had substituted that title instead because IMHO, it's more accurate.
The above link should have many current resources for you.
Correction: Some people. Java's checked and unchecked exception approach is quite nice if used judiciously. It certainly beats checking for error after every function call (default: mostly people ignore error codes) and you even get typed errors so you can trivially incorporate exception handling in the conceptual design as a first class design element.
I am frankly not sure how people get confused about "control flow" and exceptions. (In decades of Java programming the only thing that can still cause minor reading/writing nuisance are generic types and type erasure in over elaborate generic code.)
try {
... something ...
} finally {
... clean up ...
}
this (plus try-with-resources) is the genius of exceptions. The tragedy of exceptions in Java is that checked exceptions convert the above to try {
... something ...
} catch(ACheckedExceptionThatHasNothingToDoWithThisCode x) {
throw new SomeOtherCheckedExceptionToPleaseTheCompiler(x)
} finally {
... do what has to be done ...
}
with the variations of throw new AnUncheckedExceptionSoIDontVandalizeMyCodeMore(x)
and catch(...) {
// i forgot to rethrow the exception but at least the compiler isn't complaining
}
as well as // i forgot to add a finally cause because I was writing meaningless catch clauses
As much as I think checked exceptions are a mistake in Java, it is not hard to make up your mind about rethrows and apply them in a checked or unchecked form with little or no thought.The unhappy path that you get for free with exceptions is correct for code with ordinary control flow. Most of the code has no global view of the application and is no position to handle errors. On the other hand, for many simple programs, the correct behavior is "abort the program, clean up resources, display an error message" which a sane exception system gives you for free (except for the finally which cleans up the happy path too)
For a complex control flow there is something high up in the call stack that has global responsibility. Imagine a webcrawler which is coordinating multiple threads that call fetchUrl(url) fetchUrl doesn't need to catch exceptions at all, just clean up with finally. What it may need to do is tag exceptions with contextual information that will help the coordinator make decisions. That webcrawler in particular will deal with intermittent failures all the time and only the coordinator is in a position to decide if it wants to retry and on one schedule.
And this means that your method either always demands to be wrapped in a try-catch, or you migrate to unchecked exceptions.
Rust makes errors a part of the regular type system, so they automatically benefit from all its features.
If you bend over far enough backwards, and squint a bit, you can kind of do that...
interface Runner<E extends Throwable> {
public void run() throws E;
}
class Test {
public static <E extends Throwable, T extends Runner<E>> void test(T runner) throws E {
runner.run();
}
}
It compiles. I haven't tried running it though.If you could it'd mean altering the implementation would automatically alter the API, which would be rather unexpected.
That's why the Java approach is to wrap exceptions and propagate causal chains. The underlying errors thrown by the implementation can change but the advertised exceptions don't, but no information is lost.
Well, Swift is a much newer language than Java, and exceptions in Swift cannot be hidden either. And some people do rather like this.
What people? Please tell me where they’re at so I can tell them they are wrong (lol)
But seriously, I could not disagree more.
https://github.com/paulhoule/pidove
Some people don't like the Lispy signatures so I did start coding up a version with with a fluent interface but didn't quite finish.
Overall I would say the implementation of lambdas and method references in Java 8 was genius, but the stream library was a big mistake. Part of it is that has this cumbersome API that in principle would let it optimize query execution by looking at the pipeline as a whole but doesn't really take advantage of it.
I actually think this would be a fun one. How to make scrambled eggs, but where all failure cases are covered. Would be the "Hal fixes a lightbulb" in prose.
Meanwhile, every time I make scrambled eggs, there is a small but very real chance that my house burns down. And we accept this because to err is human.
That is, you aren't accepting a risk that things will go wrong. You have moved what to do about many exceptions to somewhere else.
Furthermore, handling errors has little to do with where the error is actually caused. In general, you can only do two things with errors: log and kill the operation or retry the operation. Neither of these has anything to do with the leaf function 20 items down in the stack that actually made the network call that failed.
The end result is either completely unreadable or impossible to figure out. "How do I return fallback data for FooBarService.wiggle()" often ends up in digging through (incomplete, outdated) documentation or with code that breaks unexpectedly, sometimes even in production.
Note that Rust has the same issue, any method can panic and allocation may just fail at some point. There are very few ways good ways to handle those problems correctly, which is why this "everything may kill your program" approach is often criticised.
My best example of this is a UI application that I built that had a single try/catch block around the event loop. It just displayed the error message to the user and returned to the event loop. If they tried to save a file to a network and failed, they got the message, and could just hit save again for somewhere else. No other code needed.
In Erlang, "everything may kill your program" is typical method of operation, and there should always be some kind of path to reset your state from known good values.
I disagree, a function that has no possibility of an error is a proper function, and what we need for performance optimized code.
Proper functions by definition are just mappings from a domain to a range. That mapping really shouldn’t be predicated on any other state, so it should never fail if the inputs are valid within the domain.
We need to focus on such functions if we want performance, because we can only achieve too speeds by not worrying about checking the function result for correctness. Given a proper function, we should just be able to compute the result and move on to the next function.
Therefore it’s of great benefit to us (as authors of performant code) to separate our fallible functions from our infallible ones. Keep the fallible ones iutside of hot loops, only infallible ones inside, and that’s a recipe for mechanical sympathy of the sort that results in great performance.
>A function that has no possibility of error is so uninteresting that focusing on that is the wrong thing.
I spend a lot of time debugging errors in code that has 0% chance of failing. It tends to involve a lot of matrix math. This isn't something you can say is universally true especially given all the hype around AI now.
This is highly presumptuous. I have written many programs that did not need to handle errors locally, and so exception handlers were only at the very top level (or, actually, just below the top-level usually - but the point is that there were generally few and I had flexibility to decide where to put them). Perhaps you and I write very different applications. But the fact remains that the "almost always" in your statement doesn't hold.
Alternatively line of reasoning: if this was always true then there would be little point to Rust's ? as it would be so rarely used.
Yes, but “if you can” does a lot of heavy lifting here. In most cases you can’t, and this is when Rust’s ? is used.
No, absolutely not! You only care about errors where you can retry/ignore or log and terminate so you only have try/catch in those areas. So maybe one or two.
Yours is an approach which is likely to ensure your code is discarded and has to be rewritten relatively quickly.