Skip to Content
Author's profile photo Otto Gold

My “light” tricks for building more “robust” ABAP code

17/03/2013: An important part of building robust code is the correct code modularization. I don`t pretent to be an expert, but if you`re interested in my opinion on that, read the new blog called ABAP code modularization: lesson learnt.

Many people are already practicing or at least considering “agile development” on their projects nowadays. The development cycles get shorter, faster and teams must work with the ubiquitous uncertainty and fight it with unprecedented flexibility. Although there are methodologies and tools to cope with the uncertainty on the project level, there are also ways and tricks on “development army of one” level that can help too.

If you think ahead, try to foresee the requests and ideas forcing you to change what you`ve built so far, you can prepare yourself for the change and minimize the impact of the changes that might come. And they always come.

You can also approach the thing statistically. Let`s say that you have ten developers on the team. Let`s say every one of them needs to rewrite something from the past every few days, let`s say 2 hours per week… then you can immediately see the benefit as a project manager (or development lead). I am not implying that you will be able to get rid of this overhead completely or that a developer that must (or simply wants for technical reasons that project managers don`t “get” quite often) go and rewrite things is a bad developer. It`s NOT like that. But one can get ready for the cases and reduce the overhead.

Example #1: Parameters of the calls …example for the lazy

You`re building a skeleton of the application, prepare function and method calls, objects, screens and leave the details of the implementation for later (that is my way, because I believe that the design is the most important thing at the beginning. You don`t have time to change the design of the application on the way and you don`t have time to start over almost never. That means that you can segment the application with the big picture in mind and then assign team members to implement “details” as they have time or their different skill sets are used).

Now let`s say that the customer comes and says something like “we need to build another layer on top of you have here, guys”, which means to go and enrich the application with a set of new attributes and parameters (note that you have to add the new parameters to the calls + populate them in the coding too!). If you have your skeleton with calls and arguments already finished or nearly finished, touching every call again to add the new parameter or two can become a nightmare (and it`s certainly boring and not challenging at all, does not sound fun, right?).

But you could have made yourself “ready” for this. If you had just one parameter narrowing the selection, it was tempting to send that parameter as a string or range table, right? Send it via dedicated parameter. Now think about the situation when you had anticipated this and that’s why you created a –structure dedicated for selection parameters. At the time you created the structure it meant that the structure had just one attribute, the one we mentioned above as being the only one. Now you know you need more so you just go to this definition and add other selection parameters as per the changed requirement. You don`t have to change anything in the coding, except one or two places where the new parameters must get populated and where they must be used for the real selection.

Example #2: Performance considerations …real world example

The first example was something that could spare you the time and clicking around, maybe some testing if you have to add too much and the thing becomes error prone because of that. This second example about performance is much more serious, because you might be forced to rebuild (maybe even from scratch!!) something that you have and works. Unfortunately it does not work for large data volume.

Before I explain the situation and my “trick”, consider this: you do development in the development system and that is rarely “stuffed with data”. You often have “some data” in the DEV system, but the volume, the quality and other aspects are often far from the production data (ok, we can argue here, that you have a tool or a team that gets this right for you, but I have seen this going wrong too many times…). Same applies for quality system (often because somebody declined to make the confidential data available for development or testing).  So you can experience something like me: you build your application, everything works fine in DEV and QAS, but in PROD you get dumps and timeouts, because the program is just not optimized for the huge workload. When I first experienced this, I had to rebuild the considerable part of my application and decided to think about the problems more in the future, rather do 5% overhead than 50% reworking.

Now the trick: I did and often see the beginners to create methods (functions) called ADD_DETAILS_FOR_OBJECT. That code gets a structure on the input with one (key/ definition) and then queries various tables for additional details that then get compiled in the same structures fields and sent to output. Now typical scenario: You have a table of the objects you need to get the details for and you run your ADD_DETAILS_FOR_OBJECT function in a LOOP. But the function is full of SELECT SINGLE * things and other “expensive” operations. This is the corporate world; we always do things for more than just one record. This will eventually happen to you too.

Now imagine you thought about this and had built the application the “clever” (the right) way. Instead of implementing the ADD_DETAILS_FOR_MULTIPLE as a LOOP calling the function for one object, you could have implemented it the other way round. That would mean to build a _MULTIPLE code full of SELECTs and JOINs and RANGEs to make the functionality very performing and implement _FOR_OBJECT function as a call to _MULTIPLE passing just one record in the input table.

Maybe this sound naïve to you, you might think that everybody does it this way. No they don`t. It is important to note that the time or effort needed to implement the functionality “in the correct direction” is no different to the bad one.

Example #3: Look at SAP standard authority-check function modules

Take a look at the ways how some of the authority checks are called from within the SAP standard. You know how to code the AUTHORITY-CHECK I hope. You can put the construct anywhere in your code. But SAP is clever and (not in all the cases, but at least in some cases) calls central function modules to check the user authorizations (central = user authorizations for an object is coded once and called many).

The lessons here are two: first is look for these function-modules and call them instead of coding your own authority checks. Often there are multiple objects working together in a complex way and if you just go and copy few lines from that complex logic, you might damage the intended concept. You might never realize that you damaged the concept but your new coding might force the administrators/ security people/ role builders (however you call them) to give very powerful access to all people that need to use the new development, although that access didn`t have to be given when SAP standard coding was in use.

The second lesson is: complex things must be coded once. But in this case I admit I am telling the truly obvious things.

/wp-content/uploads/2012/05/central_func_101166.png

Conclusion

I could go on with my suggestions, but I prefer a discussion than a listing of my ideas (that people don`t necessary share). I was considering adding a ALV field catalog dynamic generation as one of my tricks. I like to create a structure, ask REUSE_ALV_FIELDCATALOG_MERGE to prepare the field catalog for me and then (if needed) I change some lines in the generated definition. That means that when you add a column to your structure, it immediately “works” (user can see the field and the value). But I know so many developers that still create field catalogs statically (you know, those funny screens of copied code with changed column header texts) or do other strange things. But as you can see (or feel) that this is something others might not agree about, so listing more of my “tricks” would not help the message.

I welcome comments about my blogs even if you don`t agree with me. Please comment.

I am also very keen on ideas coming from people all around SCN which I can learn something from. I got some comments about ways I have never used under my last blog about debugging. I would love to learn something again here. Please tell us about your tricks, habits, ways and improvements.

Cheers Otto

Assigned Tags

      37 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Kesavadas Thekkillath
      Kesavadas Thekkillath

      Nice blog Otto,

      But you see fm REUSE_ALV_FIELDCATALOG_MERGE has some limitations, when we declare a field of type p ( not refered to a data element ) in a local structure. Then this function will not work 🙂 . Suggesting SALV would have been good in this case( where we need not have an cataloug ).

      Author's profile photo Otto Gold
      Otto Gold
      Blog Post Author

      Hi TK,

      a) I didn`t include it (I only said I was considering including it 😆 😆 😆 ) and b) there are situations when you have to do certain things. But I was talking about cases when one can choose. Of course you`re right 😀

      Hope all is well on your part of the globe, take care,

      cheers Otto

      Author's profile photo Former Member
      Former Member

      Kesavadas Thekkillath: This FM anyway warns you against using it with program-defined internal tables ℹ

      Otto Gold: I remember our performance team suggestion not to use REUSE_ALV_FIELDCATALOG_MERGE, because additional DDIC structures would cause database overload 😯

      Author's profile photo Otto Gold
      Otto Gold
      Blog Post Author

      Hi,

      with you and TK that makes two friendly faces commenting on the blog in 20 minutes are posting it. So happy about it:))

      Also about the fact that you`re having fun around this one FM and don`t question the blog message. By the way: Applications I am currently building are crunching hundreds of thousands of records, so one extra DB read to get the ALV I can affor, I think 😈

      cheers Otto

      Author's profile photo Chris Paine
      Chris Paine

      Hi Otto,

      I really think that you have to be aware that the right solution for one area may be a bad one for another.

      But in general, I find building wireframes to be a dangerous practice unless you have put an awful lot of thought into design - what often happens is that you end up making design compromising work-arounds.

      Some thoughts I'd put up - think of interfaces think of how you could build in testing frameworks - perhaps even Test Driven Development (TDD).

      Think of making your code easier to support rather than faster. Code that is easy to understand/ uses an interface is also easy to re-implement for performance concerns at a later date. Worry first about functionality and ease of maintenance, then think about performance.

      That all said - it depends on what sort of design/build process you use, how the code is going to be used.

      Thanks for posting,

      Chris

      Author's profile photo Otto Gold
      Otto Gold
      Blog Post Author

      Hi Chris,

      I don`t pretend there is one size fit all (always true) solution, but one can/ should (in general) think about maintainability (or what could the next client`s request look like) before it is a painful need.

      My point is you can pay the flexibility forward with very little or no extra effort.

      Maybe you can share your own tricks. I tried to generalize, not sure how successfully.

      Anyway thanks for the comment 🙂

      cheers Otto

      Author's profile photo Dirk Wittenberg
      Dirk Wittenberg

      Hi Chris,

      thank's for mentioning TDD - it's such a valuable and disregarded tool.

      @Otto Thank's for this great post.

      Regards,

      Dirk

      Author's profile photo Thomas Zloch
      Thomas Zloch

      Hi Otto, adding to your #3, if you look at function module AUTHORITY_ACCOUNT as example, you can see that the result of the AUTHORITY-CHECK is buffered in an internal table (with header line, oh no...). So if the program scans 5,000 G/L accounts with say 10 distinct authorization groups, the actual authority check is only done 10 times and not 5,000 times, which is both a performance aspect and also prevents the ST01 logs from overflow.

      Another reason to use these standard function modules as much as possible.

      Cheers

      Thomas

      Author's profile photo Otto Gold
      Otto Gold
      Blog Post Author

      Hi Thomas,

      any performance improvement helps, right 😛

      Do you have any tricks to share that are kind of generally useful? You know tricks that help one deal with the curveballs.

      Cheers Otto

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Hi Otto! Unfortunatelly, in these tough times development is becoming a bit too "agile" (i.e. "rushed" in plain English). I love to spend time on design as much as the next guy, but, unfortunately, can hardly afford such luxury nowadays. Everything is just "fix it", "fix it" and needed two weeks ago.

      I shamelessly declare ALV structures in dictionary and use REUSE_ALV_FIELDCATALOG_MERGE. Never heard of this being a performance issue (although we probably have less than 10 custom ALV reports anyway) and it's much easier to maintain, especially in a multi-language system.

      But I'm actualy surprised the holly war hasn't started yet on how the function modules are "soo yesterday" and how we need to use OO (even if the methods call the same FMs). I guess it's still a bit early in the day... 🙂

      Author's profile photo Otto Gold
      Otto Gold
      Blog Post Author

      Hi Jelena,

      long time no see/ hear/ read 😀 how is life?

      I find "our" way of defining structures in DDIC and then using REUSE_ to generate flexible multilanguage field catalog better, but I don`t want to fight another holy war.

      Same with OO ABAP. I miss the good old flamewars about religion in the Coffee corner, but that time is long gone I am afraid. D you have any development tricks how to deal with the uncertainty?

      You people all comment, all friends and local legends, but I have learnt no tricks so far here.

      Cheers Otto

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      Touché, my friend. I guess everyone is saving their "tips and tricks" for their own blogs, so that we could get more sweet, sweet points. 🙂 (I'm now down to 600 from 700 two months ago, damm'it!)

      But I have to admit that recently my biggest "tricks" are actually Google and copy/paste. I'd say for 90% of things like strange error messages, user exits, etc. there already is an answer on SCN or elsewhere. Hint: in Google, add 'site:sap.com' to limit search to SAP domain (this includes SCN).

      I also keep a collection of my own development and every time I find some interesting code, I stash it too. It's on a thumb drive (with a copy in a vault in West Virginia mountains ). Few staples like a good ALV report, Excel file upload/download, a report using application log (neat!), output processing, etc. usually take care of most of the development requests. Caveat - unless I'm very, very, very pressed for time, I do not copy/paste code that I don't fully understand; also I update my "templates" if I find more efficient solution.

      And general advise "keep it simple", "do it like you would do for yourself" and "leave comments" is probably the best I can offer on this subject. Cheers! 🙂

      Author's profile photo Otto Gold
      Otto Gold
      Blog Post Author

      Soooo... that means we can hope for a blog by yourself soon? Wow, cool. Can you send a link here when it`s out? I must not miss it 😈

      If you`re too low with the points, I can sincerely recommend going through any SCN content you can find and click like everywhere. You will become platinum contributor in like two hours 🙂 Just kidding.

      Google is cool. Yes. I do the same as you. Except the fact that I often keep my database "public" (as a SCN blog instead of a text file on my harddrive). But Google is king, that is certain.

      Your comments resonate very much with my thoughts. That could mean that from a certain skill level, people work or can work approximately the same and the difference is the care, hardwork, motivation, team, company etc Also interesting finding.

      Anyway, have fun coding or whatever else you do, take care,

      cheers Otto

      Author's profile photo Former Member
      Former Member

      Jelena, it sounds like we were taught be the same guru. i do exactly the same thing, except the copy is buried in my back yard in southern ontario. i do put a lot of the cool things i find on sdn(now scn) and try to teach the people in whatever company i am in now. and thx otto for your blog. it was a good read. btw, i have been abaping for about 17 years. it has been a fun ride, and it has more than paid the bills. i am always looking for the next ride.

      thx, Erik

      Author's profile photo Clemens Li
      Clemens Li

      Hi Otto,

      like this very much!

      #1: I use the same method of creating a skeleton first. Only it happens quite often that I just forget to populate a method or form body 😈 .

      Very good idea of using a structure for parameters. Usually I keep the parameters and select-options global and use them where needed. Nowadays I create a minimal report frame with just the selection screen and creation of a report-specific object. All parameter and select-option values call setter methods in AT <parameter> event. Here I can also do checks if required.

      The object has methods called at/named like events INITIALIZATION, START-OF_SELECTION etc.. Meanwhile I use one class created in the past and inherit a new report-specific one.

      Most methods (including SALV handling) are already present and are redefined if required.

      #2: For the numerous select singles I try to use read methods: At first call, required fields of customizing or other small tables are read fully into static hashed table. All subsequent calls get their results with READ TABLE ... WITH TABLE KEY which is ligtning-fast.

      For other tables, we have to decide if a FOR ALL ENTRIES will get all values required later, then same technique applies.

      If we do not know what is required next, we read the static (or instance) hashed buffer table first. If value is not present, select from database and insert into buffer.

      #Conclusion

      Well, data without data element and without domain should (and can) be avoided. All ALV techniques will bear different problems. Usually I create a local type with full dictionary reference (TYPE data element with domain) and if everything works fine I create a dictionary structure accordingly (never heard this hurts the database 🙂 as structures are no database object). But the you have full F1 (including tech. details) and F4 functions which make users and developers happy.

      Regards

      Clemens

      Author's profile photo Aaron Kitts
      Aaron Kitts

      One recent and painful lesson I've learned for ALV's is to not use CL_GUI_DOCKING_CONTAINER when the report could be run in the background.

      While modifying an old report that used a screen based control container; I thought it would be nice for the report to use every bit of screen available and also not produce the frustrating double scroll bar.  The docking container does this but will not work (at least not for me) in background mode.  Quickly went back to the original container.

      Author's profile photo Dirk Wittenberg
      Dirk Wittenberg

      Hi Aaron,

      all the enjoy controlls need a front end and a background job has no front end. So it's not only for you. You just have to distinguish IF SY-BATCH IS INITIAL ...

      Regards,

      Dirk

      Author's profile photo Former Member
      Former Member

      I know that your comment might be outdated already (Got to it only now),

      but I wanted to add a short comment of my own:

      I think that you should better use method cl_gui_alv_grid=>offline instead of simple sy-batch check.

      FYI,

      Shai

      Author's profile photo Clemens Li
      Clemens Li

      Hi All,

      I'd prefer the use of public static method CL_SALV_MODEL=>IS_OFFLINE( ) because it returns a SAP_BOOL of 'X' for offline. This follows common use in ABAP as opposed to

      cl_gui_alv_grid=>offline( ) returning INT4 value of 1 for offline.

      OK if you check if the result is initial, the use is equivalant.

      Just to mention it: SY-BATCH is true only for background processing, the methods mentionend also take care of WEB processing, exporting list to memory, availability of SAPGUI and some stuff I do not understand myself.

      Conclusion: The methods mentioned are both superior to SY-BATCH although the latter will do in 99 % of all cases.

      Regards

      Clemens

      Author's profile photo Sandro Ramos
      Sandro Ramos

      Hi Aaron,

      Try testing SY-BATCH (like Dirk said above) and then use fm REUSE_ALV_BLOCK_LIST_APPEND, i suppose you have an internal table for each dock container.

      Regards,

      Sandro Ramos

      Author's profile photo Aaron Kitts
      Aaron Kitts

      Hi Sandro and Dirk,

      I posted my experience so other could learn from my misfortune.  Thanks for your input and I bet it will help others. 

      AK  

      Author's profile photo Alejandro Bindi
      Alejandro Bindi

      Good observations. I'm not totally convinced with the parameters trick though.

      There are times when a structure is more appropiate and there are times when it's not.

      The structure approach has some drawbacks too, for instance:

      - No way to make some of the parameters optional or default-valued.

      - Need to declare the structure in the calling code, hence slightly complicating it and making it dependent on the structure besides of the called module.

      On the ALV catalog topic, I've found this to be a great approach:

      http://wiki.sdn.sap.com/wiki/display/Snippets/ALV+fieldcatalog+-+create+for+ANY+table

      This works for generating both LVC (ALV control) and SLIS catalogs by using two separate methods.

      Author's profile photo Former Member
      Former Member

      One little trick I always use is "parameter / control table". Just move all the fixed values / constants used in the program to control table and read the values from table before using them in the code. Update the table whenever necessary and program is ready to go. No need change the program.

      Author's profile photo Otto Gold
      Otto Gold
      Blog Post Author

      True, I do the same. Cool you mention that.

      Maybe this blog will give me enough to compile it into a part two 🙂 Even if not, it can help some people as is (with the discussion, which is cool!), I hope.

      Cheers Otto

      Author's profile photo Former Member
      Former Member

      I do like to use the standard BAL log implementation on my reports.

      With just few lines of code I can keep track on the behavior of the program.

      I can save hours every time an user called me asking for explanation or asking what's have happened. I just go on SLG1 and check what the log is telling!

      Have a nice day!

      Author's profile photo Otto Gold
      Otto Gold
      Blog Post Author

      Hi Damien,

      thanks for the suggestion. Maybe you can share what standard (demo maybe even ?) program you use to copy the thing from? That`s always handy - to know good examples of use in standard where you can quickly copy from (especilly by clients who don`t allow internet access).

      Cheers Otto

      Author's profile photo Clemens Li
      Clemens Li

      Hi Otto,

      some months ago I created a class for this with some methods

      ADD_SY_MSG add a message from SY- fields

      CLOSE_MESSAGE_BOX close message dialog box

      CREATE create instance

      DISPLAY_MESSAGE_BOX Anzeigen der Nachrichten

      GET_HIGHEST_MSGTY_XAEWIS get highest message type of messages stored - Priority 'XAEWIS'

      GET_MSG_TABLE get message table in BAPIRET2 table

      LOAD load from database

      NEW create new instance of message log-Handler

      SAVE save messages in database (usually not used in test run of program )

      First: How to use

      i.e.

        MESSAGE s070(<message class>) WITH sy-datum sy-uzeit."[use INTO MV_MESSAGE string variable if screen output is not wanted]

      * Program executed on & at &

      log_sy_msg_add( )." This is the only method created in specific report class, create attribute mo_log type ref to ZCL_SBAL_APPL_LOG

      method LOG_SY_MSG_ADD.

        DATA:

          lv_balnrext  TYPE balhdr-extnumber. "Type BALNREXT.

        WRITE:

          sy-datum TO lv_balnrext LEFT-JUSTIFIED,

          sy-uzeit TO lv_balnrext+11 LEFT-JUSTIFIED.

        CONCATENATE:

          lv_balnrext

          '<individual report text, i.e. cleanup done>'

          INTO lv_balnrext

          SEPARATED BY space.

        IF mo_log IS NOT BOUND.

          mo_log =

            /item/cl_edm_sbal_appl_log=>create(

              iv_balnrext  = lv_balnrext

              iv_balobj_d  = '<your log object created in SLG0>'

              iv_balsubobj = '<your log subobject created in SLG0>'

      *       iv_baldate   = SY-DATUM

      *       iv_baltime   = SY-UZEIT

      *       iv_baluser   = SY-UNAME

      *       iv_baltcode  = SY-TCODE

      *       iv_balprog   = SY-CPROG

                  ).

        ENDIF.

        mo_log->add_sy_msg( ).

      endmethod.

      At report end, in PBO you may call mo_log->DISPLAY_MESSAGE_BOX( ) to display the messages recorde in log

      If it is an update run (or if you want), use  mo_log->save( ).

      To retrieve already stored messages, fill lt_balhdr from BALHDR application log header table, then

        lo_log = zcl_sbal_appl_log=>new( ).

        lo_log->load( lt_balhdr ).

        lo_log->display_message_box( ).

      class /ITEM/CL_EDM_SBAL_APPL_LOG definition

        public

        create private .

      *"* public components of class /ITEM/CL_EDM_SBAL_APPL_LOG

      *"* do not include other source files here!!!

      public section.

        constants BALOBJ_EDM type BALOBJ_D value '/ITEM/EDM'. "#EC NOTEXT

        constants BALSUBOBJ_PROF_SEND type BALSUBOBJ value '/ITEM/PROF_SEND'. "#EC NOTEXT

        methods GET_HIGHEST_MSGTY_XAEWIS

          returning

            value(RV_MSGTY) type SY-MSGTY .

        methods CLOSE_MESSAGE_BOX

          for event CLOSE of CL_GUI_DIALOGBOX_CONTAINER .

        class-methods CREATE

          importing

            !IV_BALNREXT type BALNREXT optional

            !IV_BALOBJ_D type BALOBJ_D default '/ITEM/EDM'

            !IV_BALSUBOBJ type BALSUBOBJ default '/ITEM/PROF_SEND'

            !IV_BALDATE type BALDATE default SY-DATUM

            !IV_BALTIME type BALTIME default SY-UZEIT

            !IV_BALUSER type BALUSER default SY-UNAME

            !IV_BALTCODE type BALTCODE default SY-TCODE

            !IV_BALPROG type BALPROG default SY-CPROG

          preferred parameter IV_BALSUBOBJ

          returning

            value(RO_INSTANCE) type ref to /ITEM/CL_EDM_SBAL_APPL_LOG .

        class-methods NEW

          returning

            value(RO_INSTANCE) type ref to /ITEM/CL_EDM_SBAL_APPL_LOG .

        methods ADD_SY_MSG .

        methods DISPLAY_MESSAGE_BOX

          importing

            !IV_GRIDTITLE type BAL_GRIDTITLE optional

            !IV_WIDTH type INT4 default 730

            !IV_HEIGHT type INT4 default 133

            !IV_TOP type INT4 default 150

            !IV_LEFT type INT4 default 500

          preferred parameter IV_GRIDTITLE .

        methods GET_MSG_TABLE

          returning

            value(RT_BAPITET2) type BAPIRET2_T .

        methods SAVE

          returning

            value(RT_BAL_S_LGNM) type BAL_T_LGNM .

        methods LOAD

          importing

            !IT_BALHDR type BALHDR_T .

      *"* private components of class /ITEM/CL_EDM_SBAL_APPL_LOG

      *"* do not include other source files here!!!

      private section.

        data MT_BALLOGHNDL type BAL_T_LOGH .

        data MV_BALLOGHNDL type BALLOGHNDL .

        data MO_DIALOGBOX_CONTAINER type ref to CL_GUI_DIALOGBOX_CONTAINER .

        data MV_BALCNTHNDL type BALCNTHNDL .     

      METHOD ADD_SY_MSG.

        DATA:

          ls_bal_s_msg    TYPE bal_s_msg.

        CHECK:

          syst-msgty IS NOT INITIAL,

          syst-msgid IS NOT INITIAL,

          syst-msgno IS NOT INITIAL.

        ls_bal_s_msg-msgty = syst-msgty .

        ls_bal_s_msg-msgid = syst-msgid .

        ls_bal_s_msg-msgno = syst-msgno .

        ls_bal_s_msg-msgv1 = syst-msgv1 .

        ls_bal_s_msg-msgv2 = syst-msgv2 .

        ls_bal_s_msg-msgv3 = syst-msgv3 .

        ls_bal_s_msg-msgv4 = syst-msgv4 .

        CALL FUNCTION 'BAL_LOG_MSG_ADD'

          EXPORTING

            i_log_handle        = mv_balloghndl

            i_s_msg             = ls_bal_s_msg

      *    IMPORTING

      *      e_s_msg_handle      = e_s_msg_handle

      *      e_msg_was_logged    = e_msg_was_logged

      *      e_msg_was_displayed = e_msg_was_displayed

          EXCEPTIONS

            log_not_found       = 1

            msg_inconsistent    = 2

            log_is_full         = 3

            OTHERS              = 4.

        IF sy-subrc <> 0.

          MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno

                  WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

        ENDIF.

      ENDMETHOD.

      METHOD close_message_box.

      * Make sure Box is open

        CHECK mv_balcnthndl IS NOT INITIAL.

        CALL FUNCTION 'BAL_CNTL_REFRESH'

          EXPORTING

            i_control_handle             = mv_balcnthndl

      *     I_T_LOG_HANDLE               = I_T_LOG_HANDLE

      *     I_T_MSG_HANDLE               = I_T_MSG_HANDLE

      *     I_S_LOG_FILTER               = I_S_LOG_FILTER

      *     I_S_MSG_FILTER               = I_S_MSG_FILTER

      *     I_T_LOG_CONTEXT_FILTER       = I_T_LOG_CONTEXT_FILTER

      *     I_T_MSG_CONTEXT_FILTER       = I_T_MSG_CONTEXT_FILTER

      *     I_SRT_BY_TIMSTMP             = ' '

      *   IMPORTING

      *     E_NO_DATA_AVAILABLE          = E_NO_DATA_AVAILABLE

      *     E_NO_AUTHORITY               = E_NO_AUTHORITY

          EXCEPTIONS

            control_not_found            = 1

            internal_error               = 2

            OTHERS                       = 3

                  .

        IF sy-subrc <> 0.

          MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno

                  WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

        ENDIF.

        CALL FUNCTION 'BAL_CNTL_FREE'

          CHANGING

            c_control_handle  = mv_balcnthndl

          EXCEPTIONS

            control_not_found = 1

            OTHERS            = 2.

        IF sy-subrc <> 0.

          MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno

            WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

        ENDIF.

        CLEAR mv_balcnthndl.

        mo_dialogbox_container->free( ).

        FREE mo_dialogbox_container.

      ENDMETHOD.

      METHOD create.

        DATA:

          ls_bal_s_log TYPE bal_s_log,

          lv_guid_32   TYPE guid_32.

        CREATE OBJECT ro_instance .

      * create unique LOG external number

        IF iv_balnrext IS INITIAL.

          CALL FUNCTION 'GUID_CREATE'

            IMPORTING

              ev_guid_32 = lv_guid_32.

          ls_bal_s_log-extnumber = lv_guid_32.

        ELSE.

          ls_bal_s_log-extnumber = iv_balnrext.

        ENDIF.

        ls_bal_s_log-extnumber = iv_balnrext.

        ls_bal_s_log-object    = iv_balobj_d.

        ls_bal_s_log-subobject = iv_balsubobj.

        ls_bal_s_log-aldate    = iv_baldate.

        ls_bal_s_log-altime    = iv_baltime.

        ls_bal_s_log-aluser    = iv_baluser.

        ls_bal_s_log-altcode   = iv_baltcode.

        ls_bal_s_log-alprog    = iv_balprog.

        CALL FUNCTION 'BAL_LOG_CREATE'

          EXPORTING

            i_s_log                 = ls_bal_s_log

          IMPORTING

            e_log_handle            = ro_instance->mv_balloghndl

          EXCEPTIONS

            log_header_inconsistent = 1

            OTHERS                  = 2.

        IF sy-subrc <> 0.

          MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno

                  WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

        ENDIF.

      ENDMETHOD.

      METHOD display_message_box.

        DATA:

          ls_bal_s_prof    TYPE bal_s_prof,

          lt_balloghndl    TYPE bal_t_logh .

        FIELD-SYMBOLS:

          <balloghndl>     TYPE balloghndl.

      * close anythingopen

        close_message_box( ).

      *       create dialogbox container

        IF mo_dialogbox_container IS NOT BOUND.

          CREATE OBJECT mo_dialogbox_container

            EXPORTING

              width  = iv_width

              height = iv_height

              top    = iv_top

              left   = iv_left

            EXCEPTIONS

              OTHERS = 1.

          IF sy-subrc <> 0.

            MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno

                    WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

          ENDIF.

        ENDIF.

      *       get a display profile which describes how to display messages

        CALL FUNCTION 'BAL_DSP_PROFILE_NO_TREE_GET'

          IMPORTING

            e_s_display_profile = ls_bal_s_prof.

        ls_bal_s_prof-grid_title-gridtitle = iv_gridtitle.

      *       define amount of data to be displayed

        INSERT mv_balloghndl INTO TABLE lt_balloghndl.

        LOOP AT mt_balloghndl ASSIGNING <balloghndl>.

          INSERT <balloghndl> INTO TABLE lt_balloghndl.

        ENDLOOP.

      *       create control to display data

        CALL FUNCTION 'BAL_CNTL_CREATE'

          EXPORTING

            i_container         = mo_dialogbox_container

            i_s_display_profile = ls_bal_s_prof

            i_t_log_handle      = lt_balloghndl

          IMPORTING

            e_control_handle    = mv_balcnthndl

          EXCEPTIONS

            OTHERS              = 1.

        IF sy-subrc <> 0.

          MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno

                  WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

        ENDIF.

        SET HANDLER close_message_box FOR mo_dialogbox_container.

      ENDMETHOD.

      METHOD GET_HIGHEST_MSGTY_XAEWIS.

        DATA:

          lv_prio     TYPE string VALUE 'XAEWIS',

          lv_offset   TYPE sy-fdpos,

          lt_bapiret2 TYPE bapiret2_t.

        lt_bapiret2 = get_msg_table( ).

        CHECK lt_bapiret2 IS NOT INITIAL.

        WHILE lv_offset < STRLEN( lv_prio ).

          LOOP AT lt_bapiret2 TRANSPORTING NO FIELDS

            WHERE type = lv_prio+lv_offset(1).

            EXIT.

          ENDLOOP.

          IF sy-subrc = 0.

            rv_msgty = lv_prio+lv_offset(1).

            RETURN.

          ELSE.

            ADD 1 TO lv_offset.

          ENDIF.

        ENDWHILE.

      ENDMETHOD.

      METHOD get_msg_table.

        DATA:

          lt_balloghndl         TYPE bal_t_logh,

          lt_msg_handle         TYPE bal_t_msgh,

          ls_bal_s_msg          TYPE bal_s_msg.

        FIELD-SYMBOLS:

          <bapiret2>            TYPE bapiret2,

          <balloghndl>          TYPE balloghndl,

          <msg_handle>          TYPE LINE OF bal_t_msgh.

        INSERT mv_balloghndl INTO TABLE lt_balloghndl.

        LOOP AT mt_balloghndl ASSIGNING <balloghndl>.

          INSERT <balloghndl> INTO TABLE lt_balloghndl.

        ENDLOOP.

        CALL FUNCTION 'BAL_GLB_SEARCH_MSG'

          EXPORTING

      *   I_S_LOG_FILTER               = I_S_LOG_FILTER

      *   I_T_LOG_CONTEXT_FILTER       = I_T_LOG_CONTEXT_FILTER

            i_t_log_handle               = lt_balloghndl

      *   I_S_MSG_FILTER               = I_S_MSG_FILTER

      *   I_T_MSG_CONTEXT_FILTER       = I_T_MSG_CONTEXT_FILTER

      *   I_T_MSG_HANDLE               = I_T_MSG_HANDLE

          IMPORTING

      *   E_T_LOG_HANDLE               = E_T_LOG_HANDLE

            e_t_msg_handle               = lt_msg_handle

          EXCEPTIONS

            msg_not_found                = 1

            OTHERS                       = 2

                  .

        IF sy-subrc <> 0.

          MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno

                  WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

        ENDIF.

        LOOP AT lt_msg_handle ASSIGNING <msg_handle>.

          CALL FUNCTION 'BAL_LOG_MSG_READ'

            EXPORTING

              i_s_msg_handle                 = <msg_handle>

      *     I_LANGU                        = SY-LANGU

            IMPORTING

              e_s_msg                        = ls_bal_s_msg

      *     E_EXISTS_ON_DB                 = E_EXISTS_ON_DB

      *     E_TXT_MSGTY                    = E_TXT_MSGTY

      *     E_TXT_MSGID                    = E_TXT_MSGID

      *     E_TXT_DETLEVEL                 = E_TXT_DETLEVEL

      *     E_TXT_PROBCLASS                = E_TXT_PROBCLASS

      *     E_TXT_MSG                      = E_TXT_MSG

      *     E_WARNING_TEXT_NOT_FOUND       = E_WARNING_TEXT_NOT_FOUND

            EXCEPTIONS

              log_not_found                  = 1

              msg_not_found                  = 2

              OTHERS                         = 3

                    .

          IF sy-subrc <> 0.

            MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno

                    WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

          ENDIF.

          APPEND INITIAL LINE TO rt_bapitet2 ASSIGNING <bapiret2>.

          <bapiret2>-type       = ls_bal_s_msg-msgty.

          <bapiret2>-id         = ls_bal_s_msg-msgid.

          <bapiret2>-number     = ls_bal_s_msg-msgno.

          <bapiret2>-message_v1 = ls_bal_s_msg-msgv1.

          <bapiret2>-message_v2 = ls_bal_s_msg-msgv2.

          <bapiret2>-message_v3 = ls_bal_s_msg-msgv3.

          <bapiret2>-message_v4 = ls_bal_s_msg-msgv4.

          MESSAGE

            ID      <bapiret2>-id

            TYPE    <bapiret2>-type

            NUMBER  <bapiret2>-number

            WITH    <bapiret2>-message_v1

                    <bapiret2>-message_v2

                    <bapiret2>-message_v3

                    <bapiret2>-message_v4

            INTO    <bapiret2>-message.

        ENDLOOP.

      ENDMETHOD.

      METHOD load.

        DATA:

          lt_balloghndl TYPE bal_t_logh.

        FIELD-SYMBOLS:

          <balloghndl>  TYPE balloghndl.

        CALL FUNCTION 'BAL_DB_LOAD'

          EXPORTING

            i_t_log_header                      = it_balhdr

      *   I_T_LOG_HANDLE                      = I_T_LOG_HANDLE

      *   I_T_LOGNUMBER                       = I_T_LOGNUMBER

      *   I_CLIENT                            = SY-MANDT

      *   I_DO_NOT_LOAD_MESSAGES              = ' '

      *   I_EXCEPTION_IF_ALREADY_LOADED       = I_EXCEPTION_IF_ALREADY_LOADED

          IMPORTING

            e_t_log_handle                      = lt_balloghndl

          EXCEPTIONS

            no_logs_specified                   = 1

            log_not_found                       = 2

            log_already_loaded                  = 3

            OTHERS                              = 4

                  .

        IF sy-subrc <> 0.

          MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno

                  WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

        ENDIF.

        LOOP AT lt_balloghndl ASSIGNING <balloghndl>.

          INSERT <balloghndl> INTO TABLE mt_balloghndl.

        ENDLOOP.

      ENDMETHOD.

      METHOD NEW.

        CREATE OBJECT ro_instance .

      ENDMETHOD.

      METHOD save.

      * Save current new LOG (s)

        DATA:

          lt_balloghndl TYPE bal_t_logh.

        INSERT mv_balloghndl INTO TABLE lt_balloghndl.

        CALL FUNCTION 'BAL_DB_SAVE'

          EXPORTING

      *   I_CLIENT               = SY-MANDT

      *   I_IN_UPDATE_TASK       = ' '

      *   I_SAVE_ALL             = ' '

            i_t_log_handle         = lt_balloghndl

          IMPORTING

            e_new_lognumbers       = rt_bal_s_lgnm

          EXCEPTIONS

            log_not_found          = 1

            save_not_allowed       = 2

            numbering_error        = 3

            OTHERS                 = 4

                    .

        IF sy-subrc <> 0.

          MESSAGE ID sy-msgid TYPE sy-msgty NUMBER sy-msgno

                  WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.

        ENDIF.

      ENDMETHOD.

      Regards

      Clemens

      Author's profile photo Alejandro Bindi
      Alejandro Bindi

      This is something I got definitively used to have when working with i.s.h.med, which has it's own class for it: CL_ISHMED_ERRORHANDLING. Another standard one is the interface IF_RECA_MESSAGE_LIST (explained in http://wiki.sdn.sap.com/wiki/display/profile/2007/07/09/Message+Handling+-+Finding+the+Needle+in+the+Haystack).

      It's far more flexible to use some kind of handler like these ones, than relying on the MESSAGE sentence to control the program flow, allowing for example to throw several warnings and errors at once.

      Author's profile photo Rüdiger Plantiko
      Rüdiger Plantiko

      That's precisely what I recommend in our programming guidelines. We have a class ZCL_MESSAGES, which is a wrapper of the application log (package SZAL), which by the guidelines has to be the single class to be used for message collection. To collect a single message, one has to write (e.g.)

      message s901(v1) with vbak-vbeln into sy-msgli.

      go_log->add( ).

      Here, go_log, is an instance of the class zcl_messages. The message is transported via the sy-msg.. fields and the defaulting of import parameters iv_msgid, iv_msgno and so on..., and then collected in the application log.

      The first line, the message statement, is important for things like the where-used list of messages. The where-used reference of messages is a heavily important tool in ABAP support. We often only get a screenshot where something strange happened, and the only method to get into the code often is the visible text of the message in that screenshot.

      Author's profile photo Former Member
      Former Member

      Hello Otto,

      do you know the Interface IF_RECA_MESSAGE_LIST. It provides a standard message handler. It's great! I use it in almost all of my reports.

      Please refer to the Blog/Wiki article from Uwe Schiferstein:

      http://wiki.sdn.sap.com/wiki/display/profile/2007/07/09/Message+Handling+-+Finding+the+Needle+in+the+Haystack

      Best regards,

      Tapio

      Edit: Didn't saw the post from Alejandro Bindi.

      Author's profile photo Matthew Billingham
      Matthew Billingham

      For example two, I might use something like:

      ADD_OBJECT_TO_QUERY - this adds an object that I want later to get the details of

      GET_OBJECTS_DETAILS - this goes through all objects added, gets the details in an efficient way.

      This divides the process into two parts;

      1. Prepare data for selection
      2. Do the selection

      By splitting the logic in this way, you've decoupled the preparation from the selections. Decoupling generally allows more flexibility (less change) when you need to make a change, as you've added another layer of abstraction.

      What's also nice about this, is that if the objects are different, you can use the same calls by subclassing (or using an interface). So it doesn't matter what the objects are, you use the same code.

      DATA: lr_object   TYPE REF TO superclass,
            ltr_objects TYPE STANDARD TABLE OF superclass.
      
      lr_object = superclass=>factory( 'GERANIUM' ).
      INSERT lr_object INTO TABLE ltr_objects.
      lr_object->add_object_to_query( ).
      
      lr_object = superclass=>factory( 'AARDVARK' ).
      INSERT lr_object INTO TABLE ltr_objects.
      lr_object->add_object_to_query( ).
      
      LOOP AT ltr_objects INTO lr_object.
        lr_object->get_object_details( ).
      ENDLOOP.
      
      Author's profile photo Adam Krawczyk
      Adam Krawczyk

      Hi Otto,

      Thank you for interesting blog and good hints.

      1. I agree that sometimes grouping parameters into structure is good idea, especially with report input parameters that are passed to the logic layer. As it is common to update screen input components when user changes mind, having structure for that is easy to maintain. On the other hand we cannot overuse it everywhere, as clearly defined input and output parameters define method interface and helps with testing.

      And of course writing methods signatures and calls without implementation is a good idea that saves time. If you combine it with unit tests - understand business requirements and prepare validation before implementation is ready - that drives good design and brings quality.

      2. It is fact that even good design, without performance considerations, can fail. We can have well built class, covered with unit tests but if method like GET_ITEM_DETAILS is executed thousands times from outside (instead of once for all), performance issue may occur and reimplementation is needed.

      I am fan of putting all database statements (or functions that use database) into separate DAO class and use queries through this class in business logic. This offers reusability of already tested database statements. We can define two methods variants - GET_ITEM_DETAILS and GET_MULTI_ITEMS_DETAILS. Then use what we actually need in current context. In addition DAO concept allows to create unit tests without database dependency:

      http://scn.sap.com/community/abap/testing-and-troubleshooting/blog/2013/03/21/abap-unit-tests-without-database-dependency--dao-concept

      3. Authorization in low level, most used functions - yes and no. Yes - as you mentioned, you do not need to repeat code as it is already built in somewhere into call hierarchy. No - I saw many performance issues due to authorization checks (waste on seconds level but still important for dialog reports), when program was running over large table and performing authorization checks for same user thousand times, is it really needed?

      And some tricks from me:

      - Keep class attributes in single structure. It saves space in definition. All methods operates on single attributes (subset of structure), but you can process structure or table of it easily as well. ABAP is in fact optimized for internal tables processing.

      - If we assume large data for report, it is worth to do measurements with ST05 or SE30 and see if something goes wrong there, even for small data set in dev system. Especially check indexes, if internal tables are sorted or queries do not ask for same data many times - 3 common performance issues. In case if queries are repeated, we can introduce own CACHE class that easily integrates with DAO class (or even extends it).

      - Do not create constructor with many parameters unless you are really sure it is only way to initialize object and parameters are required. I prefer to leave constructor without parameters and create separate INITIALIZE methods with different set up of parameters - initialization for different purposes. Especially for unit tests it is convenient to start with empty object for some test cases.

      Regards 🙂

      Adam

      Author's profile photo Otto Gold
      Otto Gold
      Blog Post Author

      Hi! All points in your comment are very valid, but... your blog... that is very, Very, VERY cool stuff! Thank you for that one. It tells the story A to Z (which is something I value in the blogs the most) and I`ve learnt a lot there 🙂

      Putting my watch on you, Sir 😛

      Have a nice day and see you around again very soon,

      cheers Otto

      Author's profile photo Matthew Billingham
      Matthew Billingham

      Hi Adam

      Do you really mean having all class attributes in one single structure? That seems bad to me. 

      1. You may well have attributes that are not used
      2. It reduces readability
      3. It increase work if you need to refactor - e.g. make some attributes protected when you've the opportunity to subclass.

      I do use structures where the attributes (or parameters) fit together (like a record from a table), or when using persistence classes, but just one attribute for the whole class? No... If you're needing to transfer a whole bundle of data, pass a class reference instead.

      I also don't agree with you regarding constructors. One of the purposes of the constructor is to ensure that everything that needs initialising gets initialised. It can be unsafe if you're relying on the programmer consuming your class to do things in the right order. Unfortunately ABAP_objects doesn't allow directly for overriding parameters, so I can see why you've adopted your approach. What I do is have optional parameters, and checks within the constructor to ensure I've got all I need at create time... well, to be honest, I use the factory pattern most often, but the principle applies!

      matt

      Author's profile photo Adam Krawczyk
      Adam Krawczyk

      Hi Matthew again,

      You are right that structure for all fields of class is not always good idea, but I like the proposal of parameters groupped together. Everything depends on case, for some cases it is better like performance aspect. If you can represent class as structure (or at least part of it), ABAP offers syntax for internal tables operations like sorting, finding by key etc. You do not have it when you have table of objects. I see it as joined power of OO world with ABAP syntax features. Of course you can always have method that creates structure from attributes, but this is additional work which matters with mass processing.

      1. You may have unused attributes in class as well.

      2. It does not need to reduce readability as this is only private member and data holder, exposed to external world through getters and setters for its attributes.

      3. Correct. On the other hand you can save time when you need to pass subset of attributes, sort, organize objects for massive processing etc.

      Regarding constructors you are right - I miss possibility of having many constructors with different parameters. It is often case that I can intialize object from different sources. I prefer to build two INITIALIZE methods than have 5 parameters optional in constructor and provide 3 of them XOR 2 of them (depending on case). If there is one constructor I am obligated to use it always! If I extend it with optional parameters, I may decrease readability. But again - there are cases that we should force user to use constructor and defined parameters to avoid wrong state. Factory pattern is great and I think that INITIALIZE in object borrows rules from it.

      Regards 🙂

      Adam

      Author's profile photo Matthew Billingham
      Matthew Billingham

      Well, we'll never reach a definitive answer - this is a subjective topic. 🙂 The point about unused attributes is that they can be spotted by SCI, but I'm not sure that unused structural fields can be.

      matt

      Author's profile photo Rainer Hübenthal
      Rainer Hübenthal

      The variant based on a program-internal table should only be used for rapid
      prototyping since the following restrictions apply:

      • Performance is affected since the code of the table definition must always
        be read and interpreted at runtime.
      • Dictionary references are only considered if the keywords LIKE or INCLUDE
        STRUCTURE (not TYPE) are used.

      Thats why this FM is not allowed in our reports. Object SALV is available. No need for this FM.

      Old reports still using the REUSE FMs are changed by an intelpattern creating a standard. static fieldcatalog. Saving developping time is not argument cause this is below 5% of a project. Not worth thinking of it.