The other day I was talking about exception handling and trying to get my head round how to use class based exceptions to make my programs better and achieve things that normal exception handling cannot.
Sadly, all of the examples I have seen concentrate on how class based exceptions work, which is fairly straightforward, as opposed to how they can be used to achieve new functionality. What I have been looking for is something along the lines of “when you have an error like THIS then with traditional exceptions you can only do THAT but with class based exceotions you can use the extra information / improved process flow options to do SOMETHING FAR BETTER “and suddenly everything is obvious.
When I started using ABAP Unit I found a raft of errors I would not have found otherwise within the first hour, so the added value was obvious from the start. If class based exceptions really are so much better then it should not be difficult to come up with an example scenario such as one I have described? You shouldn’t even have to think about it, if it’s that obvious?
Further to my investigations into the class based exception handling in ABAP, as I mentioned in my “Back to the Future” blog that I came across the – old in IT terms, from 1992 – concept of “design by contract” which ABAP has implemented in a way, and I believe Microsoft has implemented under the name “code contracts”.
The idea is that in the signature of a method, as well as saying what input variables are mandatory for input and what ones are output, you also say what conditions at the start of a method (preconditions) would cause an error due to a software bug in the caller, and what conditions after the method has finished (post conditions) would cause an error due to a software bug in the method itself.
ABAP has the idea of ASSERTIONS which is the same thing, but in ABAP they just cause a dump or write to a log, and don’t raise an exception which can be reacted to, and you want that exception to be raised so that each program can decide how best to deal with the problem.
Don’t take my example to seriously, they are just examples, in the case above the idea is that when the program reaches the ASSERT line the only way that condition will be true is if the program flow is doing something totally contrary to the way it is supposed to work.
In ABAP UNIT you can also test the conditions at the end (post conditions) for each routine, by starting a process which runs through each routine in isolation in test mode.
However by their very nature these tests are isolated, so you check for errors in the caller, as the test IS the caller i.e. you are supplying the data manually. So, ABAP is almost there, but with gaps. I thought it would be nice to implement the “design by contract” thing exactly as it was intended, the way it was implemented in the original programming language EIFFEL.
The example from that language goes as follows:-
set_hour (a_hour: INTEGER)
Set `hour’ to `a_hour‘
valid_argument: a_hour>= 0 and a_hour <= 23
hour := a_hour
hour_set: hour = a_hour
If a silly value for the hour, like 99, is passed in then the caller is to blame, and that is where the exception should be directed, if the export parameter HOUR at the end of the routine has the wrong value then it is the fault of the routine itself, and that is where any exception should be directed. If either condition ever fails this indicates a bug in the program which should be corrected, so the exceptions raised are to deal with “impossible” situations, but as we know the impossible situations happen all the time.
In languages like JAVA you have extensions to the language built in all the time, as I understand it at the start of a Java program you list which libraries you are going to import, that is sort of like having to say at the start of an ABAP program which commands you want to use, a better analogy would be a string of INCLUDES each containing useful subroutines.
In ABAP OO world this becomes static classes, so you don’t have to declare them at the start, you just use them whenever you feel like it. So I created two exception classes ZCX_VIOLATED_PRECONDITION & ZCX_VIOLATED_POSTCONDITION both inheriting from CX_NO_CHECK on the grounds these are errors which should never happen. Interestingly I found some exception classes with almost the same name being used in the standard SAP HR programs.
Then I created a class ZCL_DBC (Design by Contract) to implement my two new ABAP commands with the same names as the EIFFEL commands above. Here is the result in my open item clearing program:-
*& Form UPDATE_CUSTOM_FIELDS
* Head office have a requirement to track which customer open items are
* automatically cleared.
* That is not as clear cut as you might think, but we pass in which
* transaction was used, and if the program was in batch at the time
FORM update_custom_fields USING pud_company TYPE bsid–bukrs
pud_customer TYPE bsid–kunnr
pud_clearing_doc TYPE bsid–augbl
put_belnr TYPE bkk_r_belnr.
* Local Variables
DATA: lf_in_batch TYPE abap_bool,
ld_tcode TYPE sy–tcode VALUE ‘ZF32’,
ld_subrc TYPE sy–subrc.
* Preconditions – indicate a bug in calling routine
zcl_dbc=>require( id_that = ‘all cleared item key fields must be supplied’(081)
if_true = boolc( pud_company IS NOT INITIAL AND
pud_customer IS NOT INITIAL AND
pud_clearing_doc IS NOT INITIAL AND
put_belnr IS NOT INITIAL ) ).
* Routine Body
“Do code that updates some Z data
ld_subrc = sy–subrc.
IF ld_subrc = 0.
ROLLBACK WORK. “#EC CI_ROLLBACK
* Postconditions – indicate a bug in this program
zcl_dbc=>ensure( id_that = ‘the Cleared Items database table will be updated’(080)
if_true = boolc( ld_subrc = 0 ) ).
ENDFORM. ” UPDATE_CUSTOM_FIELDS
To translate back into English:-
This routine REQUIRES THAT all key item fields must be supplied
And in return
This routine ENSURES THAT the Cleared Items database table will be updated.
I am forcing a meaningful comment each time as it will be put into an error message whch wll make no sense unless the samenatic of the phrase are correct..
That is the “contract” the routine has with any routine which wants to call it. If the pre-condition or post-condition fails then the corresponding exception is raised. How those “impossible” situationare dealt with is entirely up to the programmer.
The idea is also that as a by-product the routine is more self-documenting as you can see at the start more information of what is expected from the input parameters and at the end more information on what the routine was supposed to do. The document at that start of the routine should focus on WHY the routine is doing what it does rather than what it is doing.
You may ask why I am bothering when ABAP already has assertions and unit tests? To quote the Borg Queen “you see disparity where there is none”. I think this is complimenatry to existing tools. If I had a purely mathematical routine then a unit test is perfect for testing the post-condition. If I had a situation in the middle of a routine which I was 100% sure would never occur then an ASSERT statement may be the way to go. The “design by contract” thing is more for routines that do things like update the database which you never expect to fail and want to raise an exception at run-time to be handled as you see fit.
There is another concept called the “class invariant” – with a great analogy in the attached article about it not being mentioned in a cooks employment contract that he should not burn the restaurant down, but you expect him not to do that regardless, but god knows how you would do that in ABAP, other than calling the same general check at the end of every unit test method,
As always, feedback is more than welcome. Please don’t be afraid to tell me I am barking mad, I am doing all these experiments and reading all this background material just to try and get the most out of OO programming, and if I am going down the wrong path you would be doing me a favour steering me right.
I did think it is funny that in the SAP press book “ABAP Programming Guidelines” Horst Keller syas that ABAP Objects is easier to learn than procedural programming. Does anyone else out there feel that way?
P.S. On a totally different subject, I was in the SAP Service Marketplace the other day whilst changing a repair in our system into an enhancement, and the SAP netweaver version defaulted to a lower value than I am on, 710 instead of 711. So when I did the drop down I wondered what the highest version I could choose was. It turned out to be SAP netweaver version 804. As the service marketplace is used interanlly by SAP that explain why those values are there, I can only presume internally SAP are working on the next major release, even if it does not come out until 2020….
That’s it for now, local election day here in New South Wales, Australia. It is illegal not to vote in Australia, so I had better get down to the polling station rigth now, or I will get a fine.
Mit freundlichen Grüßen/Cheersy Cheers