If you know the author of the book this post comes from, it won’t come as a shock that it starts out with ZX81.
The language it used back in 1981 was able to handle statements like IF ( A + B) > ( C + D ) THEN …. Later, in 1999, when the author, Paul Hardy, started programming in ABAP, he missed this ability. Luckily, with the advent of ABAP 7.02, which he first had access to in 2012, he was once again able to do this sort of thing. (It might have taken 31 years for them to work out how to do this, but it was worth the wait.)
In ABAP 7.40, the IF/THEN and CASE constructs you know and love keep on getting easier. The next sections will explain how.
3
Omitting ABAP_TRUE
When functional methods were introduced to ABAP, part of the idea was that this would make the code read a bit more like English. Over the years, the consensus among ABAP programmers both inside and outside of SAP was that if you were creating a method that returned a value saying whether or not something is true, then the returning parameters should be typed as ABAP_BOOL.
For example, the ZCL_MONSTER->IS_SCARY method should return ABAP_TRUE if the monster is in fact scary but ABAP_FALSE if it’s not quite as monstrous as it should be. So far, so good. However, as the code below shows, something’s rotten in the state of Denmark.
DATA(monster) =
zcl_monster_model=>get_instance( '0000000001' ).
IF monster->is_scary( ) = abap_true.
MESSAGE 'Oh No! Send for the Fire Brigade!' TYPE 'I'.
ENDIF.
Why do you need the = ABAP_TRUE at the end? It doesn’t make the sentence any more readable, just longer. As any English teacher will tell you, adding words that do not change the sentence’s meaning to a sentence only makes you sound long-winded. The answer is that you had to do this because otherwise the syntax check would fail.
As of release 7.4 (SP 8), however, you can now do just what you would expect and omit ABAP_TRUE, as shown here.
DATA(monster) =
zcl_monster_model=>get_instance( '0000000001' ).
IF monster->is_scary( ).
MESSAGE 'Oh No! Send for the Fire Brigade!' TYPE 'I'.
ENDIF.
What’s happening from a technical point of view is that if you don’t specify anything after a functional method, the compiler evaluates it as IS_PRODUCTION( ) IS NOT INITIAL. An ABAP_TRUE value is really the letter X, so the result is not initial, and so the statement is resolved as true.
Opinion is divided as to whether it is a Good Thing to have a true Boolean data type in a programming language. SAP says no, and the creators of every other programming language ever invented in the history of the universe say yes. This is why there are workarounds like this in ABAP.
That said, you have to be really careful when using this syntax; it only makes sense when the functional method is passing back a parameter typed as ABAP_BOOL. As an example, consider this code:
DATA(monster) = NEW zcl_monster_model( ).
IF monster->wants_to_blow_up_world( ).
DATA(massive_atom_bomb) = NEW lcl_atom_bomb( ).
massive_atom_bomb->explode( ).
ENDIF.
If the functional method returns a string and that string is NO NO! Do not blow up the world, whatever you do, do not blow up the world, then the result is NOT INITIAL and thus evaluated as true, and so it’s curtains for all of us.
Using XSDBOOL as a Workaround for BOOLC
Another common situation with respect to Boolean logic (or the lack thereof) within ABAP is a case in which you want to send a TRUE/FALSE value to a method or get such a value back from a functional method. In ABAP, you can’t just say something like the following:
RF_IS_A_MONSTER = ( STRENGTH > 100 AND SANITY < 20)
But in some programming languages, you can do precisely that (can you guess which computer could do that in 1981?). Again, we have a workaround in the form of the built-in BOOLC function.
* Do some groovy things
* Get some results
* Postconditions
zcl_dbc=>ensure( that = 'A result table is returned'
which_is_true_if = boolc( rt_result[] IS NOT INITIAL ) ).
You pass in a TRUE/FALSE value based on whether an internal table has any entries. This works fine.
But what if you want to test for a negative using this method? Say, for example, that you want to pass into the IF_TRUE parameter a TRUE/FALSE value that’s true if the table is empty. If you use the previous technique using BOOLC, then things start going horribly wrong. This can be demonstrated by running the code demonstrated below.
3
DATA: empty_table TYPE STANDARD TABLE OF ztmonster_header.
IF boolc( empty_table[] IS NOT INITIAL ) = abap_false.
WRITE:/ 'This table is empty'.
ELSE.
WRITE:/ 'This table is as full as full can be'.
ENDIF.
IF boolc( 1 = 2 ) = abap_false.
WRITE:/ '1 does not equal 2'.
ELSE.
WRITE:/ '1 equals 2, and the world is made of snow'.
ENDIF.
When you run this code, the output is as follows:
- This table is as full as full can be.
- 1 equals 2, and the world is made of snow.
Oh, dear! The reason for this outcome is a fundamental design flaw in the built-in function BOOLC. Instead of returning a one-character field defined in the same way as ABAP_BOOL, it returns a string. If the string is an X (TRUE), then all is well—but in ABAP, comparing a string of one blank character with the blank character inside ABAP_FALSE means that the comparison fails, even though the values are identical.
Therefore, given that a real Boolean variable is out of the question for whatever reason, a new workaround is needed. Fixing BOOLC so that it returns an ABAP_BOOL value would have been too easy, so in ABAP 7.4 a newly created built-in function was added, called XSDBOOL, which does the same thing as BOOLC but returns an ABAP_BOOL type parameter. You can see an example of its use below. The function was not invented for this purpose, but it works, and that’s all that matters.
* Then do the same using XSDBOOL.
IF xsdbool( empty_table[] IS NOT INITIAL ) = abap_false.
WRITE:/ 'This table is empty'.
ELSE.
WRITE:/ 'This table is as full as full can be'.
ENDIF.
IF xsdbool( 1 = 2 ) = abap_false.
WRITE:/ '1 does not equal 2'.
ELSE.
WRITE:/ '1 equals 2, and the world is made of snow'.
ENDIF.
When you run this code, the output is as follows:
- This table is empty.
- 1 does not equal 2.
Even better: if you use the wrong one (BOOLC) in ABAP in Eclipse, you get a warning that tells you to use XSDBOOL instead.
The SWITCH Statement as a Replacement for CASE
How many times have you seen code like that? Here you’re getting one value and using a CASE statement to translate that value. The problem is that you need to keep mentioning what variable you’re filling in every branch of your CASE statement.
* Use adapter pattern to translate human readable CRUD
standard values to the BOPF equivalent
DATA: bopf_edit_mode TYPE /bobf/conf_edit_mode.
CASE id_edit_mode.
WHEN 'R'."Read
bopf_edit_mode = /bobf/if_conf_c=>sc_edit_read_only.
WHEN 'U'."Update
bopf_edit_mode = /bobf/if_conf_c=>sc_edit_exclusive.
WHEN OTHERS.
"Unexpected Situation
RAISE EXCEPTION TYPE zcx_4_monster_exceptions.
ENDCASE.
As mentioned in the code, this is the adapter pattern, very common in OO programming. In 7.4, this can be slightly simplified by using the new SWITCH constructor operator, as shown here.
* Use adapter pattern to translate human readable CRUD
* standard values to the BOPF equivalent
DATA(bopf_edit_mode) =
SWITCH /bobf/conf_edit_mode( id_edit_mode
WHEN 'R' THEN /bobf/if_conf_c=>sc_edit_read_only "Read
WHEN 'U' THEN /bobf/if_conf_c=>sc_edit_exclusive "Update
ELSE THROW zcx_4_monster_exceptions( ) ). "Unexpected
As you can see from this example, the data definition for BOPF_EDIT_MODE (/bobf/conf_edit_mode in this case) has moved into the body of the expression, thus dramatically reducing the lines of code needed. In addition, Java fans will jump up and down with joy to see that instead of the ABAP term RAISE EXCEPTION TYPE we now have the equivalent Java term, THROW. The usage is identical, however; the compiler evaluates the keywords RAISE EXCEPTION TYPE and THROW as if they were one and the same. As an added bonus, this actually makes more grammatical sense, because THROW and CATCH go together better than RAISE EXCEPTION TYPE and CATCH. (It’s lucky that exception classes have to start with CX; otherwise some witty programmer at SAP would create an exception class called UP.)
It’s important to note that the values in the WHEN statements have to be constants, as in the preceding example. If you put something like MONSTER->HEAD_COUNT after the WHEN statement, then the SWITCH statement as a whole explodes and gives an incorrect error message saying “HEAD_COUNT” is unknown, when what it really means is that MONSTER->HEAD_COUNT isn’t a constant. If you need a WHEN statement with variables, you have to use the COND statement described in the next section.
To move away from monsters for a second, as painful as that is, here’s an example of combing two new ABAP constructs together. Let’s say you wanted to merge some values of different lengths into a uniform format. In standard SAP, table VBPA is a good example. It has customer (KUNNR) values, which are 10 characters long, and personnel number (PERNR) values, which are eight characters long. In the listing below, we fill new table LT_VAKPA with the data from VBPA, except the KUNDE field will always come out as a 10-character field no matter if a customer number or personnel number was in VBPA.
LOOP AT lt_vbpa ASSIGNING FIELD-SYMBOL(<ls_vbpa>).
INSERT VALUE #(
vbeln = <ls_vbpa>-vbeln
parvw = <ls_vbpa>-parvw
kunde = SWITCH #( <ls_vbpa>-parvw
WHEN 'AG' OR 'WE' OR 'RG' OR 'RE'
THEN <ls_vbpa>-kunnr
ELSE '00' && <ls_vbpa>-pernr )
adrnr = <ls_vbpa>-adrnr )
INTO TABLE gt_vakpa.
ENDLOOP.
The COND Statement as a Replacement for IF/ELSE
All throughout this book, you’ll be reading about how resistance to change is bad. Despite that, let’s admit it: Change can be annoying, especially when it affects your code. One great example of this fact involves coding a CASE statement based on the assumption that one value is derived from the value of another—before someone comes along and tells you that the rules have changed and the last value in the CASE statement is only true if it’s a Tuesday. On Wednesday, the value becomes something else.
CASE statements can only evaluate one variable at a time, so in the case of this example you have to change the whole thing into an IF/ELSE construct. That’s not the end of civilization as we know it, but the more changes you have to make, the bigger the risk.
Say that you’re really scared that the logic you’ve been given might change at some point in the future, but nonetheless you start off with CASE, such as in the code below, which is pre-7.4 code that evaluates the description of a monster’s sanity based on a numeric value.
* Fill the Sanity Description
CASE cs_monster_header-sanity_percentage.
WHEN 5.
cs_monster_header-sanity_description = 'VERY SANE'.
WHEN 4.
cs_monster_header-sanity_description = 'SANE'.
WHEN 3.
cs_monster_header-sanity_description = 'SLIGHTLY MAD'.
WHEN 2.
cs_monster_header-sanity_description = 'VERY MAD'.
WHEN 1.
cs_monster_header-sanity_description = 'BONKERS'.
WHEN OTHERS.
cs_monster_header-sanity_description = 'RENAMES SAP PRODUCTS'.
ENDCASE.
In 7.4, you can achieve the same thing, but you can do so in a more compact way by using the COND constructor operator. This also means that you don’t have to keep specifying the target variable again and again.
"Fill the Sanity Description
cs_monster_header-sanity_description =
COND #(
WHEN cs_monster_header-sanity_percentage > 75 THEN 'VERY SANE'
WHEN cs_monster_header-sanity_percentage > 50 THEN 'SANE'
WHEN cs_monster_header-sanity_percentage > 25 THEN 'SLIGHTLY MAD'
WHEN cs_monster_header-sanity_percentage > 12 THEN 'VERY MAD'
WHEN cs_monster_header-sanity_percentage > 1 THEN 'BONKERS'
ELSE 'RENAMES SAP PRODUCTS' ).
3
That looks just like a CASE statement, and the only benefit of the change at this point is that it’s a bit more compact. However, when the business decides that you need to take the day into account when saying if a monster is bonkers or not, you can just change part of the COND construct. In the pre-7.4 situation, you had to give up on the whole idea of a CASE statement and rewrite everything as an IF/ELSE construct. The only change needed to the COND logic is shown here.
DATA: day TYPE char10 VALUE 'Tuesday'."Lenny Henry!
* Fill the Sanity Description
cs_monster_header-sanity_description =
COND text30(
WHEN cs_monster_header-sanity_percentage = 5 THEN 'VERY SANE'
WHEN cs_monster_header-sanity_percentage = 4 THEN 'SANE'
WHEN cs_monster_header-sanity_percentage = 3 THEN 'SLIGHTLY MAD'
WHEN cs_monster_header-sanity_percentage = 2 THEN 'VERY MAD'
WHEN cs_monster_header-sanity_percentage = 1 AND
day = 'Tuesday' THEN 'HAVING AN OFF DAY'
WHEN cs_monster_header-sanity_percentage = 1 THEN 'BONKERS'
ELSE 'RENAMES SAP PRODUCTS' ).
Editor’s note: This post has been adapted from a section of the book ABAP to the Future by Paul Hardy.
Comments