If I understood rightly, exceptions in Haskell basically intended to deal within IO monad. At least exception could be caught inside IO monad.
But sometimes even pure functions may throw an exception, e.g. read "..." :: Int (when reading string does not represent integer), operator (!!) (when we trying to get item out of range of the list), and so forth. And this is true behavior, I don't deny. However, I would not want to change signature of function just to get it chance to catch possible exception, because in this case I have to change signature all the functions by call stack before.
Is there some pattern to deal with exceptions more comfortable in Haskell, out of IO monad? May be I should use unsafePerformIO in this case? How "safe" to use unsafePerformIO just for catching exceptions in pure functions?
In pure code, it's usually best to avoid exceptions from happening in the first place. That is, don't use head unless you're absolutely positive that the list isn't empty, and use reads and pattern matching instead of read to check for parse errors.
I think a good rule of thumb is that exceptions in pure code should only come from programming errors, i.e. calls to error, and these should be treated as bugs, not something an exception handler can deal with.
Note that I'm only talking about pure code here, exceptions in IO have their uses when dealing with the exceptional cases that sometimes happen when interfacing with the "real world". However, pure mechanisms like Maybe and ErrorT are easier to work with, so they are often preferred.
That's what monads are for! (Well, not just that, but exception handling is one use of monadic idioms)
You do change the signature of functions that can fail (because they change semantics, and you want to reflect as much of the semantics as possible in the type, as a rule of thumb). But code that uses these functions does not have to pattern match on every failable function; they can bind if they don't care:
head :: [a] -> Maybe a
eqHead :: (Eq a) => [a] -> Maybe [a]
eqHead xs = do
h <- head xs
return $ filter (== h) xs
So eqHead cannot be written "purely" (a syntactic choice whose alternatives I would like to see explored), but it also doesn't really have to know about the Maybe-ness of head, it only has to know that head can fail in some way.
It's not perfect, but the idea is that functions in Haskell do not have the same style as Java. In typical Haskell design, an exception does not usually occur deep in the call chain. Instead, the deep call chain is all pure, when we know that all arguments are fully defined and well-behaved, and validation occurs at the outermost layers. So an exception bubbling up from the deep is not really something that needs to be supported in practice.
Whether the difficulty of doing so causes the design patterns, or the design patterns cause the lack of features supporting this is a matter of debate.
If you anticipate that a function such as read may cause an exception, then why not simply restructure your code to avoid the possibility of the exception occurring?
As a more direct answer to your question, there is spoon.
I'm going to go out on a limb and disagree strongly with the opinions of people who say that the solution is to "just avoid having bugs in the first place." I would structure your code around handling your errors within one of these monads.
One of the main strengths of pure functions (and FP) is the ability to reason about your code, along the lines of "if a function has the type [a] -> a, then for all lists of a type a, I'll get back a value of type a." Exceptions like these cut the legs out from under that.
A good reason for head being the way that it is, is that it's much simpler for beginners to learn list manipulations before Maybe and friends. But if you can understand a better way to do it, I'd avoid these risks.
Related
Say I have a web application with UserController. Client sends a HTTP POST request that is about to be handled by the controller. That however first must parse the provided json to UserDTO. For this reason there exist a UserDTOConverter with a method toDTO(json): User.
Given I value functional programming practices for its benefits of referential transparency and pure function the question is. What is the best approach to deal with a possibly inparsable json? First option would be to throw an exception and have it handled in global error handler. Invalid json means that something went terrible wrong (eg hacker) and this error is unrecoverable, hence the exception is on point (even assuming FP). The second option would be to return Maybe<User> instead of User. Then in the controller we can based on the return type return HTTP success response or failure response. Ultimately both approaches results in the same failure/success response, which one is preferable though?
Another example. Say I have a web application that needs to retrieve some data from remote repository UserRepository. From a UserController the repository is called getUser(userId): User. Again, what is the best way to handle the error of possible non existent user under provided id? Instead of returning User I can again return Maybe<User>. Then in controller this result can be handled by eg returning "204 No Content". Or I could throw an exception. The code stays referentially transparent as again I am letting the exception to bubble all the way up to global error handler (no try catch blocks).
Whereas in the first example I would lean more towards throwing an exception in the latter one I would prefer returning a Maybe. Exceptions result in cleaner code as the codebase is not cluttered with ubiquitous Eithers, Maybes, empty collections, etc. However, returning these kinds of data structure ensure explicitness of the calls, and imo results in better discoverability of the error.
Is there a place for exceptions in functional programming? What is the biggest pitfall of using exceptions over returning Maybes or Eithers? Does it make sense to be throwing exceptions in FP based app? If so is there a rule of thumb for that?
TL;DR
If there are Maybes/Eithers all over the codebase, you generally have a problem with I/O being mixed promiscuously with business logic. This doesn't get any better if you replace them with exceptions (or vice versa).
Mark Seemann has already given a good answer, but I'd like to address one specific bit:
Exceptions result in cleaner code as the codebase is not cluttered with ubiquitous Eithers, Maybes, empty collections, etc.
This isn't necessarily true. Either part.
Problem with Exceptions
The problem with exceptions is that they circumvent the normal control flow, which can make the code difficult to reason about. This seems so obvious as to barely be worthy of mention, until you end up with an error thrown 20 calls deep in a call stack where it isn't clear what triggered the error in the first place: even though the stack trace might point you to the exact line in the code you might have a very hard time figuring out the application state that caused the error to happen. The fact that you can be undisciplined about state transitions in an imperative/procedural program is of course the whole thing that FP is trying to fix.
Maybe, Maybe not: It might be Either one
You shouldn't have ubiquitous Maybes/Eithers all over the codebase, and for the same reason that you shouldn't be throwing exceptions willy-nilly all over the codebase: it complicates the code too much. You should have files that are entry points to the system, and those I/O-concerned files will be full of Maybes/Eithers, but they should then delegate to normal functions that either get lifted or dispatched to through some other mechanism depending on language (you don't specify the language). At the very least languages with option types almost always support first-class functions, you can always use a callback.
It's kind of like testability as a proxy for code quality: if your code is hard to test it probably has structural problems. If your codebase is full of Maybes/Eithers in every file it probably has structural problems.
You're asking about a couple of different scenarios, and I'll try to address each one.
Input
The first question pertains to converting a UserDTO (or, in general, any input) into a stronger representation (User). Such a conversion is usually self-contained (has no external dependencies) so can be implemented as a pure function. The best way to view such a function is as a parser.
Usually, parsers will return Either values (AKA Result), such as Either<Error, User>. The Either monad is, however, short-circuiting, meaning that if there's more than one problem with the input, only the first problem will be reported as an error.
When validating input, you often want to collect and return a list of all problems, so that the client can fix all problems and try again. A monad can't do that, but an applicative functor can. In general, I believe that validation is a solved problem.
Thus, you'll need to model validation as a type that isomomorphic to Either, but has different applicative functor behaviour, and no monad interface. The above links already show some examples, but here's a realistic C# example: An applicative reservation validation example in C#.
Data access
Data access is different, because you'd expect the data to already be valid. Reading from a data store can, however, 'go wrong' for two different reasons:
The data is not there
The data store is unreachable
The first issue (querying for missing data) can happen for various reasons, and it's usually appropriate to plan for that. Thus, a database query for a user should return Maybe<User>, indicating to the client that it should be ready to handle both cases: the user is there, or the user is not there.
The other issue is that the data store may sometimes be unreachable. This can be caused by a network partition, or if the database server is experiencing problems. In such cases, there's usually not much client code can do about it, so I usually don't bother explicitly modelling those scenarios. In other words, I'd let the implementation throw an exception, and the client code would typically not catch it (other than to log it).
In short, only throw exceptions that are unlikely to be handled. Use sum types for expected errors.
I have seen code like below, where exceptions are wrapped in a generic error. What i don't like about this approach is that we need to write a handler to deal with this UnexpectedError, inspect it, extract the exception and log it. Not sure if this is the correct way to do it.
override suspend fun update(
reservation: Reservation,
history: ReservationHistory
): Either<ReservationError, Reservation> {
return Either.catch {
mongoClient.startSession().use { clientSession ->
clientSession.startTransaction()
mongoClient.getDatabase(database)
.getCollection<ReservationDocument>()
.updateOneById(reservation.reservationId.value, MapToReservationDocument.invoke(reservation))
mongoClient.getDatabase(database)
.getCollection<ReservationHistoryDocument>()
.insertOne(MapToReservationHistoryDocument.invoke(history))
reservation
}
}.mapLeft {
UnexpectedError(it)
}
}
For example, what should I use when it is necessary to create a column in the table only if such column does not exist?
I can write a code that will check if a column exists doesn't exist, and only then add it, so I can and it without check by wrapping my method in the try-catch (if there is - catch exceptions, and if not - the column will be added)
The result will be the same.
And there are a lot of examples, for example, you can scan files to exist, and only then make a copy, and you can catch exceptions.
Which method is more literate, or the right?
Interestingly enough, your question depends on the programming language you are talking about.
In languages such as C, Java, C++, C# ... people prefer the "LBYL" (Look Before You Leap) pattern; whereas languages such as python heavily emphasize "EAFP" (it's Easier to Ask for Forgiveness than Permission).
Meaning: in python, you are using try/catch a lot (even the "counting for loop" is implemented as try/catch); whereas in C#, Java, C++ you would prefer doing if/else instead.
And these conventions are really important to follow - most Cx-language programmers simply assume that you don't use try/catch to model control flow. In other words: you should follow that paradigm that the majority of other developers in that language would be using.
I do not think there is a "right" way to go. Either way works so that you achieve your goal no matter what. For clean code however, my (very subjective) view is that exceptions should only be used as that very "literal" thing and indicate that something exceptional (or unexpected) has occured. So in most cases you would simply be overspamming your runtime with exceptions. If however you seek to scan a document and always expect to have a certain number of columns, it might be the right way to go with an exception, as the wrong number of columns would be just that: an exception.
what can't be done with if else clause, and can be done with exception handling ?
In other words where do we actually need to use exception handling and mere if else won't serve the purpose.
Is exception handling just a glorified way of showing errors ?
In the earliest C++ implementations, exceptions were compiled into equivalent if/else constructs, more or less. And they were slow as molasses, to the point where you can still find programming guides that recommend against exceptions on the grounds that "they are slow".
So it is not a question of "can" or "cannot". It is a question of readability and performance.
Littering every line of your code with error checks makes it harder for a human to follow the non-exceptional (i.e. common) case. And as every good programmer knows, the primary audience for your code is human readers.
As for performance, if/else is slower than a modern exception implementation. Modern implementations incur literally zero overhead except when an exception is actually thrown. If your exceptions truly represent "exceptional" cases, this can be a significant performance difference.
Some languages (for example C++) don't allow return values in certain cases. For example, in C++ constructors don't have return values (even void), so the only way to signal an error in a constructor is to throw an exception.
Look, there isn't anything you can do with exceptions you can't do with hand-coded 8086 assembler. (Turing complete!) Except that hand-coded assembler isn't a very good tool for a 100K lines-of-code project unless you are the best coder in the Milky Way, if then. Experience shows a number of situations where the idiom of exception handling yields the most robust code written by ordinary humans. Sharptooth's example of ctors is good. So are errors that need to propagate up the call stack. Edit: And how many people actually check that malloc didn't fail every single time? Better to throw an OutOfMemoryException.
Exceptions are just what they are named for: Exceptional events or situations which break the current flow.
You should use exceptions to indicate that something nasty and very exceptional has happened. Let's say you have a class which reads your configuration file. Exceptional situations could be:
The file cannot be found.
The file exists, but cannot be read.
The file was deleted in the middle of
a read operation.
You could handle all of these with if-else blocks but it is much simpler to do with exceptions.
Of course, you can write code that does not use exceptions. However, if you do, you would have make sure that any function that could issue an error is handled properly.
For me, the biggest advantage of having exceptions is that I can write straight code that simply assumes that all functions succeeds, knowing that upper layers will take care of reporting errors.
Concretely, take the following fictitious function to work with text in an editor:
do-stuff:
backward-line 10
x = point
search-for "FOO"
return buffer-substring x point
Both "backward-line" and "search-for" could fail. If I would have to handle errors myself, I would have to check them. In addition, I would have to invent a side-channel to report to my caller that an error occurred. As I said, it can be done, but it would be a lot messier.
Consider a following real-life example. You have a complex recursive function and need to get out of all the calls currently in progress on some condition.
void complexFunction()
{
//do stuff, then
if( timeToBailOut() ) {
// what? how would you get out of all instances at once?
}
}
You would need to write those if statements very carefully, perhaps introduce a special return value, check for that value at all times, that would complicate you code greatly. You can easily achieve that behavior with exceptions.
if( timeToBailOut() ) {
throw BailOutException();
}
There is nothing that can be done with exception handling that can't be done without it, however, some situations would be a pain without exception handling.
Example: a() calls b() and b() calls c(). An error might occur in c() that has to be handled in a(). Without exception handling you would have to deal with it by using sentinel return values in b() and c(), and extra checking code in b(). You can imagine how much more code you would need to write if the error has to be passed through more levels from what caused it to what needs to handle it.
This is a short version of my answer to a different question that was asked here earlier and migrated.
Why doesn't F# naturally support a try/with/finally block?
Doesn't it make sense to try something, deal with whatever exception it throws, at least to log the exception, and then be sure that some code executes after all that?
Sure, we can do
try
try
...
with ex -> ...
finally
...
But that seems too artificial, it clearly demonstrates that "F# is against try/with/finally". Why is that?
As somebody already mentioned, you would usually use try-with-finally to make sure that you properly release all resources in case of an exception. I think in most of the cases you can do this more easily using the use keyword:
let input =
try
use stream = new FileStream("C:\temp\test.txt");
use rdr = new StreamReader(stream);
Some(rdr.ReadToEnd())
with :? IOException as e ->
logError(e)
None
I think this is mostly the reason why you don't need try-with-finally as often as you would in other languages. But of course, there are some situations where you may need it (but you could of course avoid that by creating instance of IDisposable using object expressions (which is syntactically very easy). But I think this is so rare that the F# team doesn't really need to worry about this.
Orthogonality? You can simply nest a try-with inside a try-finally, as you show. (This is what happens at the IL level anyway, I think.)
That said, try-with-finally is something that we may consider in a future version of the language.
Personally I have only run into wanting it a couple times, but when you do need it, it is a little bothersome to have to do the extra nesting/indent. In general I find that I rarely write exception handling code, and it's usually just one or the other (e.g. a finally to restore an invariant or other transactional semantics, or a 'catch' near the top of an app to log an exception or show the user diagnostics).
But I don't think there's a lot to 'read in to' regarding the language design here.
Without going into great detail because the great details are in
[Expert .NET 2.0 IL Assembler] 01 by Serge Lidin
See: Ch. 14, Managed Exception Handling
"The finally and fault handlers cannot peacefully coexist with other handlers, so if a guarded block has a finally or fault handler, it cannot have anything else. To combine a finally or fault handler with other handlers, you need to nest the guarded and handler bocks within other guarded blocks, ..., so that each finally or fault handler has its own personal guarded block."
pg. 300
A try/catch uses a fault handler and a try/finally uses a finally handler.
See: ILGenerator.BeginFaultBlock Method
If you emit a fault handler in an exception block that also contains a
catch handler or a finally handler, the resulting code is
unverifiable.
So, all of the .net languages could be consider to be using syntactic sugar and since F# is so new, they just haven't implemented it yet. No harm no foul.
But that seems too artificial, it clearly demonstrates that "F# is against try/with/finally". Why is that?
I guess F# might be "against" exception-handling at all. For the sake of .NET interoperability, it has to support them, but basically, there is no exception-handling* in functional programming.
Throwing/Catching exceptions means performing "jumps to nowhere" that aren't even noticed by the type system which is both fundamentally against the functional philosophy.
You can use purely functional (monadic) code to wrap exceptions. All errors are handled through values, in terms of the underlying type system and free of jumps/side effects.
Instead of writing a function
let readNumber() : int = ...
that may throw arbitrary exceptions, you'll simply state
let readNumber() : int option = ...
which makes this point automatically clear by its type signature.
*This doesn't mean we don't handle exceptional situations, it's just about the kind of exception-handling in .NET/C++.
I will clarify my comment in this answer.
I maintain there is no reason to assume that you want to catch exceptions and finalize some resources at the same level. Perhaps you got used to do it that way in a language in which it was convenient to handle both at the same time, but it's a coincidence when it happens. The finalization is convenient when you do not catch all exceptions from the inner block. try...with is for catching exceptions so that the computation can continue normally. There simply is no relationship between the two (if anything, they go in opposite directions: are you catching the exception, or letting it go through?).
Why do you have to finalize anything at all? Shouldn't the GC be managing unreferenced resources for you? Ah... but the language is trying to give you access to system primitives which work with side-effects, with explicit allocations and de-allocations. You have to de-allocate what you have allocated (in all cases)... Shouldn't you be blaming the rotten interface that the system is providing instead of F#, which is only the messenger in this case?
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
Google's Go language has no exceptions as a design choice, and Linus of Linux fame has called exceptions crap. Why?
Exceptions make it really easy to write code where an exception being thrown will break invariants and leave objects in an inconsistent state. They essentially force you to remember that most every statement you make can potentially throw, and handle that correctly. Doing so can be tricky and counter-intuitive.
Consider something like this as a simple example:
class Frobber
{
int m_NumberOfFrobs;
FrobManager m_FrobManager;
public:
void Frob()
{
m_NumberOfFrobs++;
m_FrobManager.HandleFrob(new FrobObject());
}
};
Assuming the FrobManager will delete the FrobObject, this looks OK, right? Or maybe not... Imagine then if either FrobManager::HandleFrob() or operator new throws an exception. In this example, the increment of m_NumberOfFrobs does not get rolled back. Thus, anyone using this instance of Frobber is going to have a possibly corrupted object.
This example may seem stupid (ok, I had to stretch myself a bit to construct one :-)), but, the takeaway is that if a programmer isn't constantly thinking of exceptions, and making sure that every permutation of state gets rolled back whenever there are throws, you get into trouble this way.
As an example, you can think of it like you think of mutexes. Inside a critical section, you rely on several statements to make sure that data structures are not corrupted and that other threads can't see your intermediate values. If any one of those statements just randomly doesn't run, you end up in a world of pain. Now take away locks and concurrency, and think about each method like that. Think of each method as a transaction of permutations on object state, if you will. At the start of your method call, the object should be clean state, and at the end there should also be a clean state. In between, variable foo may be inconsistent with bar, but your code will eventually rectify that. What exceptions mean is that any one of your statements can interrupt you at any time. The onus is on you in each individual method to get it right and roll back when that happens, or order your operations so throws don't effect object state. If you get it wrong (and it's easy to make this kind of mistake), then the caller ends up seeing your intermediate values.
Methods like RAII, which C++ programmers love to mention as the ultimate solution to this problem, go a long way to protect against this. But they aren't a silver bullet. It will make sure you release resources on a throw, but doesn't free you from having to think about corruption of object state and callers seeing intermediate values. So, for a lot of people, it's easier to say, by fiat of coding style, no exceptions. If you restrict the kind of code you write, it's harder to introduce these bugs. If you don't, it's fairly easy to make a mistake.
Entire books have been written about exception safe coding in C++. Lots of experts have gotten it wrong. If it's really that complex and has so many nuances, maybe that's a good sign that you need to ignore that feature. :-)
The reason for Go not having exceptions is explained in the Go language design FAQ:
Exceptions are a similar story. A
number of designs for exceptions have
been proposed but each adds
significant complexity to the language
and run-time. By their very nature,
exceptions span functions and perhaps
even goroutines; they have
wide-ranging implications. There is
also concern about the effect they
would have on the libraries. They are,
by definition, exceptional yet
experience with other languages that
support them show they have profound
effect on library and interface
specification. It would be nice to
find a design that allows them to be
truly exceptional without encouraging
common errors to turn into special
control flow that requires every
programmer to compensate.
Like generics, exceptions remain an
open issue.
In other words, they haven't yet figured out how to support exceptions in Go in a way that they think is satisfactory. They are not saying that Exceptions are bad per se;
UPDATE - May 2012
The Go designers have now climbed down off the fence. Their FAQ now says this:
We believe that coupling exceptions to a control structure, as in the try-catch-finally idiom, results in convoluted code. It also tends to encourage programmers to label too many ordinary errors, such as failing to open a file, as exceptional.
Go takes a different approach. For plain error handling, Go's multi-value returns make it easy to report an error without overloading the return value. A canonical error type, coupled with Go's other features, makes error handling pleasant but quite different from that in other languages.
Go also has a couple of built-in functions to signal and recover from truly exceptional conditions. The recovery mechanism is executed only as part of a function's state being torn down after an error, which is sufficient to handle catastrophe but requires no extra control structures and, when used well, can result in clean error-handling code.
See the Defer, Panic, and Recover article for details.
So the short answer is that they can do it differently using multi-value return. (And they do have a form of exception handling anyway.)
... and Linus of Linux fame has called exceptions crap.
If you want to know why Linus thinks exceptions are crap, the best thing is to look for his writings on the topic. The only thing I've tracked down so far is this quote that is embedded in a couple of emails on C++:
"The whole C++ exception handling thing is fundamentally broken. It's especially broken for kernels."
You'll note that he's talking about C++ exceptions in particular, and not exceptions in general. (And C++ exceptions do apparently have some issues that make them tricky to use correctly.)
My conclusion is that Linus hasn't called exceptions (in general) "crap" at all!
Exceptions are not bad per se, but if you know they are going to happen a lot, they can be expensive in terms of performance.
The rule of thumb is that exceptions should flag exceptional conditions, and that you should not use them for control of program flow.
I disagree with "only throw exceptions in an exceptional situation." While generally true, it's misleading. Exceptions are for error conditions (execution failures).
Regardless of the language you use, pick up a copy of Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries (2nd Edition). The chapter on exception throwing is without peer. Some quotes from the first edition (the 2nd's at my work):
DO NOT return error codes.
Error codes can be easily ignored, and often are.
Exceptions are the primary means of reporting errors in frameworks.
A good rule of thumb is that if a method does not do what its name suggests, it should be considered a method-level failure, resulting in an exception.
DO NOT use exceptions for the normal flow of control, if possible.
There are pages of notes on the benefits of exceptions (API consistency, choice of location of error handling code, improved robustness, etc.) There's a section on performance that includes several patterns (Tester-Doer, Try-Parse).
Exceptions and exception handling are not bad. Like any other feature, they can be misused.
From the perspective of golang, I guess not having exception handling keeps the compiling process simple and safe.
From the perspective of Linus, I understand that kernel code is ALL about corner cases. So it makes sense to refuse exceptions.
Exceptions make sense in code were it's okay to drop the current task on the floor, and where common case code has more importance than error handling. But they require code generation from the compiler.
For example, they are fine in most high-level, user-facing code, such as web and desktop application code.
Exceptions in and of themselves are not "bad", it's the way that exceptions are sometimes handled that tends to be bad. There are several guidelines that can be applied when handling exceptions to help alleviate some of these issues. Some of these include (but are surely not limited to):
Do not use exceptions to control program flow - i.e. do not rely on "catch" statements to change the flow of logic. Not only does this tend to hide various details around the logic, it can lead to poor performance.
Do not throw exceptions from within a function when a returned "status" would make more sense - only throw exceptions in an exceptional situation. Creating exceptions is an expensive, performance-intensive operation. For example, if you call a method to open a file and that file does not exist, throw a "FileNotFound" exception. If you call a method that determines whether a customer account exists, return a boolean value, do not return a "CustomerNotFound" exception.
When determining whether or not to handle an exception, do not use a "try...catch" clause unless you can do something useful with the exception. If you are not able to handle the exception, you should just let it bubble up the call stack. Otherwise, exceptions may get "swallowed" by the handler and the details will get lost (unless you rethrow the exception).
Typical arguments are that there's no way to tell what exceptions will come out of a particular piece of code (depending on language) and that they are too much like gotos, making it difficult to mentally trace execution.
http://www.joelonsoftware.com/items/2003/10/13.html
There is definitely no consensus on this issue. I would say that from the point of view of a hard-core C programmer like Linus, exceptions are definitely a bad idea. A typical Java programmer is in a vastly different situation, though.
Exceptions aren't bad. They fit in well with C++'s RAII model, which is the most elegant thing about C++. If you have a bunch of code already that's not exception safe, then they're bad in that context. If you're writing really low level software, like the linux OS, then they're bad. If you like littering your code with a bunch of error return checks, then they not helpful. If you don't have a plan for resource control when an exception is thrown (that C++ destructors provides) then they're bad.
A great use-case for exceptions is thus....
Say you are on a project and every controller (around 20 different major ones) extends a single superclass controller with an action method. Then every controller does a bunch of stuff different from each other calling objects B, C, D in one case and F, G, D in another case. Exceptions come to the rescue here in many cases where there was tons of return code and EVERY controller was handling it differently. I whacked all that code, threw the proper exception from "D", caught it in the superclass controller action method and now all our controllers are consistent. Previously D was returning null for MULTIPLE different error cases that we want to tell the end-user about but couldn't and I didn't want to turn the StreamResponse into a nasty ErrorOrStreamResponse object (mixing a data structure with errors in my opinion is a bad smell and I see lots of code return a "Stream" or other type of entity with error info embedded in it(it should really be the function returns the success structure OR the error structure which I can do with exceptions vs. return codes)....though the C# way of multiple responses is something I might consider sometimes though in many cases, the exception can skip a whole lot of layers(layers that I don't need to clean up resources on either).
yes, we have to worry about each level and any resource cleanup/leaks but in general none of our controllers had any resources to clean up after.
thank god we had exceptions or I would have been in for a huge refactor and wasted too much time on something that should be a simple programming problem.
Theoretically they are really bad. In perfect mathematical world you cannot get exception situations. Look at the functional languages, they have no side effects, so they virtually do not have source for unexceptional situations.
But, reality is another story. We always have situations that are "unexpected". This is why we need exceptions.
I think we can think of exceptions as of syntax sugar for ExceptionSituationObserver. You just get notifications of exceptions. Nothing more.
With Go, I think they will introduce something that will deal with "unexpected" situations. I can guess that they will try to make it sound less destructive as exceptions and more as application logic. But this is just my guess.
The exception-handling paradigm of C++, which forms a partial basis for that of Java, and in turn .net, introduces some good concepts, but also has some severe limitations. One of the key design intentions of exception handling is to allow methods to ensure that they will either satisfy their post-conditions or throw an exception, and also ensure that any cleanup which needs to happen before a method can exit, will happen. Unfortunately, the exception-handling paradigms of C++, Java, and .net all fail to provide any good means of handling the situation where unexpected factors prevent the expected cleanup from being performed. This in turn means that one must either risk having everything come to a screeching halt if something unexpected happens (the C++ approach to handling an exception occurs during stack unwinding), accept the possibility that a condition which cannot be resolved due to a problem that occurred during stack-unwinding cleanup will be mistaken for one which can be resolved (and could have been, had the cleanup succeeded), or accept the possibility that an unresolvable problem whose stack-unwinding cleanup triggers an exception that would typically be resolvable, might go unnoticed as code which handles the latter problem declares it "resolved".
Even if exception handling would generally be good, it's not unreasonable to regard as unacceptable an exception-handling paradigm that fails to provide a good means for handling problems that occur when cleaning up after other problems. That isn't to say that a framework couldn't be designed with an exception-handling paradigm that could ensure sensible behavior even in multiple-failure scenarios, but none of the top languages or frameworks can as yet do so.
I havent read all of the other answers, so this ma yhave already been mentioned, but one criticism is that they cause programs to break in long chains, making it difficult to track down errors when debugging the code. For example, if Foo() calls Bar() which calls Wah() which calls ToString() then accidentily pushing the wrong data into ToString() ends up looking like an error in Foo(), an almost completely unrelated function.
For me the issue is very simple. Many programmers use exception handler inappropriately. More language resource is better. Be capable to handle exceptions is good. One example of bad use is a value that must be integer not be verified, or another input that may divide and not be checked for division of zero... exception handling may be an easy way to avoid more work and hard thinking, the programmer may want to do a dirty shortcut and apply an exception handling... The statement: "a professional code NEVER fails" might be illusory, if some of the issues processed by the algorithm are uncertain by its own nature. Perhaps in the unknown situations by nature is good come into play the exception handler. Good programming practices are a matter of debate.
Exception not being handled is generally bad.
Exception handled badly is bad (of course).
The 'goodness/badness' of exception handling depends on the context/scope and the appropriateness, and not for the sake of doing it.
Okay, boring answer here. I guess it depends on the language really. Where an exception can leave allocated resources behind, they should be avoided. In scripting languages they just desert or overjump parts of the application flow. That's dislikable in itself, yet escaping near-fatal errors with exceptions is an acceptable idea.
For error-signaling I generally prefer error signals. All depends on the API, use case and severity, or if logging suffices. Also I'm trying to redefine the behaviour and throw Phonebooks() instead. The idea being that "Exceptions" are often dead ends, but a "Phonebook" contains helpful information on error recovery or alternative execution routes. (Not found a good use case yet, but keep trying.)