Application Development Discussions
Join the discussions or start your own on all things application development, including tools and APIs, programming models, and keeping your skills sharp.
cancel
Showing results for 
Search instead for 
Did you mean: 

Design and management of custom exception classes - best practice?

Jelena
Active Contributor

This is more of a discussion than a question but here we go. How do y’all design and manage the exception classes? In some cases, I’m struggling to find a solution that would be simple and practical but not too "un-clean".

Let’s say there is a class ZCL_DELIVERY that performs some operations with an outbound delivery. There could be many different errors, some accompanied by a single message (“Delivery has a wrong status”), some by the whole table (e.g. returned by a BAPI or an error list for multiple items).

The class is used by different programs that might want to react to the exceptions differently: some would want to display an error message to the user, some would want to post in application log.

In the books and examples online I’m mostly finding suggestions to maintain text in the exception class and use IF_T100_MESSAGE. Personally, I don’t like this at all and find the whole IF_T100_MESSAGE~T100KEY super-confusing and bulky. The approach adopted by a colleague to create an exception class for basically every error doesn’t seem to be practical either (good luck searching by ZCX*).

I tend to gravitate towards more “generic” exceptions that carry error message details as attributes (this could be the T100 key equivalent or a string or BAPIRET2 table). In this example, I might create single exception class ZCX_DELIVERY and just pass whatever error message occurred with it. And I would define different exception classes only when exception requires a different handling or it’s single error message vs BAPI table, for example.

Advantages: (1) it keeps number of exception classes low; (2) “where used” in SE91 shows where exactly message is used in the class. Disadvantage: I don’t know if this is the right approach and if it backfires down the road.

How to keep balance between proliferating ZCX… classes vs ZCX_SOMETHING_BAD_HAPPENED? Is there some method (no pun intended) to the madness? What are your thoughts on this?

Thank you!

21 REPLIES 21

keremkoseoglu
Contributor

First things first: The "Clean ABAP" GitHub page contains some valuable inspiration on exception classes: https://github.com/SAP/styleguides/blob/master/clean-abap/CleanABAP.md

What I find valuable in my personal experience is this: Exception classes have a property called "Previous" - which points to another exception object. This can be used to pack exceptions inside each other.

Typically, the container exception will be more generic and the "previous" exception will be more specific.

Example:

  • Method A, which reads a Z table, raises an exception of type CX_NO_ENTRY_IN_TABLE to indicate that a material is missing in table ZMATERIALS
  • Method B, which is supposed to create an order and catches the exception of method A, raises an exception of type ZCX_ORDER_CREATION_ERROR. But, the PREVIOUS property now contains CX_NO_ENTRY_IN_TABLE.
  • Method C, which is supposed to run an integration and catches the exception of method A, raises an exception of type ZCX_INTEGRATION_ERROR. But, the PREVIOUS property now contains ZCX_ORDER_CREATION_ERROR.

You get the idea. In this example, wherever you catch the exception of Method C; you can track the error back to its main source following the PREVIOUS chain recursively. You can also display / log the error chain with some recursion. On the other hand, if you don't care about the root cause of the problem, you can simply assume "OK, integration failed", and move on.

Thanks for the answer! I actually never used PREVIOUS, noted it was there but somehow just glossed over it. I'll look some more into this, can see some suitable use cases.

0 Kudos

One more thing I forgot to mention above are "Resumable exceptions". When method A raises a resumable exception; the caller may decide whether it should stop, or tell method A to ignore the error and resume. That is a neat and useful feature.

In general principal, I follow kerem.koseoglu's methodology and use the PREVIOUS parameter quite frequently.

I will generally only create one exception class for any given project, as the errors are all in regards to the same overall process. I always make use of an message class to make the messages as meaningful as possible. Not much use in returning an exception message like "Error creating order.".

I will also create a helper method from my higher level classes which can take a caught exception and loop through the PREVIOUS parameters. I keep checking if is bound. Once I hit the "deepest" or "lowest" exception, I will often return that as the error message to the user.

My helper static method...

  METHOD zif_sc1_threshold~process_exception.
    DATA: prev_error   TYPE REF TO cx_root.

    CLEAR: r_message.

    IF ix_error->previous IS BOUND.
      prev_error = CAST #( ix_error->previous ).
      DO.
        IF prev_error->previous IS BOUND.
          prev_error = CAST #( prev_error->previous ).
        ELSE.
          EXIT.
        ENDIF.
      ENDDO.

      r_message = prev_error->get_text( ).

    ELSE.
      r_message = ix_error->get_text( ).

    ENDIF.
  ENDMETHOD.

The one importing parameter is an object of type cx_root. I keep the types generic, so that I can process any possible exception class and not just my custom ZCX... exception class.

It may not be the best or perfect solution, but it has worked well for my development.

In the case I need to return multiple return messages, such from a BAPI, I will often employ an error log, either a simple display of a table of messages or use the application log if it needs to be more robust and/or preserved.

Thanks,

Justin

FredericGirod
Active Contributor

I join the discussion without the solution, as you jelena.perfiljeva2, I am little bit lost with Exception Class, and I am searching for a good practice way.

I do it because I need to do it, but without real usage in real life.

If your exceptian failed, and you raised an error with a table, where did you use this ? who will use this ? end-users ? IT support ?

Michael_Keller
Active Contributor

I also recommend the Clean ABAP tips about error handling.

About the "exception class madness": I try to keep the number low (check this). Let's say there's a class that provides customizing: ZCL_CUSTOMIZING. Customizing is general and company code dependent. Then there are two more classes for these two types of customizing: ZCL_CUSTOMIZING_GENERAL and ZCL_CUSTOMIZING_COMP_CODE. The result is a composition, both classes are used bei ZCL_CUSTOMIZING. Nevertheless, in my example I would only create one exception class ZCX_CUSTOMIZING to be used by all three ZCL... classes.

About the design: If necessary I would work with error codes.

---
I ❤️ ABAP - with arms wide open for the tech community worldwide...

Thanks for the answer! "Clean ABAP" was the first place I checked. "Use class based exceptions" - yeah, thanks for the tip. 🙂 It doesn't really offer practical guidance on organizing the exception classes. For example, it says "use own superclasses". OK, but how high would I start the hierarchy and how far down should I go? Your practical example here is much more helpful to me. I'm also trying to minimize the number of exception classes. But I want to make sure to strike the balance here and not to confuse the situation where more ZCX would be beneficial with the one where it wouldn't.

ennowulff
Active Contributor

The IF_T100_and_so_on challenge ends with ABAP 7.50. Since then it is easier to return a T100 exception:

https://blogs.sap.com/2015/11/12/abap-news-for-release-750-converting-messages-into-exceptions/

I also fight this battle often enough and don't have a best practice so far.

  1. I often build a super exception class with some sub exception classes. Then the exception classes are not "lost in the wild" and it keeps the signature of methods using the exceptions small.
  2. I use a single exc. class for every error.
  3. If the error is too similir, I extend the exc. class by an additional text-id
  4. If I need more information about the caller than can be placed in the message variables, I add generic attributes like OBJECT, INFO, VAR that can be passed in RAISE EXCEPTION TYPE
  5. I often try to have exc. classes only internal/ technical and add the user message after the catch. But that often fails because the "exception raiser" knows best what the error was.

Some issues we had when mixing SE80-exceptions and eclipse-exceptions:

  • if an exception had been changed with eclipse, additional attributes added in SE80 are not generated into the CONSTRUCTOR
  • In SE80 you can add a gloabl message-class (properties section) - in eclipse you can't
  • adding text-id in eclipse is very painful. Not sure if it works at last because with a new text-id you get a unique GUID

general

  • you could use general exceptions for lots of application like OBJECT_LOCKED, AUTORIZATION_FAILED, ALREADY_EXISTS and so on
  • But this makes you dependent on other applications which might not always be the best idea.

Jelena
Active Contributor
0 Kudos

frdric.girod - there are two typical use cases in our system when the exception class returns a table of errors: a BAPI call in a class method and when a method receives a table with data and needs to validate all entries (or something like that).

This is typically used in an interface (e.g. a Gateway OData service) or a custom report that executes a chain of transactions, for example. In case of a service, the whole table would be returned to the caller. In case of a custom report, it is usually presented to the users via application log.

egor_malov
Contributor
0 Kudos

Jelena,

one more disadvantage of having a single class is that we cannot react differently to different situations by simply writing multiple 'catch'es within one try .. endtry like this:

try.
my_delivery->set_completed( ).
catch zcx_delivery_not_so_bad.
" retry or ignore
catch zcx_delivery_critical.
" rollback
endtry.

BiberM
Active Participant

In the beginning exception classes were a real pain to me. Especially when I started to use ADT.

But with some time there were some best practices and with those at hand I became a fan of exception classes.

advantages in my opinion:
+ exception can be raised over several layers of code.
+ you can decide in what granularity you react (or if at all)
+ if equipped with a message the caller can simply append the exception to its application log
+ it can store more content or error information than every message can

disadvantages in my opinion:
- it takes some time to create one
- they tend to pile up

Due to those points discussions started. If you really want to develop object oriented you can hardly not use them.
As nearly all of our applications create BAL Logs we quickly came to the conclusion that we want all of our exception classes to be with message (T100). Additionally in my opinion those are way easier to create as you don't need this GUID for the text-IDs.

The fact that the constructor doesn't get updated once you edited it with ADT is a benefit in my opinion: The code that SE80 generates isn't very good in my opinion. So now you have the change to produce good looking exception classes as well. Additionally we created a general super class that all our exceptions should inherit from and we created and shipped a template to all ADT users. All of those points speed up creation a lot!

Talking about growth rate. Yes that is true. In newer bigger developments we see about twice as many exceptions as normal classes. We use seperate sub-packages for exceptions only. That way they are somehow out of sight.
What makes the growth rate even worse is the fact, that most of our exceptions have only one text-ID (same name as the class). The reason for that is that it is way harder to react differently on different text-IDs than on different classes.

I hope this long post helps a bit.

former_member610590
Participant

My approach: to group exception class by the complexity of functionalit.

for example: if in zcl_delivery we have:
1) calculate date for this week on base of sizes of 100 items
2) calculate route which depends on weather and traffic
3) some small checks and utilities

so exceptions from zcl_delivery:

1) ZCX_DELIV_DATE_CALC

2) ZCX_ROUTE

3) ZCX_DELIVERY_COMMON

It is just my approach 🙂

rgore
Advisor
Advisor

Yes, even we faced the same challenge while developing public APIs for our product. It was not just regarding the exception classes but on how we design our public APIs with clean code and more developer friendly.

So this is what we followed, when developing a public API.

  • We always follow factory pattern, no other patterns we used to design our APIs
  • Per class, we create one interface(makes it easier for mocking) and one exception class

Now, How we handle the exceptions?

  • There might be legacy function module calls in our APIs, so we first segregate the exceptions raised in the legacy code as follows,
    > Must be reacted by developer, example object doesn't exists
    > Must be reacted by system admin, example client settings or system errors
    > Must be reacted by user, example locking issue, missing roles
  • Once the segregation is done, say in our API the exceptions are related to developer & system admin then we create 2 exception class which must be handled by the consumer of our public API.

pokrakam
Active Contributor

Hi Jelena,

I could write at length about this topic, but as I'm short on time I'll just try to add two quick tips I didn't see covered in the answers thus far:

1. IMO don't worry about creating too many classes. Exception classes are an exception to the rule (see what I did there?) of "prefer interface over inheritance": Use inheritance. Lean on it, it's your friend. ZCX_DELIVERY can have subclasses either by process or by function, and more specialised subclasses can have additional attributes. More classes makes a where-used more targeted. On the other hand you can always CATCH the superclass to incorporate all children so your caller doesn't have to care too much what's being raised.

2. Exception classes are normal classes, use them! For example, you can use static methods to make raising easier / more readable:

CALL FUNCTION 'BAPI_GOODSMOVEMENT_CREATE' 
  ...
  TABLES
    return   = messages.

LOOP AT messages WHERE type CA 'AEX'.
  zcx_delivery_gm=>raise_with_bapiret( messages ).
ENDLOOP.

(I don't know offhand if the delivery/goods movement scenario makes sense, I just wanted to crowbar a subclass in there...). Note too, that I'm just abusing the LOOP for it's WHERE capability, it'll bomb out via the exception once it enters the first pass. The static method then does the actual 'RAISE EXCEPTION TYPE...'.

Feel free to come up with your own scenarios.

Jelena
Active Contributor
0 Kudos
I could write at length about this topic

You definitely should! 🙂

Thank you for chiming in. This type of practical advice is exactly what I've been looking for. The theory I read in the books either doesn't cover that at all or doesn't go deep enough into the use cases. (Understandably - one could easily write a book on the use of exception classes alone.) I've been kind of feeling my way around this in the dark for some time and, of course, some mistakes have been made. E.g. I see now that in some cases I leaned on inheritance where I should've used an interface.

Also your excellent point about the methods in exception classes was a revelation to me at some point. I've stumbled upon standard class CX_BAPI_CUSTOM_EX and only then realized we can actually add methods. For some reason, I thought that it was not feasible or at least not recommended.

This information could be helpful to many, I think.

pokrakam
Active Contributor

Correct, they are fully functioning regular classes. It's their inheritance from cx_root that makes them exceptional (painful pun, sorry). While technically possible, it's not recommended to write entire applications with exception classes. But it's a good place to put small bits of exception-related code.

I also sometimes inherit an exception locally just to give it a more meaningful and/or findable name (e.g. for Gateway exceptions).

Here's a different take on the classic Hello World:

REPORT hello_st22.
CLASS lcx_helloworld DEFINITION INHERITING FROM cx_static_check. ENDCLASS. START-OF-SELECTION. RAISE EXCEPTION TYPE lcx_helloworld.

0 Kudos

I think there is a bigger discussion here. Exceptions are for unexpected conditions in the program not for handling every error situation. Failing to create an order because a customer is locked for sales area is most likely not an exception. But if as part of the order creation I might need to comunicate with an external system and there is no network this would likely produce a timeout exception.

I might create an exception in my application so that I can catch and encapsulate standard exceptions but I will always favor having a goold old return code to indicate if the process was done successfully and a message table to provide additional details. I the past I might have created a return class so I can group all status information but haven't done it in ABAP.

What you are describing here is the difference between what I would term a technical exception and a business exception.

But I will wholeheartedly disagree with your theory about return codes. They are a relic from the past and suffer from many problems. IMO the only valid use for them is in API functions where no exception is possible (e.g. BAPI).

The biggest issue is that you are placing a passive responsibility on the caller to deal with it (pull principle). If they don't code for it, it gets ignored. Exceptions force the caller to handle it (push) - either through syntax error or by dumping. This in itself makes the code a LOT more robust.

Exceptions also implement separation of concerns much better - you can have one block of code that deals with your regular program flow and another for the error handling. One CATCH instead of several IF SY-SUBRC... blocks.

In principle I agree that separating business and technical exceptions can be a good thing. That separation is however best achieved by raising e.g. zcx_delivery and zcx_interface. It leaves the option of e.g. the caller to CATCH one and pass the other up the chain.

0 Kudos

Thanks for a reply! Yes, this is definitely a discussion rather than a question, as I mentioned, but unfortunately, there is no mechanism in Q&A section to start a discussion instead of a question. Discussions are possible only in the Coffee Corner but this is a serious subject, so I didn't want to open it there.

This is rather off topic but it's interesting that you favor a "return code" in case of successful process. I've been rather on the opposite side: an absence of exception should mean success and this should not require any special "OK" message. There are, of course, cases when upon success, some data is returned (e.g. a new document number). But if some method has a rather boolean result (either it worked or it didn't) then it shouldn't be expected to return anything other than an exception. If a reassuring message must be displayed to a user, for example, then the calling UI should handle it as it wants.

Case in point. Last year, we were working on some APIs (Gateway services) for external vendors. Our business users were insisting that in case of a successful execution, some APIs must return a message, such as "X was posted". However, this would (a) created more work for us because Gateway services are not really designed for that and we'd have to add such message as regular data; (b) the vendors were tempted into looking for specific message text to signify success instead of using standard HTTP code. In the latter case, an API could be easily "broken" if we changed the message text to "X was posted successfully". It took a lot of convincing to abandon this idea in most cases and I'm glad we did.

0 Kudos

mike.pokraka

I’m not saying everyone should just ditch exceptions and use return codes. For me a least an exception is something that shouldn't happen but it can happen so is subject to some type of control, so in that case the obvious solution is to create an exception. However, I wouldn't create an exception class for everything that might go wrong and probably will (business or technical) .

I don’t think adding a bunch of exceptions adds necessarily to robustness I’ve seen too many empty catch clauses.

I think one should use some means of reporting that a process did or didn’t finished and specially why. Exceptions should be used sparingly. I agree that there is separation of concerns that one should take into account.

jelena.perfiljeva2

I didn’t even know a discussions section existed…lol. Probably my answer was more suited to be a comment, I’m very sorry.

I have to agree with you that we have opposed opinions about what an exception represents. For me an absence of an exception does not mean a process completed successfully it just means that nothing went fatally wrong.

I’ve never worked with the gateway but having worked as a Portal consultant I had the experience when an ABAP developer that built a remote function module for me to consume decided that his function module should raise an exception when it failed, this broke some many things on the Portal side because the ERP would just kill the work process, the Portal never got a response, not even one that would indicate something did go wrong. I think APIs, Interfaces, Services should always fail graciously.

Without knowing the details of the API example. If I were the developer building the client app for the API you mention I would prefer that the HTTP response always was that the process finished ok, but if in fact it didn’t, it would inform it in the response, instead of just the generic HTTP error code with an empty response. This would simplify greatly any diagnosis into what might have gone wrong.

I hope you can see I totally agree with your question when you say that is not practical to create an exception class for every error that might happen.

BR

adaceroge I didn't suggest one should create lots of exception classes, only that one could.

You can use one class for a wide range of exceptions, especially when combined with RAISE EXCEPTION ... MESSAGE. But in other scenarios, especially where SY-MSGs get lost, a specific class makes an error easier to find (e.g. Gateway). If all I see in an http 400 response containing ZCX_ORDER then I'm a bit stuck. But a subclass LCX_NO_ITEMS at least tells me something and anyone can still handle ZCX_ORDER.

Regarding robustness and using exceptions sparingly, we'll just have to agree to disagree.