Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
hardyp180
Active Contributor
The Harlem Function Module Shuffle

How to improve error handling in function modules.

As really good series of blogs recently was “The Hassle with Function Modules, man” by Jorg Krause.

https://blogs.sap.com/2016/09/20/the-hassle-with-function-module-calls/

As with all good blogs it got me thinking, and made me look at something in a new way.

The blog was all about wrapping methods around function modules.

I had been against this, and what had poisoned me here was that in the early days of OO at SAP, I reckon the developers were paid by how many classes and methods they could create. So they took hundreds of function modules and put them inside static methods of classes with the exact same interface (or as close as they could get given the absence of TABLES).

That is why (I think) methods can have the “classic” exceptions which set SY-SUBRC, and can be called in a syntax almost identical to a function module call.

That whole exercise seemed pointless, as the methods were static they could not be subclassed, and you lost the ability to do an RFC call. Just to add insult to injury the name of the class/method often bore no relationship to the name of the function module, with both names being equally non-intuitive.

However looking at this again, the problem was not wrapping the subclass; it was wrapping it in a silly way. I can now turn the entire argument on its head.

Jorg mentioned the following benefits:-

  • Inline declaration become possible, they ae not for function modules

  • Better syntax checking, avoiding the dump at runtime from passing a variable of the wrong type

  • Possibly simplified interface

  • Less cluttered code by use of exception classes rather than long strings of exceptions

  • Exception classes are better than classic exceptions (I did not think so at first, after a year I was 100% convinced)


I have also thought of some more. First and foremost make all the methods (that wrap function modules) instance methods, so they can be subclassed, thus enabling unit tests. I think unit testing was also mentioned in the above blog as a further benefit.

Also, I think I want to wrap any (if there are any) standard SAP methods which raise classical exceptions in custom methods as well, so they can use exception classes and can be subclassed.

Name and Shame

In the example in the blog above were some class names like CA03 e.g. ZCL_CA03_DDIC. I have never been a big fan of secret codes in the name of anything, inside and outside of computer systems.

In Australia a Fringe Benefit Tax form is called a Fringe Benefit Tax form. In the UK it is called a P11D. I still would not call a class to deal with this ZCL_P11D even if I was still working in the UK. I would give it a name which described what it was actually about.

SAP Function group names are often utterly meaningless e.g. SPO3, and whilst the names of function modules are such that you can guess what they are doing at least 50% of the time, there is no consistent naming convention throughout the system.

The example I always give is BAPIS where there did not seem to be a firm decision what the “READ” operation should be called, leading to dozens of variations and thus making it difficult to do a search.

Another example would be, when it comes to database read buffering function modules:-

MARA_SINGLE_READ

SD_VBAK_SINGLE_READ

V_KNA1_SINGLE_READ

ANLA_READ_SINGLE

On the positive side, the word SINGLE almost always comes before the word READ. However even know I keep trying to insert the function VBAK_SINGLE_READ and forget the silly prefix and thus fail. There are also function modules with a similar purpose with an entirely different naming convention.

If I was going to wrap such functions, I would give a consistent naming convention, and also say MATERIAL rather than MARA and so forth.

What you want is the line of code to read like English as in the following example:-

MATERIAL_HEADER_DETAILS = BUFFERED_READ_OF->MATERIAL_HEADER_OF( ORDER_ITEM_DETAILS-MATERIAL ).

Some people would say I am being far too wordy there e.g. do I really need the word “OF”? Everyone has a different opinion on what is simultaneously the most compact and also the clearest code to read.

I can RFC Clearly Now

As we know only function modules can be used for an RFC. As classes came out in the year 2000 and SAP from that point started telling us not to use function modules, you would think they could have done some magic in that 16 year period to find a way to execute a method of a class on a remote system, there are some languages where you can only have classes and they seem to manage OK.

SAP methods and Excel methods seem to be able to execute inside each other’s system to an extent. Anyway, if this is forever in the too hard basket, how we get around this is to call an RFC function from within a wrapper method. A lot of standard SAP example Gateway classes do RFCS inside methods for example, as the Gateway wants the code to execute on a different system to the hub.

Putting the RFC call inside a method allows you to subclass for unit testing, and all the code inside the function module can be OO if that is so important. The function module wrapper superclass (we will come to that in a minute) could have a method to set the RFC destination. I am just blue-skying here.

Go to the Top of the Wrapper Class

The blogs talk about using a generic mechanism to transform the messages that come from function modules into class based exceptions, without having to do a load of coding each time. This has to handle both the cases where an actual SYST (T100) message is passed, as well as just a SY-SUBRC getting set by the function module.

Luckily for us SAP provides us with some great mechanisms to do this, especially in the latest releases. Once we settle on some generic code, then sticking that in a top level abstract interface/class would seem to be the way to go so you do not have to repeat it each time.

I will give a possible implementation right now. In the future, every time I can think of something that could be used by a wide range of function modules e.g. an instruction to execute on a different system, that could be added to the abstract superclass.

Fireman Code Sample

I can think of three results a function module call could result in:-

  • Happy path, all is well, desired result is returned

  • An exception is raised, together with a T100 message

  • An exception is raised, with no message


To start off, let us create a function module with some nonsense code inside, which returns all three possible results I just mentioned, depending on what monster number is input.

FUNCTION zmonster_golf_scores.
*"----------------------------------------------------------------------
*"*"Local Interface:
*"  IMPORTING
*"     REFERENCE(ID_MONSTER_NUMBER) TYPE  ZDE_MONSTER_NUMBER
*"  EXPORTING
*"     REFERENCE(ED_GOLF_HANDICAP) TYPE  I
*"  EXCEPTIONS
*"      MONSTER_ONLY_ONE_INCH_TALL
*"      MONSTER_HAS_NO_SILLY_TROUSERS
*"----------------------------------------------------------------------

IF id_monster_number = 15.
ed_golf_handicap = 3.
ELSEIF id_monster_number = 1.
RAISE monster_only_one_inch_tall.
ELSE.
"004 = Monster &1 has no Silly Trousers!
MESSAGE e004(zmonsters) WITH id_monster_number
RAISING monster_has_no_silly_trousers.
ENDIF.

ENDFUNCTION.

The idea is to write a small test program which passes in three different monster numbers, to result in the happy path, the exception with the message, and the exception without the message.

What do I want? The Moon on a Stick. When do I want it? Now. I am just like an end user.

I want my test program to call one method, and it handles all three situations. In this example my user name is David Win (WIND) but when you try this replace WIND with your actual name I want a break point just after the exception is raised so you can have a look at the exception object.

The calling program needs to decide what to do with the exception e.g. if online output a message to the user, if running in background write to the application log, or send an email, or sing a song and dance, whatever is appropriate. As always the job of the exception is to give as much information to the calling program as possible, and then its work is done here.

Here is my test program:-

START-OF-SELECTION.
DATA: wrapper TYPE REF TO zcl_get_monster_golf_handicap.

CREATE OBJECT wrapper.

PERFORM get_handicap USING :
'0000000001', "Exception - No T100 Message
'0000000002', "Exception - T100 Message
'0000000015'. "Happy Path

*&---------------------------------------------------------------------*
*&      Form  GET_HANDICAP
*&---------------------------------------------------------------------*
FORM get_handicap USING id_monster_number TYPE zde_monster_number.

TRY.
DATA(handicap) = wrapper->golf_handicap_of_monster( id_monster_number ).

CATCH zcx_function_module_error INTO DATA(function_module_error).
BREAK wind.
ENDTRY.

ENDFORM.

You will notice straight away that I am not calling the function module directly. I have created a wrapper class around it, with a more meaningful name than the original function module, according to my oh-so-logical naming convention as opposed to the random naming convention used by many programmers, and indeed SAP itself.

To keep things simple for this example class ZCL_GET_MONSTER_GOLF_HANDICAP only has one method, namely GOLF_HANDICAP_OF_MONSTER. Let us have a look at the code, which consists of a number of calls to other routines, each of which we shall deal with in due course.

METHOD golf_handicap_of_monster.

remove_existing_messages( ).

CALL FUNCTION 'ZMONSTER_GOLF_SCORES'
EXPORTING
id_monster_number             = id_monster_number
IMPORTING
ed_golf_handicap              = rd_golf_handicap
EXCEPTIONS
monster_only_one_inch_tall    = 1
monster_has_no_silly_trousers = 2
OTHERS                        = 3.

IF sy-subrc <> 0.
throw_exception_on_error_from( 'ZMONSTER_GOLF_SCORES' ).
ENDIF.

ENDMETHOD.

Clearly the main piece of business is to call the function module itself. The parameters of the method are the same as the function module, and you can rename them if the original SAP module had silly or German abbreviation names, and skip ones that never get used (use constants or something in the wrapper method), to make the life of the calling program easier.

I have all my wrapper classes inheriting from abstract class ZCL_FUNCTION_MODULE_WRAPPER.

This has methods which will never change regardless of which function module I am wrapping. By the way, one of my favourite songs for the yuletide season is “Christmas Rapping” by The Waitresses. First off, I have determined that when you call a function module and no errors occur; the values of SYST remain unchanged, which makes perfect sense. Nonetheless I want them cleared. Why? Because I said so. I could get a job writing documentation for SAP with that attitude. There is an actual reason – see if you can guess it based on what follows.

METHOD remove_existing_messages.

CLEAR: sy-msgid,
sy-msgno,
sy-msgty,
sy-msgv1,
sy-msgv2,
sy-msgv3,
sy-msgv4.

ENDMETHOD.

That’s them gone! Ha-Ha! Serves them right! Now if they change to non-initial values it is because my function module call has changed them.

If the function module call turns pear shaped because an exception has been raised, or an unexpected error message has been directly raised inside the function module (the ERROR_MESSAGE exception) then the value of SY-SUBRC will not be zero, as we all learned on our first day on the job, and so my THROW_EXCEPTION_ON_ERROR_FROM method will be called, passing in the function module name.

This method is implemented in the abstract superclass, as are all the private helper methods I will discuss shortly. My spell checker just suggested that “Pirate Method” was better than “Private Method”. As we all know, when defining a “Pirate Method’ in ABAP the first line of code has to be “Arrrrrrrrrrrrrrrrrrrrrr!”.

METHOD throw_exception_on_error_from.

md_function_name = id_function_module.

DATA(return_code)    = sy-subrc.
DATA(exception_name) = the_exception_mapped_to_the( return_code ).
DATA(exception_text) = description_of( exception_name ).

IF no_t100_message_was_raised( ).
RAISE EXCEPTION TYPE zcx_function_module_error
EXPORTING
function_module = id_function_module
exception_name  = exception_name
exception_text  = exception_text
no_t100_message = abap_true.
ELSE.
RAISE EXCEPTION TYPE zcx_function_module_error
MESSAGE ID sy-msgid
TYPE sy-msgty
NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4
EXPORTING
function_module = id_function_module
exception_name  = exception_name
exception_text  = exception_text
no_t100_message = abap_false.
ENDIF.

ENDMETHOD.

This is in a 7.50 system, by the way. Some of the exception handling used here is not available in lower releases i.e. the releases used by 99.9999999999% of SAP customers at the moment, so ha-ha-ha stuff you! I could definitely get a job writing SAP documentation However the day will dawn when most people are on this release albeit that day is ten years away.

Just in case there is no T100 message raised it would be a good idea to get the name and description of the actual exception raised, as follows:-

METHOD the_exception_mapped_to_the.

SELECT  parameter
FROM  fupararef UP TO 1 ROWS
INTO  rd_exception_name
WHERE funcname  = md_function_name
AND   r3state   = 'A'
AND   paramtype = 'X' "It's an exception
AND   pposition = id_subrc.
ENDSELECT.

IF sy-subrc <> 0.
CLEAR rd_exception_name.
RETURN.
ENDIF.

ENDMETHOD.

METHOD description_of.

SELECT SINGLE stext
INTO  rd_exception_text
FROM  funct
WHERE funcname  EQ md_function_name
AND   parameter EQ id_exception_name
AND   kind      EQ 'X'.

IF sy-subrc <> 0.
rd_exception_text = id_exception_name.
RETURN.
ENDIF.

IF rd_exception_text IS INITIAL.
rd_exception_text = id_exception_name.
RETURN.
ENDIF.

ENDMETHOD.

What have I got? I’ve got the lot! I have both the technical name of the exception and any text description that may have been entered. The latter is not compulsory so when it is blank I just set it to the technical name. Blankety Blank, Blankety Blank, Blankety Blank. Blankety Blank! Supermatch game, Supermatch game, Supermatch game. Supermatch game!

The next question, the burning question on all our minds, is whether a T100 message was raised either the correct way via MESSAGE xxx RAISING or via a direct error message. We cleared SY-MSGID and all its friends earlier so if they now have a value then the function module must have created that value.

METHOD no_t100_message_was_raised.

rf_no_t100_message_was_raised = abap_false.

CHECK sy-msgid IS INITIAL OR
sy-msgno IS INITIAL.

rf_no_t100_message_was_raised = abap_true.

ENDMETHOD.

It just occurred to me you could declare a classical exception called ARIZONA and then you could actually have in your code RAISING ARIZONA without a syntax error. Don’t do that. It’s silly. Forget I mentioned it.

Next we come to the custom exception class ZCX_FUNCTION_MODULE_ERROR. This inherits from CX_NO_CHECK which is my favourite of root exception classes.

CX_STATIC_CHECK is all well and good when I really want to highlight to the calling program that something is almost certainly likely to go wrong so you had better deal with it, and CX_DYNAMIC_CHECK  I use when I have had fifteen pints of beer and then been hit over the head with a baseball bat many times. Even then I hesitate.

The “interfaces” section looks like this:-


Interfaces


In addition, I have added some custom attributes:-


Custom Attributes


Hopefully the calling program, when confronted with an exception, can work out what to do based on the T100 data (if any) coupled with the classical exception details from the function module which was been “wrapped”.

Donna Summary

So, the artist formerly known as the function module can be wrapped up like a mummy, and then you can use inline declarations, the syntax check works better, silly naming can be improved, the interface (signature) can sometimes be simplified, you can still do an RFC, the error handling is a lot better, and the code in the calling program is drastically simplified.

Is that right? What do you think? Is this whole idea free energy crazy? Or is there an even better way to achieve the same goal?

Cheersy Cheers

Paul

 

 

 
8 Comments