A clean way of doing it is taking all the return values and making them input variables by ref to the function and retuning a boolean that is success or fail. In some coding styles that is pretty standard. Another way of doing is raising your own exception in the catch block. You can use an application exception or create your own custom .net exception, you can put your own message, or pass in the first exception into the new exception. You can even re-throw the same exception you caught. Creating a custom exception allows you to catch it separately (if you define a catch block with a specific type of exception, you will only catch that type of exception.) You can have more than one catch block for different exception types.
Personally, I try to only put try catch blocks at the highest possible level in the code, at the event handler level. For example in an OnClick event of an Icommand. Any exception that happens in a call to a procedure will automatically bubble up to the highest level to be caught. So you handler in procedure 1 will catch exceptions raised in procedure 2. Exception handling can be performance intensive and putting it in private functions or worse in non user interface classes is usually not an good idea (exception made when an edit operation needs to be terminated correctly on exception.) The reason for that is imagine if someone re-uses your code and calls it in a loop and it generates and catches/logs/deals with hundreds of exceptions. This also allows you to deal with exceptions differently with the same code base for example, on a form (errorProvider), an ArcGIS command or tool (message box), an edit session (abort operation), an exe (write to console), a service (write to event log or database.)
Also, if you know an exception condition is likely to occur, it is not an exception any more, you are better off checking for the condition than using the exception handling.