Additional Blogs by Members
cancel
Showing results for 
Search instead for 
Did you mean: 
former_member184549
Participant
Exception classes can assist in easing the load of ABAP development work, by reducing the number of things that should be occupying developer mindspace. With exception classes you don't have to worry about passing sy-subrc return codes up and down into your code blocks (and having the associated pain of being forced to create global or local variables to hold sy-subrc values that you're passing up and down). However, should you attempt to raise your own custom exception classes, ABAP forces you into creating extra variables, either as attributes on the exception class or in the block of code raising the exception. In other words, you can either do this:

(1) Create an unnecessary variable and do an unnecessary assignment

data: ld_user type xubname,
     ld_msgv1 type symsgv.

*unnecessary assignment
ld_msgv1 = ld_user.

*exception raising - must use ld_msgv1 as parameter as it is typed correctly
raise exception type zcx_example
exporting textid = zcx_example=>error_example
           gd_msgv1 = ld_msgv1.

or

(2) Add a new attribute of type XUBNAME to the exception class. You cannot have an attribute of type ANY.

Either way, it makes more work for you the programmer! Why should you have to create unnecessary variables or attributes just to raise an exception, this really should be easy to do. Later on in this post I discuss a method for overcoming this limitation, but before I do so, let's discuss a custom hierarchy of exception classes at the company that owns the fictitious xxx namespace:

Exception class hierarchy

In the above diagram /xxx/cx_root_ex is an abstract root exception class for the company. The benefit of having this custom root class is that you can trap all the customer exceptions easily, so for example for every custom program you would write the following in the main code block (or start-of-selection event in ABAP-speak) to catch all custom exceptions:

try.
  perform foo.
  perform bar.

  catch /xxx/cx_root_ex.
*   hopefully catches all custom exceptions at our company thrown by foo and bar

endtry.

In the scheme we use at my current client, the /xxx/cx_root_ex root exception class inherits from CX_STATIC_CHECK so that all of the custom classes get statically syntax checked. If dynamic checking was a requirement then the custom root exception class could inherit from CX_DYNAMIC_CHECK. Going back to the above diagram two (instantiable) classes inherit from the root exception class, these actually represent different SAP modules, so all materials management exceptions would either be in /xxx/cx_mm_ex or inherit from that class. For example, in the above diagram the materials exception class /xxx/cx_rt_materials_ex holds all material specific exceptions. As developers develop new exceptions they add their texts into the texts on the relevant exception class for that module. By the way, the where-used functionality on exception class messages is more thorough than it is for message texts created in message maintenance (transaction SE91) as the where-used for regular SE91 message maintenance will fail to find the following:

      add_message_to_applog(
        exporting
          id_ebelp        = ls_item-ebelp
          id_msgid        = '00'
          id_msgty        = 'E'
          id_msgno        = '004'
          id_msgv1        = ld_msgv1
        changing
          ct_message_link = mt_message_link ).

So, an added incentive to use exception classes!

Now, how does one raise an exception and provide a parameter to the raise that can be of any type? The solution offered here is to place the following code in the CLASS_CONSTRUCTOR of the root exception class (/xxx/cx_root_ex as per the example above, modify for your own namespace or use zcx_root_ex):

method class_constructor.

*The interface MSGV* parameters for the constructor must be defined as
* type ANY, but because exception classes behave differently to
* regular classes this cannot be done the regular way

*Local data
  types: begin of ty_itab,
           line(256),
         end of ty_itab,

         begin of ty_progtab,
           report(256),
        end of ty_progtab.

  data: lt_itab    type standard table of ty_itab,
        lt_progtab type standard table of ty_progtab,
        lt_range1 type range of trdir-name,
        ls_range1 like line of lt_range1,
        lt_range2 type range of trdir-name,
        ls_range2 like line of lt_range2,
        ld_modify_flag type boole_d.

  field-symbols:     type ty_itab,
                  type ty_progtab.

  constants: lc_xxx_exception type trdir-name value '/XXX/CX*',
             lc_constr_incl   type trdir-name value '*=CU',
             lc_replace1      type ty_itab-line value
               '!MD_MSGV1 type SYMSGV optional',
             lc_replace2      type ty_itab-line value
               '!MD_MSGV2 type SYMSGV optional',
             lc_replace3      type ty_itab-line value
               '!MD_MSGV3 type SYMSGV optional',
             lc_replace4      type ty_itab-line value
               '!MD_MSGV4 type SYMSGV optional',
             lc_replacee      type ty_itab-line value 'SYMSGV',
             lc_replacer      type ty_itab-line value 'ANY',
             lc_true          type boole_d      value 'X'.

*Get a list of classes to modify
  ls_range1-sign   = 'I'.
  ls_range1-option = 'CP'.
  ls_range1-low    = lc_xxx_exception.
  append ls_range1 to lt_range1.

  ls_range2-sign   = 'I'.
  ls_range2-option = 'CP'.
  ls_range2-low    = lc_constr_incl.
  append ls_range2 to lt_range2.

  select name    from trdir
                 appending table lt_progtab
                 where name in lt_range1
                 and   name in lt_range2.

*Modify just the type of the message parameter(s) in the constructor
  loop at lt_progtab assigning .

    refresh lt_itab.
    read report -report
     into lt_itab.

    loop at lt_itab assigning .
      if -line cs lc_replace1
      or -line cs lc_replace2
      or -line cs lc_replace3
      or -line cs lc_replace4.

        replace lc_replacee in -line with lc_replacer.
        ld_modify_flag = lc_true.
      endif.
    endloop.

    if ld_modify_flag = lc_true.
      insert report -report
       from lt_itab keeping directory entry.

      commit work."only gets infrequently called if something to fix
    endif.

  endloop.

endmethod.

So what the above code does is it dynamically alters the code of all of the custom exception classes that were developed at the fictitious XXX company and changes four parameters on the constructor method of these classes from a type SYMSGV to a type ANY. Unlike regular ABAP classes where the programmer can alter the type of the parameters on the constructor method, for an exception class you get the message "Cannot edit the constructor of an exception class" if you attempt to do so. This is true for systems from ERP2004 to Netweaver 7.0, perhaps in the future SAP will become aware of the problem and allow editing of constructor's of exception classes! In the meantime, if you follow the above scheme you need to add four parameters onto your root exception class constructor methods. Creating four attributes MD_MSGV1 through to MD_MSGV4 on the root exception class (/xxx/cx_root_ex in the example above) and typing them as type SYMSGV is a requirement for the above code to work. All your custom exception classes, because they inherit in some way from the abstract root exception class will also inherit these four parameters on their constructor methods, and this is an example of how you would use them in your own program code:

*...exception handling - error determining day of week
    raise exception type /xxx/cx_dc_ex
               exporting textid = /xxx/cx_dc_ex=>error_week_get_first_day
                       md_msgv1 = sy-subrc
                       md_msgv2 = 'DAY_IN_WEEK'
                       md_msgv3 = sy-datum.

Isn't it a lot easier if you don't have to worry about the static type check when you raise your exceptions? Of course, in the exception class itself you will need to define a text as follows:

ERROR_WEEK_GET_FIRST_DAY:
Error &MD_MSGV1& occured when calling function &MD_MSGV2& with &MD_MSGV3&

The solution described above allowing you to have dynamically typed fields on your exception classes is a pragmatic one - it does have the shortfall that after regenerating any of the custom exception classes (as a consequence of you adding new texts perhaps) you will need to somehow call the class_constructor method to readjust your exception classes, this can be easily done by "testing" an exception class: Click on the "spanner" icon on the first screen of SE24 (the class builder):

Click the spanner icon on SE24

In the above diagram, the button you need to click is the third from the left. Don't forget to do this!! If you don't then your programs will show as having a syntax error, which they do not.

Although it has this known disadvantage it is not necessary to do the readjustment on non-development systems - the readjustment is only required after changing an exception class on the development system. The above workaround is messy from the point of view that some may regard it as bad form for an ABAP program to modify itself dynamically, however the benefits gained from the approach described are real and worthwhile, especially when designing a new set of standards for ABAP development at a fresh site - exception classes are useful, especially when used in conjunction with other utility classes. In the next blog post, I will be discussing how you can develop a custom application log class that you can include in all of your programs that can be used to provide a uniform way to display all those complicated exception class texts you are now raising in all your programs!