So sometimes I like to use try/catch for control flow.
I've read a few articles about this and it seems to me most criticisms come down to "I once read that GOTO is bad and this is like local GOTO so this is bad too".
Fuck you. You know what else is like GOTO? Pretty much any other control structure there is. The article most people are referring to for their anti-GOTO dogma was written like 40 years ago. Back then if / while / for
control structures were just being introduced and Dijkstra was railing against the terrible GOTO-pasta programming style of his time. No one's going back to that, even if we could. Luckily it seems lately the pendulum has been swinging back towards sanity on this front.
But ok, let's get back to try/catch. The situation I most often use try/catch for control flow is in form validation. User submits a form. You need to do basic sanity checks / data coercion, then a deeper business rules validation and finally, try to persist it into database. Each of these steps can produce a failure state. Some of them need to be displayed to the user ("Name already exists"). Some are potential bugs and need to produce a 550 page and a log entry. Example:
First, define a few custom Exception types
public class MyException : Exception {
public MyException(string message = null) : base(message) {
}
public MyException(Exception ex) : base(ex.Message, ex) {
}
}
public class DisplayException : MyException {
public DisplayException(string message = null) : base(message) {
}
public DisplayException(BLException ex) : base(ex) {
}
}
Then, handle user's POST. For example, in WebForms Save() method
public void Save() {
try {
// Sanity checks
if (this.Name == null || this.Date == null) {
throw new DisplayException("Invalid post data");
}
int code;
if (!int.TryParseInt(this.Code, out code)) {
throw new DisplayException("Code must be a number");
}
var item = new BLItem {
Name: this.Name,
Date: this.Date,
Code: code
};
// Validate and save. Throws its own exceptions
try {
BLManager.Save(item);
}
catch (BLException ex) {
// Convert thrown BL exception into a type
// used for control flow in the interface
throw new DisplayException(ex);
}
this.DisplayMessage("Data saved", MessageType.Success);
}
catch (Exception ex) {
if (ex is DisplayException) {
// We can just display what happened to the user
this.DisplayMessage(ex.Message, MessageType.Danger);
} else {
// This wasn't user's fault. Log it for later
this.LogException(ex);
this.RedirectToCode(550);
}
}
}
I didn't include that, but BLManager would use similar syntax to convert database's DuplicateKeyError
into some kind of "Item with name xxx already exists
" error.
So, what do you think? Am I the real WTF?