Thursday, 13 April 2017

How to write a “correct” program rejected by compiler: Exception handling in Java and in ABAP

Recently I am prepare an internal training and have been racking my brains to find a real example for my attendees about writting a “correct” program which gets rejected by compiler. The tricky point here is as a programmer, we always treat compiler as our god: if compiler complains that our program has errors, then we are wrong. Programmers tend to believe in that compiler will NEVER make mistakes.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

And finally I got inspiration from Alexandru Constantin Bledea’s github.

Checked and unchecked exception in Java


Let’s see the following Java code:
package exception;
import java.sql.SQLException;
public class ExceptionForQuiz<T extends Exception> {
private void pleaseThrow(final Exception t) throws T {
throw (T) t;
}
public static void main(final String[] args) {
try {
new ExceptionForQuiz<RuntimeException>().pleaseThrow(new SQLException());
}
catch( final SQLException ex){
System.out.println("Jerry print");
ex.printStackTrace();
}
}
}

What result this program will generate?

Let’s analyze it step by step.

1. The class ExceptionForQuiz<T extends Exception> uses a generic typing syntax extends to declare a bound that T only accepts Exception and its sub classes.
As a result in my main method code the creation of new ExceptionForQuiz via the below code is legal since according to JDK source code, RuntimeException is subclass of Exception.

new ExceptionForQuiz<RuntimeException>()

Also keep in mind that RuntimeException is a kind of Unchecked exception ( do not need to be declared in method where it might be raised ), which will be compared with ABAP exception later.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

2. According to Java Document, the type parameter in generic type declaration will be replaced by its bound during compile, in my exception RuntimeException will be replaced by Exception. 

As a result, let’s forget about the try – catch for the moment.
This is original code:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

This is the code decompiled from ExceptionForQuiz.class:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

You can observe the fact of type erasure clearly.
You can also check the byte code by command javap, where the RuntimeException is erased.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

3. Now let’s see the result of this quiz.

The correct answer is: this program cannot pass compile! Compiler considers that SQLException could never have possibility to be raised from within TRY block. Unfortunately, this is what I would like to do: raise SQLException via method pleaseThrow and catch it in catch block.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

How to achieve my requirement?
Just change one line as highlighted below. Instead of catching SQLException, I now catch RuntimeException in order to pacify the compiler.
Now there is no compilation error any more but when executing it, I find the raised SQLException still cannot be caught as I expect. My println is not executed at all.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

I have to catch the Exception, the super class of all other exception instead ( like CX_ROOT in ABAP ), which is not a good practice in exception handling area.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Handleable and Unhandleable Exception in ABAP


You can find both definition in ABAP help.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Let’s now do the similar exercise as we did previous in Java. Create a method with below signature.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Now make the first test:

DATA(lo_test) = NEW zcl_exception_test( ).

  DATA: lo_exception TYPE REF TO cx_atd_exception.

  CREATE OBJECT lo_exception.

  WRITE:/ 'First test' COLOR COL_NEGATIVE.
  TRY.
      lo_test->please_throw( lo_exception ).
    CATCH cx_atd_exception INTO DATA(exception1).
      WRITE:/ 'Jerry: ' , exception1->get_text( ).
  ENDTRY.

It works as expected, the raised exception is caught.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Now the second test:
  
DATA: lo_exception2 TYPE REF TO cx_sql_exception.

  CREATE OBJECT lo_exception2.

  WRITE:/ 'Second test' COLOR COL_NEGATIVE.

  TRY.
      lo_test->please_throw( lo_exception2 ).
    CATCH cx_sql_exception INTO DATA(exception).
      WRITE:/ 'In catch sql exception:' , exception->get_text( ).
  ENDTRY.

The code is exactly the same as the first test, except that the exception is changed from CX_ATD_EXCEPTION to CX_SQL_EXCEPTION.
And result for the second test, 囧 …

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

In order to make this CX_SQL_EXCEPTION caught-able, I have to write the same dirty code as we did in Java example:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Although this time it works, but what is the reason of the different behaviors of these two examples?

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

The error message and short dump description have already given us a hint.
CX_ATD_EXCEPTION’s super class: CX_NO_CHECK. As its description says, it is not necessary to manually declare it in method signature using RAISING keyword.

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

And CX_SQL_EXCEPTION’s super class: CX_STATIC_CHECK

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

As a result now we have another solution:
Create another version of PLEASE_THROW method with RAISING keyword:

SAP ABAP Tutorials and Materials, SAP ABAP Guide, SAP ABAP Certifications

Use this new version and now CX_SQL_EXCEPTION could be caught:

 WRITE:/ 'Third test' COLOR COL_NEGATIVE.
  TRY.
      lo_test->please_throw2( lo_exception2 ).
    CATCH cx_sql_exception INTO DATA(exception3).
      WRITE:/ 'In catch sql exception:' , exception3->get_text( ).
  ENDTRY.

No comments:

Post a Comment