8 ms·
> It's not a normal thing for code that needs that record that wasn't found. No offense, but I don't know where that idea comes from. Systems are checking for
by ravenstine 7y ago
> It's not a normal thing for code that needs that record that wasn't found.
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.
- Seenso 7y ago> I'll let people decide which one is better. I personally prefer the first one. 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'); }
- sombremesa 7y agoMany years of experience tell me that you're going down the futile path of getting a tiger to change its stripes. As more and more devs get minted, prepare for codebases (and libraries) to get a LOT more gnarly. Even so, it's a good thing to lower barriers of entry and we can be discerning about where we get our code.
- hombre_fatal 7y agoWell, in real code you are likely bubbling up an error in some way for your 404 Not Found and 500 Internal Error handlers to kick in. 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.
- clon 7y ago> Well, in real code you are likely bubbling up an error in some way for your 404 Not Found and 500 Internal Error handlers to kick in. 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
- _ZeD_ 7y agoThat is a wonderful idea. You don't need to deal with 404, 500 or other stuff, you just write for the "happy path". The framework deals with the rest.
- majewsky 7y agoThen you fill in a complex form with 6 object references, and the framework just spits out "NOT FOUND", which means "have fun figuring out which one is the missing object".
- joshuamorton 7y ago`raise MissingField(field_name)` solves this problem.
- 7y ago
- irjustin 7y agoIn general, I think this is why Rust uses `panic` since the name is way... angrier. 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.
- cjfd 7y agoThe code example is not convincing at all. Obviously a single if is much cleaner than a single try/catch block but that is not the comparison. The if needs to be replicated N times for every N things that could fail and that are calling each other in a potentially deep call hierarchy. The try/catch block only needs to occur once at a level that is above all of the N things that might fail.
- dllthomas 7y agoIn my experience, on the web there are two different kinds of things we might be fetching. 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.
- cjfd 7y agoSure, it depends on the situation. In the case you outline, there could also be room for an auxiliary function like fetch_alternatives({ preferred_resouce, second_best_resource , third_best_resource}) that would fetch them in until one can actually be obtained and throw an exception if all of them fail. Not that I am married to exceptions or anything. One should just use what is best in the situation that one is in. Actually, I think articles that claim that one language construct is better than some other language construct are quite silly. It mostly depends on the situation. Though, there also may be language constructs that are best left off deprecated, depending the language that you are using.
- AstralStorm 7y agoWhy are they a poor choice for the latter? 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.
- je42 7y agoThe article is fairly specific: 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).
- necovek 7y agoThat's a pretty good example. Now imagine you want to structure your code in a way that render('record-not-found') happens in a caller to that block. So exceptions as a general approach work pretty well to allow simple code organisation according to your needs. 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)
- kstenerud 7y agoYour first example loses the real reason for the error and just assumes "record-not-found", so it will give misleading errors when the network drops for a moment, possibly resulting in an attempt to insert a duplicate record if the code tries to add-if-not-found. That'll make for some debugging fun! 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.
- ravenstine 7y ago@Seenso You probably won't find, this, but I think I somewhat misunderstood the first thing you said. 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.