Local exception class in global class – the trick
Hi Gurus,
How can I use a local exception class in a global class? (just kidding, it’s not a #notablog 🙂 )
Some background
Couple of weeks ago I was developing a BADI and came with the need of throwing an exception from one of my private methods to the public method so I would proceed with the processing or not. After some brief internal discussion with other developers whether would be better to A) create an exception class for this BADI implementation or b) create a generic BADI exception class, Ben Patterson came with the idea of a local exception class for this BADI. Good idea!
So, in my BADI implementation class I went to the menu “Go to->Local Definitions/Implementation->Local Definitions/Implementations and create a definition for my class:
CLASS lcx_my_exception DEFINITION INHERITING FROM cx_dynamic_check.
ENDCLASS. "lcx_my_exception DEFINITION
Then I activated the code, no errors, went to my private method definition and on the exceptions, typed lcx_exception (with the exception classes checkbox checked) and…
The problem (a bug?)
What?!?!? It didn’t look good, so after some failed attempts to solve it by myself, I came to SCN and among other stuff I found this discussion by Dirk Wittenberg:Local exception class in global class
Well, seems there’s no solution… I came back to talk to the team and Ben suggested me to declare the superclass in the signature, and raise my local exception in the implementation (quite smart this Ben Patterson guy). This was my reply to Dirk in the discussion above. It was a workaround that did the trick, but Dirk was not really happy, nor was I.
I tried a bit more stuff and could not believe it was not possible. If you can declare your local types and use them in methods signatures, why not local exception classes?
The trick
Last night I was playing on my trial system at home when I saw the light
Ok, it wasn’t like this and it was night so the lights were already on, but I thought “what if I try to add this exception in the source-code based mode?”.
So to the code based I switched, scrolled down to my private method definition and typed:
METHODS change_status
IMPORTING
!iv_object_number TYPE j_objnr
!iv_status TYPE j_status
RAISING
lcx_exception .
Activate the code, no errors. Went back to form based, and I now can see my local exception declared in the signature:
Instead of going to the source code based mode you can also go to your private/protected/public section, it works the same way. Important thing is: form based = bad, source code based = good. I reckon there would be no problem if using ADT/AiE
The End 😀
Cheers,
Custodio
PS1: This is my very first technical blog. No rocket science, no HANA/Cloud/Mobile stuff, but I hope it’s useful to someone.
PS2: I was not going to write a blog, rather just reply to Dirk’s thread. But for some reason I cannot reply to it, so blog it is.
Hi Custodio,
Does the local exception work OK in the debugger or when it is not correctly caught? Was just wondering if there was some reason that all exceptions should normally be publicly instantiatable?
Sounds like a great idea to have private exceptions for your private methods though.
Nice work figuring it out and sharing it with the rest of us.
Cheers,
Chris
Hi Chris,
Yes, it works fine in the debugger. As normal you can catch the local exception class or its superclass otherwise you will be presented to a short dump.
Thanks for your comment.
Cheers,
Custodio
Hi Chris,
sorry for late reply here, but I think I know what is the confusion root here:
Your input is probably based on text
"...Ben Patterson came with the idea of a private exception class for this BADI."
In my opinion, it should rather be
"...Ben Patterson came with the idea of a local exception class for this BADI."
as it does not have anything to do with instantiation of the exception class.
Just my 2 cents.
cheers,
Michal
Hi, just saw this comment now and you are absolutely right. Fixed the post, thank you.
I wholeheartedly agree with your "form based = bad" / "source based = good" conclusion.
Some years back I made the mistake of saying the same thing about SE24 on the SCN ad got a shed load of personal abuse from people leaping to the defence of form based tools, and attacking things like Eclipse.
That was before (a) the source code option on SE24 was available and (b) before the advent of ABAP in Eclipse so I like to hope some of the people who attacked me then are eating their words now once they realise how good those things really are.
I find some of the ABAP2XLS downloads don't compile properly until I mess about in the source code based version of SE24 and then all is well.
Now it is "hello gurus" time. Today I just spent two hours arguing with a colleague about class based exceptions. He was trying to throw an exception subclassed from CX_DYNAMIC_CHECK up in the air and hoping it would get caught three levels up the stack, and was puzzled by getting short dumps.
My experiments seem to indicate you can only do that with CX_NO_CHECK, and as far as I can see CX_DYNAMIC_CHECK and CX_STATIC_CHECK are 100% identical in their behaviour save that the STATIC check warns you (via syntax error) it's going to dump and the DYNAMIC one doesn't.
If that's true - and maybe it is not - why in the world would you ever use dynamic as opposed to static?
To re-iterate
TRY.
PERFORM thing_that_raises_dynamic_exception.
CATCH zcx_dynamic_exception.
ENDTRY.
... causes a short dump when the exception is raised. Am I missing something obvious?
Cheersy Cheers
Paul
Hi Paul,
Thanks for your comment.
Just to be clear, I'm not a form-based hater. The "conclusion" was valid only for this issue I was facing. Form based is actually my preferred way to work, although I've been working quite often on source-code based. I will leave my AiE rantings for another topic, but surely there are less rantings now then it was when AiE was firstly announced.
On your question, I'm not sure it's obvious, but you can catch as many levels up as you want by catching cx_root. It will actually catch cx_no_handler (you are right, it's a sub class of cx_no_check), with your zcx_dynamic_exception in the attribute lx_root->previous.
data lx_root type ref to cx_root.
TRY.
PERFORM thing_that_raises_dynamic_exception.
CATCH cx_root into lx_root.
ENDTRY.
Cheers,
Custodio
Hello Paul,
Just an educated guess 🙂
You might be getting the runtime error because the procedure thing_that_raises_dynamic_exception does not handle (TRY .. CATCH) /raise (FORM ... RAISING) the exception. You should check the subroutine code to see if the exception is propagated, otherwise the caller won't be able to catch it in the TRY .. CATCH construct 🙁
To do this he has to propagate the exception at each & every level (or handle it in the procedure, if he may) otherwise the exception will result in a runtime error.
But consider a kernel developer(read: magician), dynamic checks are a neat feature to have. We(read: mortal ABAPers) don't want the syntax check to bother us with each & every exception - Error opening App server file, Open SQL error etc. Hence almost all system exceptions(CX_SY_*) are subclassed from CX_DYNAMIC_CHECK.
Imo dynamic checks are kinda "defensive" way of programming the checks.
Let me know your thoughts 🙂
BR,
Suhas
Hi Suhas,
Firstly, thanks for your comment.
Secondly:
Consider the code below:
*----------------------------------------------------------------------*
* CLASS lcx_exception DEFINITION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcx_exception DEFINITION INHERITING FROM cx_dynamic_check.
ENDCLASS. "lcx_exception DEFINITION
*&---------------------------------------------------------------------*
*& Form do_something
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
FORM do_something.
PERFORM do_something_else.
ENDFORM. "do_something
*&---------------------------------------------------------------------*
*& Form do_something_else
*&---------------------------------------------------------------------*
* text
*----------------------------------------------------------------------*
FORM do_something_else raising lcx_exception.
RAISE EXCEPTION TYPE lcx_exception.
ENDFORM. "do_something_else
DATA lx_root TYPE REF TO cx_root.
START-OF-SELECTION.
TRY .
PERFORM do_something.
CATCH cx_root INTO lx_root.
ENDTRY.
The subroutine do_something_else raises the exception to do_something. It's neither handled nor propagated to its caller, so it raises cx_sy_no_handler, which is then caught.
Might not be "best practice", but you I can see some good use cases for it.
Cheers,
Custodio
Hello Custodio,
If i remember correctly CX_SY_NO_HANDLER can be used to raise exceptions in Event handlers. But good programming practice is to handle the exceptions within the event handlers 🙂
SAP's guideline to use the correct exception class - http://help.sap.com/abapdocu_731/en/abenexception_category_guidl.htm
BR,
Suhas
I suppose what I am asking about is a bit obscure, so I'll try to explain further.
If you add the exception to the signature of the routine / method then indeed it will get propgated...
FORM do_something RAISING zcx_dynamic
That will propogate the exception
FORM do_something.
That will not.
I am saying that if you have an exception class inheriting from CX_STATIC then you will be forced to mention the exception in the signature - I'm not saying that's a bad thing.
If you use an exception class inheriting from CX_DYNAMIC then you don't have to mention the exception in the signature, but if the exception is not handled locally then you will get a dump.
So, I say again, as far as I can see the behaviour of STATIC and DYNAMIC is identical apart from the syntax check. Is my opinion correct? I have a burning need to know?
Catching the exception with CX_ROOT is fine, but again, and I ay be wrong here, if you put some sort of extra information in your Z class, then it will be lost if CX_ROOT handles the error? Or can CX_ROOT somehow magically get the instance of your Z class and get the information back? If so, how?
Thus far the only way I can see to write a "safe" re-usable subroutine / method / function is to either use STATIC and declare the exception in the signature, or use NO_CHECK and hurl the exception in the air, hoping that someone will catch but at least knowing such a thing is possible.
If I raise a DYNAMIC exception, it cannot be caught if I do not handle it locally.
So, why in the world would I ever raise a dynamic exception if the subroutine at hand does not know what to do, yet a routine higher up the stack does?
I see the point above about not wanting to have gigantic signatures with dozens of exceptions entioned each time, which is why thus far I have been veering towards NO_CHECK.
Any thought on this topic are most welcome, error handling is the most vital part of programming.... either there is something wrong with the concept of dynamic exceptions, or something wrong with my understanding of this, either way it needs to be fixed...
Cheersy Cheers
Paul
Hello Paul,
Your understanding of the behavior of exception types - Static, Dynamic & No Check - is absolutely correct 🙂
I'll suggest you take a moment to read through this - http://help.sap.com/abapdocu_731/en/abenexception_categories.htm . Just like you SAP also recognizes that dynamic checks mostly result interface violation in procedures 😉
BR,
Suhas
Hi Custodio,
great post - great exploration work!
I implemented your solution in my sandbox class and it works.
Unit tests for the private method are passed. ( I used the "friend" - option to test the private method directly - I know there are folks out there raging against testing of private methods - but in this case I think it's ok ).
Kind regards,
Dirk
Hi Dirk.
Thanks for your comment. I don't get this "prohibition" of testing private methods. IMO every single unit of code should be tested.
Cheers,
Custodio
Hi Custodio,
the arguments against direct testing of private methods is, that they are automatically tested while testing the public methods calling them. And every private method not being called by a public one can be deleted anyway.
I'm sometimes testing private methods also when they implement some complicated algorithm.
Regards,
Dirk
I think you should be allowed to do unit tests on whatever you want to!
There are two possibilities - you can test every single routine, to make sure the logic works, or you can do the "behaviour driven development" type of unit test as advocated by Dan North. This does indeed involve calling the higher level methods which will in turn call the lower level private methods.
I do both myself.....
Cheersy Cheers
Paul
Hi Dirk and Paul,
Thank you both on your inputs. As you can see testing is clear my strength 😉
Until now I tended to test every single unit, but will probably switch to public method only, testing private if found really necessary. From Monday we will have a guy in the project who knows a thing or two about TDD, I will definitely use his wisdom.
Cheers,
Custodio
Hi Custodio
A creative solution to an interesting "situation". 🙂 I'll admit that I'd never even considered creating a local exception class for private BADI methods and would normally just create a global exception class... but I can see the argument for keeping the exception class local an not cluttering the class repository unnecessarily. I'll definitely consider this option next time.
I'm just wondering though, what happens if you make a subsequent change to the private method's signature using the form-based method? Does the error message reappear? I guess what I'm asking is, if you use "the trick" once, does that mean you can only maintain the signature using the source-code-based method from then on, even if the change is to a method parameter?
Cheers
Glen
PS. I wonder what response you would receive if you raised an OSS Message suggesting that this "situation" is a bug in the ABAP Editor?
Hi Glen,
You can use this trick and then add another parameter in the signature, will be fine. Unless you delete the exception and then try to add again via form based. No can do, have to go to the trick again.
Cheers,
Custodio
Nice!
Custodio, thanks for bringing to my attention this old document (which is how "how-to's" are supposed to be posted on SCN, by the way). I somehow missed it 2 years ago despite the fact that I'm following you and many of the commentators (my respect to these fine SCN members who will now get a "blast from the past" update in their activity stream, hehe 🙂 ).
Hopefully I won't have a need for such trickery, but regardless enjoyed reading the story and admiring investigative work and craftsmanship. Well done!