Regarding exception handling, letting internal exceptions define external behavior is perhaps a bad idea. The possible exception types can be wide and change over time as new parts or features are added. Example:
// pseudo-code
qry = new query(sql=theSql, dbConfig=DB_FOO);
if (! qry.Execute()) {
errMsg = "Something went wrong during your query. ";
if (qry.errorExceptionName=="DB_Busy") {
errMsg += "The database appears to be busy."; // append more
}
displayAlert(errMsg);
} else {
processQuery(qry.resultRows);
}
Here any fatal errors are caught inside the query object, but details are available if and when you wish to take advantage of them outside the query object. The query object (API) user doesn't have to know all possible exceptions types in order to handle an exception properly (or at least in a good-enough way).
It also bugged me that he was upset when the behavior they were relying on was an internal detail not included in the API's contract.
(Incidentally, the API itself did not change because it was something like func Read(in Reader) error, where error was a parent of all exceptions)
That's not incidental. In my opinion the authors of the API were completely fine changing the internal detail of which specific exception type was thrown because their public API never made a guarantee beyond it being an instance of error.
Future-friendly error-handling can indeed be tricky. I ran into this trying to make a lasting email-sending API. I was hesitant to depend on the API's specific exception types, and so considered mapping them to more general categories, yet still giving details for troubleshooting.
// pseudo-code
err = new Error(hasError=false); // innocent until proven guilty
try {
sendEmail(...);
catch (e in excptn1, excptn6, excptn7) // dummy names
err.hasError=true;
err.recipientProblem=true;
err.errorType = e;
catch (e in excptn2, excptn4, excptn9)
err.hasError=true;
err.contentProblem=true;
err.errorType = e;
catch (else)
err.hasError=true;
err.errorType = e;
}
...
return(err);
One could make an emumerable list of error categories, but in this case I wasn't even sure they were mutually exclusive because it still sends to the rest if one recipient is bad.