Why I Prefer Exceptions to Error Values (cedardb.com)
from AlexKochetkova@programming.dev to programming@programming.dev on 13 Sep 2024 13:49
https://programming.dev/post/19350319

#programming

threaded - newest

marcos@lemmy.world on 13 Sep 2024 16:09 next collapse

In C++, ignoring anything that any other language provides…

I mean, yeah, if your language does not support error values, do not use them.

lysdexic@programming.dev on 13 Sep 2024 18:45 collapse

I mean, yeah, if your language does not support error values, do not use them.

Nonsense. If adopting info of the many libraries already available is not for you, it’s trivial to roll your own result type.

Even if that was somehow unexplainably not an option, even the laziest of developers can write a function to return a std::tuple or a std::pair and use structured binding.

marcos@lemmy.world on 13 Sep 2024 18:50 collapse

The problem is not encoding the result.

The problem is that you need some support from the language to make it easy to deal with. Otherwise you’ll get into go-style infinite if (err != null) handlers that will make your code unreadable.

lysdexic@programming.dev on 13 Sep 2024 19:21 collapse

The problem is that you need some support from the language to make it easy to deal with.

Nonsense.

if (result.isSuccess()) {
    do_something(result.value);
}
else {
   handle_error(result error);
}
sukhmel@programming.dev on 13 Sep 2024 21:00 collapse

I feel like this will have zero protection against

if (result.isSuccess()) {
    handle_error(result.error);
} else {
    do_something(result.value);
}

Besides, this is exactly what the comment said about having to constantly check for return values at call site. I think this may be mitigated by some clever macro-magic, but that will become a mess fast.

yoevli@lemmy.world on 13 Sep 2024 21:55 next collapse

I mean, technically there’s nothing preventing that, but in practice it’s a fairly uncommon mistake to make and it’s immediately obvious that there’s an issue the first time that path is taken. If something like that makes it to production, it clearly points to an issue with test coverage rather than code paradigm.

lysdexic@programming.dev on 14 Sep 2024 18:20 collapse

I feel like this will have zero protection against

Zero protections against what? Against the programmer telling the program to do something it shouldn’t? Not programming language does that. If you resort to this sort of convoluted reasoning, the same hypothetical programmer can also swallow all exceptions.

The main problem you’re creating for yourself is that you’ve been given an open-ended problem but instead prefer to not look for solutions.

DirigibleProtein@aussie.zone on 13 Sep 2024 16:16 next collapse

Memory isn’t infinite, CPUs can’t process all integers, and Santa isn’t real

Wait, what? Need a spoiler tag.

okamiueru@lemmy.world on 13 Sep 2024 16:25 next collapse

I’m just going to comment on the face value of the title itself, and make assumptions otherwise.

  • Exceptions are control flow mechanism. I.e. that can be used for code execution flow, in the same application.

  • Error codes are useful across some API boundary.

Does this adequately cover whatever it is they figured out was a good tradeoff?

sukhmel@programming.dev on 13 Sep 2024 21:07 collapse

I’m just going to ask, without making assumptions. Have you managed to cut some time to read the article and find an answer?

okamiueru@lemmy.world on 14 Sep 2024 10:59 collapse

I did eventually yes. Thanks for asking. I was exhausted yesterday, and upon reading my comment again, I get the downvotes. Being a second language doesn’t fully explain the wrong tone there. The article was a lot more insightful and in depth than I had mistakenly assumed.

After reading it tho, it seemed a lot more focused on performance than I think would be warranted. But that could be due to different concerns and constraints than where I’m used to working. I’d focus more on the mechanisms that best expresses the intent, and although they do discuss this well, the Venn diagram for the appropriate use of exceptions and error codes don’t overlap as much in my world.

And, it’s not like I’m arguing that they are wrong. It’s an opinion on a choice for a tradeoff that I only think, while allowing the possibility of being wrong, might miss the the mark. Stack unwinding is by its nature less explicit for the state it leaves behind. So it shouldn’t be a question of either error codes or exceptions, but which are most appropriate to express what, and when.

Even for Rust, where monads are preferred and part of the language to express and handle error codes, I would say that the statement of “newer languages like Rust don’t allow the use of exceptions”, seems incorrect to me. Something like panic!(“foo”); coupled with panic::catch_unwind(|| { … } }); I believe would unwind the stack similar to that of a throw/catch.

Anyways. Thanks for reminding me to actually read the post. It was well worth it, and very insightful.

sukhmel@programming.dev on 14 Sep 2024 15:44 collapse

To be fair, I disagree with all the points author makes, except for performance which is important but may be less important than code clarity in different cases. I am surprised that exceptions perform that well, and I am surprised the author said that compared C++ exceptions to Rust results, but actually did the right thing and compared C++ exceptions with C++ expected first. I thought it was going to be one of those “let’s compare assembly to lisp”

aubeynarf@lemmynsfw.com on 13 Sep 2024 16:27 next collapse

You have to explicitly check if the return value is an error and propagate it. You write the same boilerplate if (err) return err over and over again, which just litters your code.

That’s only true in crappy languages that have no concept of async workflows, monads, effects systems, etc.

Sad to see that an intentionally weak/limited language like Go is now the counterargument for good modeling of errors.

sping@lemmy.sdf.org on 13 Sep 2024 16:48 next collapse

I naively thought it I may as well take a job using Go, as learning a new language is broadening, and some people like it, so lets find out first hand… I knew it was a questionable choice, looking at how Go adoption tailed off a while ago.

Turns out I hate Go. Sure it’s better than C but that’s a very low bar, and C was never a good alternative choice for the use cases I’m encountering. I’m probably suffering from a codebase of bad Go, but holy shit it’s painful. So much silent propagation of errors up the stack so you never know where the origin of the error was. So very much boilerplate to expand simple activities into long unreadable functions. Various Go problems I’ve hit can be ameliorated if you “don’t do it like that”, but in the real world people “do it like that” all the time.

I’m really starting to feel like there are a lot of people in the company I’ve joined who like to keep their world obtuse and convoluted for job security.

lysdexic@programming.dev on 13 Sep 2024 18:42 next collapse

That’s only true in crappy languages that have no concept of async workflows, monads, effects systems, etc.

You don’t even need to sit on your ass and wait for these data types to be added to standard libraries. There are countless libraries that support those, and even if that is somehow not an option it’s trivial to roll your own.

matcha_addict@lemy.lol on 13 Sep 2024 18:59 collapse

Can you please demonstrate how async workflows and monads resolve this issue?

Wouldn’t effect systems still be considered exceptions, but handled differently?

sukhmel@programming.dev on 13 Sep 2024 20:55 next collapse

I don’t know the answer to your question, but I think that what is needed is just a bit of syntactic sugar, e.g. Rust has ? for returning compatible errors without looking into them. That seems to be powered by Try trait, that may be a monad, but I am not fluent enough to check if it formally is.

baseless_discourse@mander.xyz on 14 Sep 2024 11:53 collapse

In Maybe monadic, its monadic bind will automatically resolves any failed computation, and don’t need explicit checking.

for example, the code in Haskell looks something like the following:

fib: Int -> Int -> Maybe Int
fib max_depth idx =
  do
     guard (0 <= max_depth)
     n1 <- fib (max_depth - 1) (idx - 1)
     n2 <- fib (max_depth - 1) (idx - 2)
     return (n1 + n2)

Haskell type class system automatically figures out this is a maybe monad, and check for error accordingly.

Notice, unlike the C code the author provide, this haskell code will exit immediately when n1 failed and never compute n2, similar to the behavior of the exception code. Thus I believe his point about performance is at least unjustified, if not wrong.

Another interesting fact about this code is that there is nothing that is built into the compiler/interpretor (except the do expression, which is just a minor syntactical sugar). For this code, the compiler designers don’t need to design special semantics for raise and catch. Everything here, guard, return, and the Maybe monad (which is in charge of propagating errors) is defined by the user, using normal functions, no metaprogramming involved.

Wouldn’t effect systems still be considered exceptions, but handled differently?

Yes, unlike monad, the error in algebraic effect is propagated by the compiler/interpretor, instead of user defined. But unlike implicit effect, explicit effect (algebraic effect, throwable, etc.) makes it clear how the code can go wrong.

Although explicit error through monad or algebraic effect is more clear in general, there are special cases where explicit effect is undesirable. One such example is effect pollution: low-level effects that are unlikely to cause impure behaviors are unnecessarily propagated through the call stack. This problem can make the code more verbose and difficult to handle.

thingsiplay@beehaw.org on 13 Sep 2024 19:18 next collapse

My anecdotal experience is that Rust code, for example, has more calls to unwrap than I’d like. The problem here is that simply unwrapping results will crash the program on errors that could have been a user-visible error message with exceptions.

unwrap() is explicitly not handling the error in a Result type. If you must do this, then at least use except(), to unwrap the code but with an error message if program crashes. Its the equivalent of having Exceptions and then not handling that exception. Therefore your critique is not valid here.

One problem with Exceptions is, you never know what code your function or library calls that can produce an exception. It’s not encoded in the type system or signature of the function. So you need to pray and try catch all possible exceptions (I look at this from Pythons perspective), if you don’t want a Catch…All, which then you wouldn’t know what error this actually is. And you still don’t know where this error came from or happened in the code, how deep in the function call chain? Instead Errors as Values means its encoded in type system and you can directly see what errors the function can cause and (in Rusts case) you must handle the error, otherwise program won’t compile. You don’t need to handle anything else in this context. Compiler ensures that all possible errors are handled (again within context of our discussion). Vast improvement!

sukhmel@programming.dev on 13 Sep 2024 21:04 collapse

you never know what code your function or library calls that can produce an exception

As far as I remember, there were several attempts at introducing exceptions into type system, and all have failed to a various degree. C++ abandoned the idea completely, Java has a half-assed exception signature where you can always throw an unexpected exception if it’s runtime exception, mist likely there were other cases, too.

So yeah, exception as part of explicit function signature is a vast improvement, I completely agree

thingsiplay@beehaw.org on 13 Sep 2024 22:56 collapse

So yeah, exception as part of explicit function signature is a vast improvement, I completely agree

Hmm, I’m not sure if you are being sarcastic. In my reply I didn’t meant encoding Exceptions into Type system. Is this a type and you probably meant “Error Types as part of” instead “exception as part of”?

Honestly I don’t know how Exceptions as part of type system would even look like. Because each function call in a chain would need to have all information from previous function call, otherwise that information gets lost to the next caller. The problem is the hierarchy of function and method calls. Somewhere some objects and functions can be edited to Throw a new Exception, that is not handled through the entire chain. And for the higher function caller, there is 0% way of knowing that (through code, besides documentation off course).

sukhmel@programming.dev on 14 Sep 2024 07:28 collapse

Yeah, I shaped my words poorly. What I meant is that errors are sort of equivalent to exceptions, but errors are first class citizens of type system, and this is an improvement over exceptions being kind of independent of type

thingsiplay@beehaw.org on 14 Sep 2024 07:31 collapse

Ah it was intentional and now I see how it was meant. It makes perfectly sense, it was just not clear before. :-) Human language and interpreter is not as precise like programming language.^^

mox@lemmy.sdf.org on 13 Sep 2024 20:15 next collapse

It would be nice to include Zig’s approach in the comparison. I’ve only just begun learning it, but the syntax seems pretty elegant from what I’ve seen so far.

Upvoting not because I share author’s preference, but because I’m interested in reading other people’s perspectives on the topic.

thingsiplay@beehaw.org on 13 Sep 2024 22:45 collapse

Read my reply with a handful of sea salt. I just read tutorials and documentation a bit and did Hello World.

Zig is pretty cool too! It can run C code directly just like C++ does (I think), kind of drop in replacement. From my reading so far, Error Handling is kind of a marriage between Go’s and Rust’s Error handling. Actually pretty cool. It has Error Types, but is kept relatively simple and doesn’t force to do all the stuff. It has Try and Catch keywords to handle errors elegantly, but don’t be fooled, this has nothing to do with Try…Catch blocks for Exceptions. Zigs Try and Catch are more like Rusts Result type handling, at least from what I read so far.

I lean more towards Zig than Go, but it still has not reached stable 1.0 release.

ExperimentalGuy@programming.dev on 13 Sep 2024 21:18 next collapse

The article made a few good points, but a good amount of it was conjecture. I liked the part about comparing the two functions and showing that exceptions are faster but I think a big thing he’s not getting is readability. Even in the functions he showed, you can directly see that the one using std::expected has the happy path and error path directly in the function signature, whereas the exception one doesn’t.

As for the “error kind” trap he was talking about, that definitely exists, but ignores the fact that you can also get this same kind of error from exceptions. I’ve definitely gotten exceptions that I didn’t understand from Python or Java libraries, but it’s not a problem with exceptions but a problem with how they’re shown. If there’s nothing to tell me that I should have thought of that error, it shouldn’t be an expectation for a dev to have thought of it.

FizzyOrange@programming.dev on 13 Sep 2024 21:35 next collapse

I think I disagree with everything here.

Exceptions Are Much Easier to Work With

Well, they’re “easier” in the same way that dynamic typing is easier. It’s obviously less work initially just to say “screw it; any error gets caught in main()”. But that’s short term easiness. In the long term its much more painful because:

  1. You don’t know which functions might produce errors, and therefore you don’t know where you should be even trying to handle errors. (Checked exceptions are the answer here but they are relatively rarely used in practice.)
  2. Actually handling errors properly often involves responding to errors from individual function calls - at least adding human readable context to them. That is stupidly tedious with exceptions. Every line turns into 5. Sometime it makes the code extremely awkward:
try {
   int& foo = bar();
} catch (...) {
   std::cout << "bar failed, try ...\n";
   return nullopt;
}
foo = 5;

(It actually gets worse than that but I can’t think of a good example.)

Over 100× less code! [fewer exception catching in their C++ database than error handling in a Go database]

Well… I’m guessing your codebase is a lot smaller than the other one for a start, and you’re comparing with Go which is kind of worst case… But anyway this kind of proves my point! You only actually have proper error handling in 140 places - apparently mostly in tests. In other words you just throw all exceptions to main().

System errors [he’s mainly talking about OOM, stack overflow & arithmetic errors like integer overflow]

Kind of a fair point I guess. I dunno how you can reasonably stack overflows without exceptions. But guess what - Rust does have panic!() for that, and you can catch panics. I’d say that’s one of the few reasonable cases to use catch_unwind.

Exceptions Lead to Better Error Messages

Hahahahahaha. I dunno if a bare stack trace with NullPointerException counts as a “better error message”. Ridiculous.

Exceptions Are More Performant

Sure maybe in error handling microbenchmarks, or particularly extreme examples. In real world code it clearly makes little difference. Certainly not enough to choose an inferior error handling system.

I would say one real reason to prefer exceptions over Result<>s is they are a fair bit easier to debug because you can just break on throw. That’s tricky with Result<> because creating a Err is not necessarily an error. At least I have not found a way to “break on Err”. You can break on unwrap() but that is usually after the stack has been unwound quite a bit and you lose all context.

BatmanAoD@programming.dev on 14 Sep 2024 08:11 next collapse

There’s also a massive tradeoff for when the error condition actually occurs. If an exception does get thrown and caught, that is comparatively slowwww.

UndercoverUlrikHD@programming.dev on 14 Sep 2024 21:30 collapse

The author pointed out how exceptions are often faster than checking every value. If your functions throws an error often enough that Exception handling noticeably slow down your program, surely you got to take a second look at what you’re doing.

BatmanAoD@programming.dev on 15 Sep 2024 06:32 collapse

It depends what kind of errors you’re talking about. Suppose you’re implementing retries in a network protocol. You can get errors pretty regularly, and the error handling will be a nontrivial amount of your runtime.

BehindTheBarrier@programming.dev on 14 Sep 2024 09:35 collapse

It can be pretty convenient to throw an error and be done with it. I think for some languages like Python, that is pretty much a prefered way to deal with things.

But the entire point of Rust and Result is as you say, to handle the places were things go wrong. To force you to make a choice of what should happen in the error path. It both forces you to see problems you may not be aware of, and handle issues in ways that may not stop the entire execution of your function. And after handling the Result in those cases, you know that beyond that point you are always in a good state. Like most things in Rust, that may involve making decisions about using Result and Option in your structs/functions, and designing your program in ways that force correct use… but that a now problem instead of a later problem when it comes up during runtime.

kersplomp@programming.dev on 14 Sep 2024 10:40 collapse

But the entire point of Rust and Result is… to force you to make a choice of what should happen

Checked exceptions also force you to handle it and take way less boilerplate.

BehindTheBarrier@programming.dev on 14 Sep 2024 11:45 collapse

But nothing is forcing you to check exeptions in most languages, right?

While not checking for exceptions and .unwrap() are pretty much the same, the first one is something you get by not doing anything extra while the latter is entirely a choice that has to be made. I think that is what makes the difference, and in similar ways why for example nullable enabled project in C# is desired over one that is not. You HAVE to check for null, or you can CHOOSE to assume it is not by trying to use the value directly. To me it makes a difference that we can accidentally forget about a possible exception or if we can choose to ignore it. Because problems dealt with early at compile time, are generally better than those that happen at runtime.

kersplomp@programming.dev on 14 Sep 2024 16:09 collapse

I see your concern, but in practice that’s not what happens in languages like Java and Python with exceptions. Not checking for exceptions is a choice because everyone knows you need to check in your top-level functions. Forgetting to catch is a problem that only hits newbies.

OmnipotentEntity@beehaw.org on 15 Sep 2024 05:49 collapse

A problem that only affects newbies huh?

Let’s say that you are writing code intended to be deployed headless in the field, and it should not be allowed to exit in an uncontrolled fashion because there are communications that need to happen with hardware to safely shut them down. You’re making a autonomous robot or something.

Using python for this task isn’t too out of left field, because one of the major languages of ROS is python, and it’s the most common one.

Which of the following python standard library functions can throw, and what do they throw?

bytes, hasattr, len, super, zip

kersplomp@programming.dev on 18 Sep 2024 07:46 collapse

Too long, didn’t read

magic_lobster_party@fedia.io on 14 Sep 2024 09:36 next collapse

I didn’t read everything, but I mostly agree with the author, especially on this point:

While you can definitely abuse exceptions, functional-style error values are not a one-size-fits-all solution.

There are time and place for both. I think exceptions are good for bigger errors. Like database connection errors. Things that shouldn’t happen without any easy backup plan. Those errors might need to be escalated as high as possible where proper action can be made (like resetting the database connection and everything relying on it).

Functional style is great for smaller stuff. Like key not found in hash maps. In many cases there might be good defaults that can be used instead.

kersplomp@programming.dev on 14 Sep 2024 10:51 next collapse

Oof, some of these comments. Sorry on behalf of the edge lords, OP.

baseless_discourse@mander.xyz on 14 Sep 2024 11:14 next collapse

The more I read about these kind of article the more I am amazed that our digital future is at hand in utterly incompetent people.

This person clearly have no understanding of monadic error (AKA Maybe/option monad or slightly more advanced Either monad), which is the first monad we teach at a class targeting second year undergrad.

The performance comparison is just plain factual error. The functional error code will continue to compute n2 when computation of n1 failed; the same do not happen in the exception version. If you compare codes with completely different traces, of course they will have different performance…

A properly implemented monadic error will return as soon as compute for n1 failed, and never execute the rest of the code. This is the default and idiomatic behavior in Haskell, OCaml, F#, and rust. This performance problem doesn’t even happen in LINQ-style handling like in C# and Kotlin (maybe also Typescript?).

The point of monadic error is that its control flow is local, whereas exception is non-local. Specifically, the exception can be handled and occur anywhere in the code base, with no indication on the type level. So programmers will be constantly worrying about whether the exception in a function call is properly handled.

Even worse, when you try to catch a certain error, there is always the risk to accidentally catch similar exceptions in a library call or someone else’s code. Writing good code with try-catch requires a lot of principle and style guides. But unlike monads, these principle and rules cannot be enforced by the type system, adding extra burden to programmers.

In fact, we have known for a long time that non-local control flows (goto, break, contiune, exception, long jump) are the breeding ground for spaghetti code. As an evidence, many non-local control flows (goto, long jump) are baned in most languages.

That being said, there are certainly cases, with proper documentation, that exception style is easy to write and understand. But I think they are very specific scenarios, which have to be justified on a case-by-case basis.

ulterno@lemmy.kde.social on 15 Sep 2024 07:44 next collapse

I was making a shared library at work and was recently asked to start throwing exceptions, because the users wouldn’t care to check my returned error and just continue with the empty returned data.
Well, now they will most probably have an empty catch block and continue doing what they did before.

Nothing can fix a lazy worker.

Anti Commercial-AI license

noddy@beehaw.org on 15 Sep 2024 20:53 collapse

I think the author of the article just haven’t understood how to use the ? operator yet, and don’t think they deserve being called “utterly incompetent” for it. Whether something is a monad or not is not necessarily something a programmer should have to think about on a daily basis IMO.

I just think of rust errors as a tagged enum with either a value or an error. And the ? operator as syntax sugar for returning if something was an error. IMO that simple understanding is sufficient to do error handling in Rust. I don’t think we should gatekeep programming behind some intellectual barrier of whether or not you understand category theory. I certainly don’t understand what a monad is, but I can still write working software and do error handling without unwraps.

skulbuny@sh.itjust.works on 14 Sep 2024 11:20 next collapse

Look at gleam and elixir. Both are functional. Both use exceptions, but both also use error values as well. There is no reason why we can’t have both. These are incredibly fault tolerant systems.

Kissaki@programming.dev on 14 Sep 2024 12:17 next collapse

Does the performance cost of error checking/result types they discovered in C++ apply to languages that have native result and option types like Rust?

I would hope they were able to find efficient, performant implementations, and that branch prediction picks the expected non-error branch in most cases.

Ephera@lemmy.ml on 14 Sep 2024 12:23 next collapse

The guy keeps on picking on Go, which is infamous for having terrible error handling, and then he has the nerve to even pick on the UNIX process return convention, which was designed in the 70s.
The few times he mentions Rust, for whatever reason he keeps on assuming that .unwrap() is the only choice, which’s use is decidedly discouraged in production code.

I do think there is room for debate here. But error handling is a hellishly complex topic, with different needs between among others:

  • short- vs. long-running processes
  • API vs. user-facing
  • small vs. big codebase
  • library vs. application code
  • prototyping vs. production phase

And even if you pick out a specific field, the two concepts are not clearly separated.
Error values in Rust usually have backtraces these days, for example (unless you’re doing embedded where this isn’t possible).
Or Java makes you list exceptions in your function signature (except for unchecked exceptions), so you actually can’t just start throwing new exceptions in your little corner without the rest of the codebase knowing.
I find it quite difficult to properly define the differences between the two.

[deleted] on 14 Sep 2024 14:23 next collapse

.

qaz@lemmy.world on 15 Sep 2024 07:52 collapse

I find it quite difficult to properly define the differences between the two.

The handling is enforced by one while the other may be unknown to the person who calls the function. I think that’s a pretty clear difference.

Ephera@lemmy.ml on 15 Sep 2024 08:36 collapse

But that’s what I mentioned regarding Java there. Java calls them “exceptions”, but generally forces the caller to either handle them or explicitly bubble them upwards…

Kissaki@programming.dev on 14 Sep 2024 12:24 next collapse

They make valid points, and maybe it makes sense to always prefer them in their context.

I don’t think exceptions always lead to better error handling and messages though. It depends on what you’re handling.

A huge bin of exception is detailed and has a lot of info, but often lacks context and concise, obvious error messages. When you catch in outer code, and then have a “inaccessible resource” exception, it tells you nothing. You have to go through the stack trace and analyze which cases could be covered.

If explicit errors don’t lead to good handling I don’t think you can expect good exception throwing either. Both solutions need adequate design and implementation to be good.

Having a top-level (in their server context for one request or connection) that handles and discards one context while the program continues to run for others is certainly simple. Not having to propagate errors simplifies the code. But it also hides error states and possibilities across the entire stack between outer catch and deep possible throw.

In my (C#) projects I typically make conscious decisions between error states and results and exceptional exceptions where basic assumptions or programming errors exist.

Phoenix3875@lemmy.world on 14 Sep 2024 19:11 next collapse

One problem with exceptions is composability.

You have to rely on good and up-to-date documentation or you have to dig into the source code to figure out what exceptions are possible. For a lot of third party dependencies (which constitute a huge part of modern software), both can be missing.

Error type is a mitigation, but you are free to e.g. panic in Rust if you think the error is unrecoverable.

A third option is to have effect types like Koka, so that all possible exceptions (or effects) can be checked at type level. A similar approach can be observed in practical (read: non-academic) languages like Zig. It remains to be seen whether this style can be adopted by the mainstream.

UndercoverUlrikHD@programming.dev on 14 Sep 2024 21:25 collapse

They both have their place. I just recently discovered a bug in lemmy bot I wrote where the lemmy API module will raise an Exception if login fails (response status code != 200), which feels extremely out of place, as the error/status code do matter in that case.

Other times exceptions make more sense as Phillip pointed out. It’s easier faster to ask for forgiveness than permission after all.

Opisek@lemmy.world on 15 Sep 2024 08:19 collapse

Why would it be 200? Imo it should be either 400 or 401.

GetOffMyLan@programming.dev on 15 Sep 2024 08:44 collapse

I think with http request failures losing the status code can be a real pain. There’s a big difference between 400, 401, 404 and 500 for instance.