Throw an extra exception to avoid code duplication

First of all, I know that the standard answer will be that exceptions to control flow are never used. Although I completely agree with this, I thought for a long time about what I sometimes did, which I will discuss with the following pseudo-code:

try string keyboardInput = read() int number = int.parse(keyboardInput) //the conversion succeeds if(number >= 1000) //That not what I asked for. The message to display to the user //is already in the catch-block below. throw new NumberFormatException() //well, there IS something wrong with the number... catch(NumberFormatException ex) //the user entered text print("Please enter a valid number below 1000.") 

First of all, take the example of a very abstract way. This does not have to happen. The situation is simple:

User input should be limited and may go wrong in two ways, or with an abandoned exception defined by language, or by check. Both errors are reported by the user in the same way because they do not need to know the technical difference in what caused it.

I thought of several ways to solve it. To begin with, it would be better to throw a custom exception. The problem I am facing is that if I catch it locally, what should I do with the other exception? As a result of this, a custom exception would be the cause of the second catch block, in which the message would be copied just as well. My decision:

 //number is wrong throw new MyException() catch(NumberFormatException ex) throw new MyException() catch(MyException ex) { print("Please enter...") 

The meaning of exception names is everything here. This application of specially made exceptions is widely accepted, but essential . I did not do anything else at first glance: I have to go into the blocking block, although throwing a custom exception, not a standard library one.

Just like using an exception for the calling method (nor does it have a catch block for a custom exception) seems to make more sense. My method may be mistaken in that technically in two ways, but essentially in one way: by incorrect user input. Therefore, you should write a UserInputException and make the method throw it. New issue: what if this is the main application method?

Currently, I am not struggling with a specific application to implement this behavior, my question is purely theoretical and non-linguistic.

What is the best way to approach this?

+6
source share
6 answers

I think you have several ways to do this with minimal code duplication:

  • Use a boolean variable / save the exception: if you made an error in the general logic of a specific task, you exit the first sign of an error and process it in a separate error handling branch.

    Advantages: only one place for error handling; you can use any custom exception / error condition that you like.

    Disadvantages: the logic of what you are trying to achieve can be difficult to detect.

  • Create a general function that you can use to inform the user about the error (preliminary calculation / saving of all information that describes the general error, for example, a message to display the user), so you can simply make one function call when an error occurs.

    Advantages: the logic of your intent may be more understandable to code readers; you can use anu custom exception / error conditon which you like.

    Disadvantages: the error must be handled in separate places (although with pre-calculated / stored values ​​there are not many copy-pastes, however complex the information about the user part would be).

  • If the goal is clear, I don’t think throwing exceptions from your try block explicitly is a bad idea. If you do not want to throw one of the exceptions provided by the system, you can always create your own, which comes from one of them, so you only need the minimum number (preferably one) of catch blocks.

    Advantages: there is only one place to handle the error condition - if there is only one type of exception in the try block.

    Disadvantages: if more than one type of exception is selected, you need nested try-catch blocks (to propagate exceptions to the outermost one) or a very general (e.g. exception) catch block to avoid duplicate error reporting.

+1
source

I would consider the first exception as a low-level exception, and I would handle it (by translating in this case) at the point of call. I find this leads to code that is easier to maintain and reorganize later since you have fewer exception types to handle.

 try string keyboardInput = read() try int number = int.parse(keyboardInput) catch(NumberFormatException ex) throw MyException("Input value was not a number") //the conversion succeeds if(number >= 1000) throw MyException("Input value was out of range") catch(MyException ex) //the user entered text print( ex.ToString() ) print("Please enter a valid number below 1000.") 
+5
source

You do not need exceptions in this particular example.

 int number; if (int.TryParse(keyboardInput, out number) && number < 1000) // success else // error 

However, the situation you described is common in business software, and throwing an exception to achieve a uniform handler is quite common.

One such template is XML validation, followed by XSLT. On some systems, invalid XML is processed by throwing validation exceptions. On these systems, it is only natural to reuse existing exception handling in XSLT (which, of course, can detect some classes of data errors that cannot be determined by a specific validation language):

 <xsl:if test="@required = 'yes' and @prohibited = 'yes'> <xsl:message terminate='yes'>Error message</xsl:message> </xsl:if> 

It is important to note that if such conditions are extremely rare (they are expected to occur only during early integration testing and disappear if defects in other modules are fixed), most of the typical problems with using exceptions for flow control are not applied.

+1
source

As I see it:

Assuming there is no other way to parse your int, which does not throw an exception, your code, as it is now, is correct and elegant.

The only problem is that your code has been in some kind of loop, in which case you may be worried about the overhead of throwing unnecessary exceptions. In this case, you will have to compromise part of your code beauty in favor of processing the processing only if necessary.

 error=false; try { string keyboardInput = read(); int number = int.parse(keyboardInput); //the conversion succeeds if(number >= 1000) { //That not what I asked for. The message to display to the user //is already in the catch-block below. error=true; } catch(NumberFormatException ex) { //the user entered text error=true; } if (error) print("Please enter a valid number below 1000."); 

You might also wonder why you are trying to combine two errors into one. Instead, you could inform the user what mistake they made, which may be more useful in some cases:

 try { string keyboardInput = read(); int number = int.parse(keyboardInput); //the conversion succeeds if(number >= 1000) { //That not what I asked for. The message to display to the user //is already in the catch-block below. print("Please enter a number below 1000."); } catch(NumberFormatException ex) { //the user entered text print("Please enter a valid number."); } 
+1
source

How about approaching this validation problem by writing several validator classes that accept input and return errors or not. As for your fight against exceptions: put this logic in each validator and process it there in each case.

after that you will select the correct validators to use for input, collect their errors and process them.

advantages of this:

  • Validators do one thing, check one case
  • This is a validation function to decide how to handle errors. Do you break the first validation error or collect them all, and then deal with them?
  • You can write your code in such a way that the main validation function can check different types of input using the same code, just choosing the right validators using your favorite method.

and disadvantages:

  • In the end, you will write more code (but if you use java, this should be placed in a “useful” bucket)

here is some kind of pseudo code example:

 validate(input): validators = Validator.for(input.type) errors = [] for validator in validators: errors.push(validator.validate(input)) if errors: throw PoopException 

and some validators:

 MaxValidator extends IntValidator: validate(input): errors = [] errors.push(super.validate(input)) if input > 1000: errors.push("bleee!!!! to big!") return errors IntValidator: validate(input): try: int.parse(input) catch NumberFormatException: return ['not an int'] return [] 

of course, you will need to do some cheating so that the parent validator might return a valid version of the input to you, in this case the string "123" is converted to int, so max. a validator can process it, but it can be easily achieved by creating a validation check or some other magic.

+1
source

I cannot see this answer anywhere here, so I will just post it as a different perspective.

As we all know, you can really break the rules if you know them well enough, so you can use Exception throws to control the flow if you know that this is the best solution for your situation. From what I saw, this usually happens with some dumb frames ...

However, before Java 7 (which brought us the powerful multicatch construct ), this was my approach to avoid code repeating:

 try { someOffendingMethod(); } catch (Exception e) { if (e instanceof NumberFormatException || e instanceof MyException) { System.out.println("Please enter a valid number."); } } 

This is also a valid technique in C #.

+1
source

Source: https://habr.com/ru/post/915271/


All Articles