It's not a normal thing for code that needs that record that wasn't found.
> They literally tell you nothing and there's no way to solve them without catching/rescuing them. A null value, a plain "error" object, or an error argument in a callback would have been sufficient. If I need an exception to be raised for this kind of thing, I'll do it myself.
They tell you lots: you asked for something your code path wanted and your request couldn't be satisfied. Also, you've been helpfully kicked onto the alternative execution path to handle that situation.
Exceptions are a hell of a lot better than littering your code with null checks or error code checks, especially when you forget one and get a null pointer error or your code wanders away from the root cause and fails later.
No offense, but I don't know where that idea comes from. Systems are checking for records all the time in ways where the absence of data doesn't necessitate throwing an exception.
For instance, a page, user, or piece of media on a website may have existed at one point but was since deleted, but still has a permalink floating around the net. Is it useful, in the case that someone clicks on such a link, to throw an exception when I can instead choose to render different page content when a record wasn't found? I'll never need a stack trace for that.
> They tell you lots: you asked for something your code path wanted and your request couldn't be satisfied. Also, you've been helpfully kicked onto the alternative execution path to handle that situation.
Why would I want a code path for expected behavior? I agree for cases like a failed connection, where the system is actually broken, but there isn't anything fundamentally broken about data absence.
> Also, you've been helpfully kicked onto the alternative execution path to handle that situation.
That's not only presumptuous, but if I wanted that to happen, I can do so myself.
> Exceptions are a hell of a lot better than littering your code with null checks or error code checks, especially when you forget one and get a null pointer error.
Hmmm...
const record = store.findRecord(params.id);
if (record) {
render('show-record');
} else {
render('record-not-found');
}
or... try {
const record = store.findRecord(params.id);
render('show-record');
} catch(err) {
if (err.name === 'RECORD_NOT_FOUND') {
render('record-not-found');
} else {
throw err;
}
}
I'll let people decide which one is better. I personally prefer the first one. item = db.findItem(id)
assert(NotFoundError, item != null)
render('show-item', item)
upstream middleware: try {
res = await downstream()
} catch(e) {
if (e is NotFoundError)
render('not-found')
else
render('internal-error')
}
you can do this with other patterns for upstreaming errors like Result + Rust's handy short-circuiting, but your example doesn't demonstrate much. what about internal errors like your store throwing a database error, your first example doesn't even have a store capable of erroring? your comparison incomplete. and surely you don't do all error checking at every callsite no matter which pattern you use.Which is essentially GOTO 404
A horrible idea that I see often in clever frameworks disobeying encapsulation and reasonable control flow in favour of magic
On the one hand, it's terribly convenient when you're writing the code.
On the other hand, as clon says, it breaks encapsulation. Not necessarily, but I've rarely seen these style of Web frameworks used in a way that doesn't result in network communication concerns wormholing their way through the entire codebase. And it sometimes does do weird things with control flow. Again, not necessarily but in practice. For example, JAX-RS frameworks do the exception mapping off in some magic location that's outside the part of the call stack that you directly control, meaning that customizing the logic requires hacking some fairly complex special-purpose mechanisms into the framework.
I suspect that there's a way to achieve convenience without relying on spooky action at a distance. But one could be forgiven for looking at the way popular frameworks on popular platforms work and concluding that cleanliness must be the price of convenience.
I agree that a database API should not itself throw an error if an empty result set is found. It may throw an error if there is an SQL error, though, and really should. When one gets an empty result set where this should not happen, for instance because a row was just inserted and one is in a transaction, the user code could throw an exception instead.
Making exceptions optional is something that I have indeed done in the past because some uses of the code could benefit from exceptions while other uses did not so much.
Some things are essential to the page we are rendering, and if they are missing we should 404.
Some things are ancillary, and if they are missing we should render a stub in their place. E.g., saying that a comment is from "deleted user".
Exceptions are a good fit for the former, where we want to unify every failure. They are a poor choice for the other case, where we want to treat each failure special.
Which case a given fetch represents is a choice that should be driven by the fetcher.
They have a type, make it say MissingUserException and the special place can handle it in a special way. Ensure it is a subclass of some general exception type you also handle elsewhere and you're good.
Often people throw generic exceptions instead of using the type system.
I like it. I wish that the concept of exceptions was closer to Panic because then we wouldn't be doing nearly as much flow control.
In the Rails world (where I live) looking top down, exceptions are handled via flow control or 500 pages. Anything that doesn't result in a 500 is effectively flow control.
While I agree with Rust's semantics, it generally isn't that bad to work around and knowing which is what.
That's JavaScript, right? That's probably not the best language to use to judge the concept of exceptions.
It's cleaner in languages like Java:
try {
Record record = store.findRecord(params.id);
render(record);
} catch(RecordNotFoundException e) {
render('record-not-found');
} catch (Exception e) {
render('unexpected-error');
}My response to that would be that if it isn't a normal thing for your code to encounter a record not being found, then you should handle that state in whatever you see fit, whether that's by passing down a value or object reflecting that error state or throwing your own exception(yes, I do think such a thing can be appropriate). I still don't favor the latter, but at least with throwing your own exception your application can catch it at a higher level and will be less likely to mistake it for another exception of the same class that should be handled differently. If you are using an SDK that throws an error for a "record not found" type situation, it would certainly work in your favor if your application really does benefit from it, but I think a lot of code doesn't actually call for such a thing.
But my point is that there's no reason I can think of that an exception can't be totally optional in these circumstances. I don't think it should be assumed that everyone wants an exception. There's a reason why we use conditional syntax like if-statements because, if used try-catch syntax as control flow for everything, the flow would be bass ackwards and hard to understand. In my opinion, the example situation I described is on the borderlands where an exception might make sense but probably doesn't actually make things better.
Every project is different, and people might choose to use exceptions a lot, which is fine for them. I've personally been better off not relying on the exception-based control flow taught to me when I learned Ruby, but that doesn't mean I would come into a project and remove everyone's exception handling.
And this is the problem with error checks: They're purely optional as far as the language is concerned, so we keep getting them wrong or forgetting things, even when we're being careful.
And you are the one who knows best if something is an expected failure, so wrap that up in a fetch_one_or_none() — as a matter of fact, most DB interfaces I work with have an API of the kind.
Basically, exceptions are a decent way to keep what might need to be a separate control flow, well, separate. And you can choose at any time to "join them in". As a concept, they are quite like interrupts on the very low (hardware) level, so you can learn only one "tool" to deal with them.
(And yes, I can trivially find cases where I want errors to bubble up when a record is not found, eg. a required configuration record or similar)
The first example: - no stack trace - loosing the specifics of error to troubleshoot.
The second example: - explicitly discards the stack trace - explicitly discards the error specifics.
Hence, if this is really what you want. fine, but in a lot of cases you do want to see stack traces and the exact reasons (and meta data why an error was thrown).
> It's not a normal thing for code that needs that record that wasn't found.
This is something of a religious dispute and there are arguments both ways.
But I think it’s important to note that an exception is vastly more expensive than a simple function call or return value. So I’d say you need a really good reason to use exceptions.
I agree with the GP that in general, “lookup failed” is not an exceptional error. If it always succeeds, why are you looking up something in the first place?
I agree with the original poster that exceptions do have their place, even though they’re expensive.
If you have the primary key of a database object that you know to be correct, and you try to retrieve that record but fail, that might legitimately be an exception (unless having records deleted from under you is a common occurrence in your app).
Kotlin gets this right for the most part (although I think it would be better if it had checked exceptions).
When a new exception object is created, the slowest operation is filling information about the stack trace. It is possible to override Exception's fillInStackTrace() with an empty implementation. In this case throwing exceptions with new exception objects, is only slightly slower than using one exception object (17ms vs 15ms for 1M throws).
Deeper stacks make difference even bigger. Adding 100 nested invocations to the stack slow downs the classic approach (a new exception object created just before invocation) to 9 seconds, while alternative approaches are not affected.
That said, I agree exceptions should be reserved for exceptional circumstances. Something like checking if a record exists or not should probably use something like Optional instead, unless you are in a situation where a record "should" exist but doesn't (which is itself an exceptional case).
Maybe not Python? But Python is slow regardless.
But C++ does not provide a stack trace.
Which makes exception in C++ a very, very bad idea!
My reaction to 'the test fails because map::at threw an exception': stupid STL!!
A core dump would be so much easier to analyze.. Gdb's 'catch thow' is wonderful (well, it is after I fixed the part of our codebase which use exceptions as a control flow mechanism)
> A core dump would be so much easier to analyze..
Uncaught exceptions call terminate(), which by default calls abort(), which generates a core dump. So if you want a core dump, just avoid catching the exception.
Also works in C.
I’d simply add that most people greatly underestimate how expensive exceptions are, even in C++.
In many popular programming languages, the way to check whether some value has a certain property is with an “if” statement. It would be very odd to replace every code path inside an “if” statement with exception control flow simply because that code path “needs” some condition to be met for it it to execute.
[0] https://www2.lib.uchicago.edu/keith/ocaml-class/pattern-matc...
[1] https://dev.to/babak/exhaustive-type-checking-with-typescrip...
https://www.youtube.com/watch?v=AnZ0uTOerUI Unconditional Code by Michael Feathers
One solution to this problem is discussed toward the middle: giving data instead of asking for data avoids error conditions. Doing so pushes use of the data farther down the call tree and pulls acquisition up near the top, where we are closer to the user and thus able to more clearly decide what if anything to do about these corner cases.
That's basically limitation of the language. Null checks and result checking can basically be abstracted away with non-nullable types, option types, and result types and bind.
You only need to do match or check right at the edge, so they're not everywhere.
Like, handling a checked exception (Java) and a Google-cpp StatusOr<T> or a rust Result<T> provide extraordinary similar code. Unchecked exceptions provide a bit of extra dynamism, and languages without either are anti-user.
obj = session.query(MyObject).one_or_none() # no exception if missing
obj = session.query(MyObject).one() # exception if missing
Why call it then?
It's a fine approach for prototyping but we should be highly suspicious of it for code people rely upon to work. As complexity approaches infinity, the odds of the system being "happy path"-aligned approach zero.
Network-relevant programming (such as web UIs), in particular, really highlights this fact (since any given network request is always allowed to fail, and "user closes their laptop or mobile device, moves somewhere else, and resumes work on an entirely different physical network" has gone from a rare occurrence to an extremely common one in the past two decades).
This is why API generally have checks that allow developers to avoid that failure if the failure is expected (eg Record.exists). What you are suggesting is simply a reverse order (check after vs check before). I think check before leads to a much cleaner API than one that dumps a null or failure code instead.
The world is full of bugs of the form "does this file exist? no? OK, open it for writing", which is exploitable by dropping a symlink in there between the check and the open.
The classic primitive CMPXCHG works like this too; you use it as you would a "store", but one that might fail if another thread has changed a value in the meantime.
It’s good practice to design APIs to make race conditions less likely, by explicitly not splitting operations across multiple calls.
Separating “exists” and “get” into separate calls is a disaster.
I guess databases and file systems are different because concurrent access is the norm, not the exception, and APIs should be designed accordingly. But for most of your in-memory structures, you probably don't want them dealing with concurrency because they aren't going to be able do that very well anyways (instead, deal with concurrency outside of the data structure and have them throw when inconsistencies from bad concurrency policies arise).
(Okay, probably not twice as slow, since the cache will be hot, but still.)
Or if you "know" it's there call the version that throws an exception if it isn't.
Then again, maybe there are cars built that way. Oy vey.
I would never expect that a call to `Record.find(...)` would always find records, and that the failure to find a record means that something is broken. The absence of data is usually all you need to handle control flow, and treating absence as a failure is presumptuous of API engineers. Having to account for a specific exception just adds more lines of code that could be easily avoided.
A better use of an exception would be for a system failure like this: "Connection Failed"
IMHO, that's being a bit too ideological. It's better to think about it in terms of how you'd want to respond to the condition, and pick your tool appropriately.
For instance, if I'm asking for a record by ID, then a record not found exception is a good fit. I was probably going to do something with the record, but now I can't, and the exception idiom tool the condition to be dealt with where it happened even if I forget.
If I'm asking for a set of records matching a criteria, and nothing matches, then a record not found exception is a poor fit. Code that can deal with a populated list usually can deal with an empty list just fine, and using an exception just adds friction.
If I'm asking for a set of records matching a criteria, and the database is is down, then an empty list is a poor fit. I have information about the connection error I need to communicate, so it's better to use an exception.
This isn't something you can make universal statements about, because it depends on context.
If the program knows it just put or saw the record there, is it strange to assume it's still there? If the program completely controls the database and something else in it implies the record is there, is it still strange to assume?
Sometimes broken assumptions can only signify that something has gone very wrong, and it's not worth handling them at a fine-grained level. That leads to the "gotta catch em all" antipattern the article mentions.
This just isn't true. It depends on what the record is, and what state you're in (aka: CONTEXT).
incredibly clear example: 1. User logs in 2. User record no longer exists 3. Exception
That's not a recoverable situation, and the client should see that as an unexpected error.
> This just isn't true. It depends on what the record is, and what state you're in (aka: CONTEXT).
These two things mean the same thing. It's in the context - > it is not an inherent property.
It's not an inherent property - > it's not always present.
Some conclude therefore it shouldn't be an exception by default.
>Anyone who expects a database to always have records and that the absence of a record is an "exception" has a very strange way of thinking.
But pedantics aside, my point stands. There are absolutely contexts and code paths where a missing record is basically unrecoverable. Why bother returning an error to the local calling context when you know that code doesn't make sense anymore and can't recover? A jump to a new code path is the right call, and exceptions do that (with a lot of nice trace information about why we ended up on that new code path to boot).
Doesn't mean a missing record should always result in an exception, but it's certainly not a "very strange way of thinking".
I don't consider looking for an invalid key exceptional, basically, so I wish the syntaxes were reversed, so I could use the more succinct form for (my) more common case.
The idiomatic way here being that the zero value should be a sane default for that value. You are then free to create your own types ontop of that, but the defaults are often in the ballpark of where you want to be unless you have specific needs in your context.
This is not always the case. In fact, for query APIs I do not expect it at all. Sometimes I'm doing something similar to a UPSERT operation with more nuanced behavior, then returning no existing entry on a preceding SELECT query could be the 99% use case.
> This is not always the case. In fact, for query APIs I do not expect it at all.
And, in your case, "if you expected the record to be found" doesn't apply.
If I'm iterating over a list of names/id provided by the APi, and then calling the API for more information each one, then I would expect the record to be found... because the API just told me it exists. In that case, it not being found is an exceptional condition.
Unfortunately in static languages this leads to an unholy amount of boilerplate:
public Result<SomethingGood, SomethingBad> PerformUpdate(int goodThingId, UpdateForm form) { ... return new Result<SomethingGood, SomethingBad>(error); ... return new Result<SomethingGood, SomethingBad>(obj); }
The type annotations were hell. Sometimes there were three cases we wanted to consider. We went back to using exceptions, and are keeping a keen eye on the new C# features.
So ultimately - I agree with the author that throwing exceptions is fine, when things actually go wrong. It's also not that bad when things kinda went wrong, but sometimes the effort required to fix it isn't worth it.
F# is a lot better at using result types. You don't even need to specify the return type.
public Result<SomethingGood, SomethingBad> PerformUpdate(int goodThingId, UpdateForm form) { ... return Err(error); ... return Ok(obj); }
which is considerably less boilerplate-y. When there are three cases you want to consider, that's no longer a sum type consisting of just success or failure - that's a different sum type. Maybe even represented as Result<ThreeCases, Error>.
> A null value, a plain "error" object, or an error argument
In my mind, this just adds a lot more mental overhead while also being less informative than a simple exception. Plus you have to keep track of which method your current library has chosen to represent errors.
Note I am biased from using Python for a long time.
Any time you have user input, you can expect errors and so should have proper paths to handle file not found, invalid input, ... For those case, exceptions should not be used.
Testing is big this way, and I've had many 'discussions' with people who could demonstrate no experiences dealing with the consequences of their testing decisions two years on. Either they were gone or had externalized those consequences to others.
Getting multiple exceptions from multiple layers of the code only becomes problematic after the depth and breadth of the call tree has passed the point where a single developer can keep it all in their head.
Not having non-throwing versions of these methods (e.g. Java) means you lose the ability to signal intent and you have to do a lot of pointless catching of exceptions.
I am quite a fan of Rust's solution, with Result<T> types and various macros/methods which can do the commmon stuff for you
I agree having to write the same method a bunch of times is tedious but that is usually not the case. Most situations have clear intentions. It's much more likely in framework APIs where there are a bunch of non-task-specific operations.
I find the whole manual propagation of errors, even with a lot of helpers, to just be tedious boilerplate. Most of the time I literally don't care about the error -- I want to crash my app, log the error, and alert me and the user. I don't need to propagate the potentially limitless number of errors possible in any non-trivial application.
Optional<Womp> getWomp() {...}
Womp womp = getWomp().orElseThrow();
I like this a lot since generic utility methods can have use cases where you sometimes want an exception thrown or simply an error code returned.
If I am working in Java and a checked exception is thrown, my method usually allows the caller to supply an Function<UnderlyingException, DomainException> so that the caller can specify that a domain exception should be thrown instead of having to try-catch the thing.
But I agree with the author of the article - Exceptions are the right design technique when the caller considers an error as a failure as opposed to an unusual but handlable situation. Messing with Result in a deep call-stack is painful.
For recoverable errors, you catch.
For unrecoverable ones, you don't. The exception throwing mechanism unwinds the entire stack and crashes the app with a helpful stack trace.
I feel like encapsulation means that functions often should throw errors, because the small, one responsibility functions shouldn't have knowledge of control flow. For example, division. Anytime you divide, you might try to divide by 0. That's an exception. The / operator returns a number or throws an exception. Who would use a TryDivide function? What would it solve? The same error might be an exception in one scope, and expected in another.
I get that exceptions can be expensive, but they should be rare. Saving time on the pre-check for an exception might save more time than the rare exception. I would never throw an exception strictly for control flow, but avoid exceptions is a bad idea IMO.
But the line is very blurry.
There are scenarios where it is and scenarios where it is not, it's fairly common for APIs to present both throwing and non-throwing variants (many languages stdlibs, for instance, provide both mechanisms for associative array lookups), and of course in any language with exception handling you can convert either to the other according to the needs of the consuming process at the point where you interface just by wrapping the finction in one which either throws or swallows the appropriate exceptions, depending on which direction you are converting.
And if you're dealing with non-determinism, then even a pair of APIs which let you separate out existence-checking from fetches isn't enough; you just introduce a race.
I like how .net does it with methods returning bool and an out parameter. Pattern matching on a return value would be fine too. It only makes sense on methods where you reasonably do need to handle the error case. And a variant which triggers an error condition on failure should exist too, for simpler programs which don't need to handle such failures, merely log them.
Errors can be of two kinds: recoverable and unrecoverable.
Recoverable should be handled by checked exceptions and unrecoverable errors should be handled by runtime exceptions.
This is the thing. This is the root of the thing. Let developers decide what's right for themselves. There are too many "me-too" developers writing terrible code according to terrible guidelines and terrible "best practices" (lol) and few people are actually thinking about what they're doing.
I agree with everything in your comment. Every single thing, and I wish more people understood your (our?) point of view.
They are trying to avoid returning null in this scenario.
Unfortunately, if the API is designed poorly you're stuck with it.
I can sort of understand why people hate checked exceptions but working without exceptions is just terrible.
There's no one-size-fits-all solution. In a lot of ways, the best response to "record not found" is that you get the same result as finding one, except with zero answers. That means your main line can process the same way -- unless that doesn't produce the result you need. In which case maybe you an if(null) somewhere, or a special fake record, or an exception.
At the same time before NULL, devs used to use "guard values", so NULL is really just a convenience.
for instance, just to illustrate what I'm saying:
let NULL = {} /* should every lib define its own null value? */
function GetOneRecord(){
let dbResult = queryRecordsFromDB();
if (dbResult.length === 0){
return NULL
}
return dbResult.records[0];
}
Optional types, are better, but one needs functional programming features for it to be really useful.What's people opinion on zero values by the way?
There are more clever ways to do this nowadays with monadic Result types, but the core idea of forcing a developer to handle error conditions is useful in API design.
The best motivation for unchecked exceptions are things like division by 0, or null pointer dereferences: these are things that result from programmers misusing the API, and are generally unrecoverable. What you want instead is some sort of "graceful crash"--for example, if you're a web server, display a 500 page and log the stack trace somewhere internally. Using a pretty distinct mechanism for driving these error cases (e.g., Rust's panic mechanism) I think works better for emphasizing the crash aspect here.
Of course, now you have the regular checked exceptions which are a fundamental part of the function type. It's not clear to me that the try/catch/throw model is necessarily the best model for propagating these exceptions, and there are several cases where the overhead involved in the (misleadingly named) zero-cost exception handling model are not appropriate for the model.
Although there is something to be said for easy upgrading of checked into unchecked exceptions. It's a relatively natural assertion that something can't fail for reasons the compiler doesn't understand (which means informing the programmer when and where it failed is very critical!). I've always been annoyed by Eclipse deciding that the default implementation of catch should be "ignore the exception" instead of "wrap it in a RuntimeException."
Well, you have no idea in Java either, given that unchecked exceptions exist.
I understand the goal of checked exceptions, I just haven't found much value in them in practice, and given that nobody has copied them I don't think I'm alone. I'm sure it depends what area you're working in, but in my experience it's rare (not unknown, but rare) to be able to do much with e.g. an `IOException` at the call site, and naive approaches like automatic retry can easily be worse than crashing if those retries end up looping excessively and spamming a resource that was already struggling.
My personal preference is for non-exception mechanisms (Maybe/Option/Result objects, or C#'s TryXXX methods) to handle recoverable cases, and unchecked exceptions with very high-level catchers to convert the nonrecoverable ones into sensible diagnostics. YMMV, obviously.
The way I see it, exceptions should only be used for things that are irreconcilable, which most of the time is interpreter errors(e.g. undefined is not a function). In other words, I don't think it's that common that custom exceptions are needed outside of assertions to prevent the developer from doing something stupid. If you aren't using types, raising an exception can be helpful to provide feedback and useful information when data isn't formatted correctly.
Exceptions suck when they're used for problems that aren't actual problems. For example, if a query is made to an API for a record and that record isn't found, the SDK wrapping that API would raise a "Record not found" exception. What the hell? A record not being found is a normal thing! I've seen this kind of thing all over the place, and I recently had to write some application code to work around one of these useless exceptions. They literally tell you nothing and there's no way to solve them without catching/rescuing them. A null value, a plain "error" object, or an error argument in a callback would have been sufficient. If I need an exception to be raised for this kind of thing, I'll do it myself.