I think exceptions are kind of cursed by their name. To treat exceptions correctly you have to constantly keep in mind that they are misnamed. And you have to deal with type systems that do not treat them as precisely as they treat return values. And on top of that you have to coexist with people who impose all kinds of semantic limitations on their mental model of exceptions, such as that they have to be "exceptional." Exceptions have to be treated as happening all the time, and in strongly typed languages (such as Scala, my hammer) you have to keep in mind that the type system has this huge loophole built in that is very important and that the type system gives you very little help with.
(The most obvious example that utterly confounds semantic limitations on exceptions is opening a file. Programmers accustomed to working on a certain kind of system find it quite natural to regard a missing file as "exceptional", even fatal — for them a missing file means the install or even the very build is corrupted. Programmers who work on a different kind of software may regard missing files as completely routine, maybe a commonplace result of user error. If these two groups of programmers believe exceptions are "exceptional" they will never agree on the contract for opening a file. Another example is deciding whether something that happens 0.0001% of the time is exceptional. Some programmers will regard that as exceptional while others will consider it utterly routine and regard it as a failure of discipline to believe otherwise.)
(The logical consequence of insisting on "exceptionality" is that you need two sets of basic libraries for everything and may need to mass refactor your code from one to the other as your use cases change. This is a needless imposition that offers no recompense for its offensiveness to good taste and good sense.)
The great merit of exceptions is that they remove boilerplate and make it trivial (nothing is more trivial and readable than no code) to express "I am not handling this, someone else must" which makes it quite easy to e.g. signal "400 Bad Request" from deep within some code that parses a request entity.
Personally, I think that for now it is best to prefer exceptions for writing lots of useful code quickly and concisely and to prefer strongly-typed, expressively-typed return values for achieving a higher level of care and reliability. But I look forward to a better linguistic solution that combines the virtues of both these approaches — and I have to admit that in my ignorance of many important languages I may be overlooking an already existing solution. I am reminded that in the early 2000s I would have relegated all static typing to the "verbose boring highest reliability required" category and then type inference and other ergonomic advances converted me to statically typed languages such as Scala as the best way to pump out lots of valuable working code. I'm looking forward to a linguistic solution to this problem.
(The most obvious example that utterly confounds semantic limitations on exceptions is opening a file. Programmers accustomed to working on a certain kind of system find it quite natural to regard a missing file as "exceptional", even fatal — for them a missing file means the install or even the very build is corrupted. Programmers who work on a different kind of software may regard missing files as completely routine, maybe a commonplace result of user error. If these two groups of programmers believe exceptions are "exceptional" they will never agree on the contract for opening a file. Another example is deciding whether something that happens 0.0001% of the time is exceptional. Some programmers will regard that as exceptional while others will consider it utterly routine and regard it as a failure of discipline to believe otherwise.)
(The logical consequence of insisting on "exceptionality" is that you need two sets of basic libraries for everything and may need to mass refactor your code from one to the other as your use cases change. This is a needless imposition that offers no recompense for its offensiveness to good taste and good sense.)
The great merit of exceptions is that they remove boilerplate and make it trivial (nothing is more trivial and readable than no code) to express "I am not handling this, someone else must" which makes it quite easy to e.g. signal "400 Bad Request" from deep within some code that parses a request entity.
Personally, I think that for now it is best to prefer exceptions for writing lots of useful code quickly and concisely and to prefer strongly-typed, expressively-typed return values for achieving a higher level of care and reliability. But I look forward to a better linguistic solution that combines the virtues of both these approaches — and I have to admit that in my ignorance of many important languages I may be overlooking an already existing solution. I am reminded that in the early 2000s I would have relegated all static typing to the "verbose boring highest reliability required" category and then type inference and other ergonomic advances converted me to statically typed languages such as Scala as the best way to pump out lots of valuable working code. I'm looking forward to a linguistic solution to this problem.