Quantcast
Channel: ABAP Development
Viewing all 948 articles
Browse latest View live

Linear search vs binary search in internal table

$
0
0

Hi,

 

Here will show difference between the linear search vs binary search in the internal table.

 

First let me give me some intro about the liner search linear search or sequential search is a method for finding a particular value in a list, that consists of checking every one of its elements, one at a time and in sequence, until the desired one is found.

 

Binary search: It finds the position of the specified value in the a sorted array

 

 

For ex: linear search in a internal table

 

READ TABLE ITAB INTO WA WITH KEY K = 'X'.

 

Binary search in internal table:

 

READ TABLE ITAB INTO WA WITH KEY K = 'X' BINARY SEARCH.

 

If internal tables are assumed to have many (>20) entries, a linear search through all entries is very time-consuming. Try to keep the table ordered and usebinary search or used a table of type SORTED TABLE. If TAB has n entries, linear search runs in O( n ) time, whereas binary search takes only O( log2( n ) ).

 

And when you measure the runtime of the liner search is takes 77 microseconds and for binary search is 0 micro seconds


SE30 Tips and Tricks for abap objects

$
0
0

Hi

 

When you go to SE30  tcode some of them will think it mainly for the Evaluate and measure a particular program or FM or etc.. But when you do press F6 . SE30.png

It goes to ABAP objects performance examples where you test and measure the runtime of each statements code.

 

SE30_1.png

Here you can explore and come to know each abap statements and it really helpful in Performance tuning of your program.

 

 

Thanks,

Siva

Using subqueries in ABAP

$
0
0

Here i'm gonna show on performance of using the sub quereis in program instead of two select statements..

 

For EX: Using the two select statements ...

 

SELECT * FROM SPFLI

  INTO TABLE T_SPFLI

  WHERE CITYFROM = 'FRANKFURT'

    AND CITYTO = 'NEW YORK'.

SELECT * FROM SFLIGHT AS F

    INTO SFLIGHT_WA

    FOR ALL ENTRIES IN T_SPFLI

    WHERE SEATSOCC < F~SEATSMAX

      AND CARRID = T_SPFLI-CARRID

      AND CONNID = T_SPFLI-CONNID

      AND FLDATE BETWEEN '19990101' AND '19990331'.

ENDSELECT.

 

 

For EX: the above two select statements can be replaced by  the sub queries

 

SELECT * FROM SFLIGHT AS F INTO SFLIGHT_WA

    WHERE SEATSOCC < F~SEATSMAX

      AND EXISTS ( SELECT * FROM SPFLI

                     WHERE CARRID = F~CARRID

                       AND CONNID = F~CONNID

                       AND CITYFROM = 'FRANKFURT'

                       AND CITYTO = 'NEW YORK' )

      AND FLDATE BETWEEN '19990101' AND '19990331'.

ENDSELECT.

 

And performance wise also sub queries is far better.

ABAP development in ECLIPSE! what is it?

$
0
0

Being an old timer and having worked for quite a long time with ABAP, it was with disbelief that I first downloaded the ECLIPSE studio and installed all the Add-ons for ABAP. Installing this new toolkit and tried to connect to the ABAP 7.3 server we just installed. I wasn't sure how I could access all the artifacts stored on App server from a desktop based IDE (we all know how robust and . But, once I was able to connect to the system and started accessing the existing programs and developing new programs I was feeling better.

Here are my observations and thought on a few initial questions you may have while switching from SE38 editor to ECLIPSE!

1.      Connecting to the App Server

Like you add multiple systems to the SAP logon pad, the Eclipse IDE allows you to add any number of ABAP systems.

You can add any number of these to your project space and connect to them as well at the same time. You can as well access the repository objects f0072om all these systems at the same time.

Integrity of the object source code is maintained by ensuring the object is locked against the user editing it. Thus any clashes with editing the object via SAP GUI and Eclipse parallel are prevented.

2.      Look & Feel of the IDE

The ECLIPSE IDE is quite trendy and offers most of  the features of the ABAP editor such as key-word highlight, auto-fill the keyword, formatting and indentation, F1-help for keywords. Setting break-points and debugging features are as well available.       

An other interesting feature is the ability to launch the object in SAP GUI from within ECLIPSE. This feature is quite helpful for those, who want to take some more time before completely switching to ECLIPSE. Also, this feature is useful for designing the traditional ABPA Dynpro’s.

However, looks like objects such as Scripts and Smartforms are not supported by this IDE. For these, you still have to use the traditional SAP GUI.

               On the whole, this new Eclipse based IDEis a step into the future. A ‘one stop place’ to create all the repository objects. This resembles the SE80 we are used to in the SAP GUI,

ABAP in Eclipse - Obect list.png

 

3.      Can I work offline with my ABAP objects?

When I simulated this by opening a package in my ECLIPSE and disconnected the network, I wasn’t able to access any other un-opened objects or save changes made to the object being edited.

Looks like for ABAP developers, working offline is still some distance away.

Would have liked the option to check-out and check-in the objects required for my build.

4.      How about transport management?

Transport management is integrated into the Eclipse editor, if a program is created in a transportable package (not $TMP), the tool prompts you to either add the modification to an existing transport or to create a new transport.

5.      Can I continue doing my RICEF objects through ABAP in ECLIPSE?

As mentioned above, other than Scripts and smartforms, all other object types you develop in SAP GUI seem to be available here. Enhancements to SAP objects is another aspect that is not available.

6.      Integration with HANA Studio

The ADT (ABAP Development Tools) can be inserted as add-ons in the  HANA studio thus allowing the developer to navigate to between the HANA modeler and ABAP perspectives. With SOH being the future, this will give great flexibility to the developers in consuming the HANA artifacts from ABAP.

 

Next will share my feedback on the debugging and unit testing via ECLIPSE.

No Comment

$
0
0

I am not sure what kind of commenting strategy you use at your company.

 

* Do you hear the people sing?

 

In a lot of SAP delivered code I either see no comments or comments in German.

 

* Singing a song of angry men?

 

Thankfully Google translate can give a literal kind of hint about what the authors were trying to get at.

 

* It is the music of a people

 

I like to provide comments with a least a hint at what is going on or why a strategy has been used.

 

* Who will not be slaves again!

 

At one client in particular one of the team ( and a senior member ) at that liked to included song lyrics from 'Les Miserables'.

 

* When the beating of your heart

 

It didn't make you miserable. I found it was quite an entertaining way to brighten up your day

 

* Echoes the beating of the drums

 

So when you next find an interesting or quirky comment block.

 

* There is a life about to start

 

Say a thank you to the developer who put it in there to make you smile.

 

* When tomorrow comes!

How to Prevent Disruption

$
0
0

In the last weeks I blogged twice about disruptions in SAP standard software– now I turn the tables and will tell what I did to prevent disruptions that have been caused by ourselves. I want to discuss what we, as ABAP developers, can do to prevent disruption. Why is that important? At the one hand we benefit from evolution of SAP NetWeaver and SAP Business Suite, on the other hand we have to take care that our solutions can smoothly survive any SP and Ehp upgrade. Every ABAP developer knows some guideline – for example to avoid modifications but is it the best we can do? I don’t think so. As one of the leading software architects of a huge ABAP solution I feel responsible to establish this quality standard because we want to benefit from new SAP releases in many ways – think of our HANA and Mobile strategy for example. And so I will blog about my activities in this area.

 

But before going into detail let me explain how the story went on.

 

SAP Listens

 

As you might know software updates have been really painful for me in the last time. Since some months I’ve been into discussion with SAP and in many constructive talks we found solutions. I am glad that SAP listened carefully and understood the problem. These are really good news!

Although we found solutions for existing problems I am thinking about the future: we want to benefit from new SAP solutions and technology and will keep a high Ehp level. How can we manage the risk of SAP software updates?

 

Evolution of SAP Software is Necessary

 

The reason is that we have to evolve our IT solutions for many reasons:

  • we have to support out business using better and completely new IT solutions,
  • we want to reduce const,
  • we fulfill merging and acquisitions and last but not least
  • we have to obey compliance aspects.

 

SAP Business Suite and SAP NetWeaver platform constantly evolves and we benefit in various aspects: from performance optimization, new technology (think of HANA, Mobile, new UIs), new development tools, improved processes and much more. If you want to benefit from this evolution then you have start to think about the risks and how manage the risk of disruption.  

 

In my opinion there are many things SAP can do to prevent disruptions as consequence of a constant evolution:

  • SAP spends time in Customer Validations and Ramp Ups to ensure that the solution is mature.
  • An important aspect is time. The more time we have we can test all necessary measures: installation, administration, performance and so on. The reason is that IT architecture often is not agile: if problems cause delays than they will lead to trouble because usually changes in systems are difficult and can be done only in small timeslots. If these are over sometimes a whole upgrade project can be delayed by months. If a new solution is available early so that we can do tests we can reduce the risks of disruptions.
  • SAP should spend budget for rectification of defects after drastic changes. This can be done with Customer Connection and Customer Engagement Initiatives.

 

The message is simple: there are many ways to manage consequences of changes – and only if this can’t be done a disruption will occur. But this means also that we as developers of customer and partner solution are also responsible to prevent disruptions:  we have to learn how to make our software adaptive to change and to prevent disruption and this is what I will talk about in the rest of this blog.  

 

Implicit Enhancements Considered Harmful

 

The reason is very simple: Implicit Enhancements have nearly all bad properties of modifications. They change the SAP code instead of using defined interfaces like BAdIs. They are code injection in SAP standard code and can cause trouble:

  • What happens if the program logic within SAP standard changes and so the implicit enhancement isn’t called?
  • What happens if you use another application / business function and an enhanced functionality that is called now from another application? Are there unwanted side-effects?

 

These are the same problems you have with modifications when checking conflicts – you only have to use transaction SPAU_ENH instead of SPAU. I looked at a lot of implicit enhancements within last time and learned the following:  

  • Some of them are completely useless and could be removed. This is an interesting phenomenon: they have been introduced some time ago and nobody controlled them.
  • Some of the enhancements are problematic: the developer who introduced wasn’t available and it was difficult to figure out the reason for it. This is sad situation compared to “proper” custom or partner development where there concrete artifacts (online and offline documentation) for every program.
  • Many developers are well aware of the risks of implicit enhancements and used them only if there was no other chance. At the same time they tried to convince SAP to introduce a BAdI. They know about the risks and do explicit tests after every software update (SP and Ehp).

 

So as leading architect I am glad that we introduced the same governance process for implicit enhancements like for modifications. And I am also glad that we have been able to remove all implicit enhancements on one of our development systems within a week.

 

Perform Package Checks!

 

I am also glad that we introduced package checks in ABAP development of solution. We use them to guarantee installability of software components, to structure our solution and reduce the risk of side-effects after changes in a complex development landscape.

 

Another reason is that we want code against the same package interfaces like SAP Business Suite does to ensure that we only use public interface and avoid using private implementation details of packages of SAP software.

 

I made a good experience with package SAI and its subpackages: the web service runtime of AS ABAP was evolved in many SPs but the elements exposed in package interfaces remained stable. I can tell you many other examples where usage of ABAP package interfaces prevent you from damage of non-compatible changes.

 

What are your Best Practices?

 

To summarize: we want to benefit from the evolution of SAP NetWeaver and SAP Business Suite because this is essential for our IT strategy. Therefore we invest to harden our solution. I think SAP should encourage others to do the same perhaps in Guidelines for Best built Apps: http://scn.sap.com/community/best-built-applications

 

Of course we use additional best practices like automated testing but it would be interesting what you as software developer can do make your custom solution as stable and robust as possible. In my opinion this is the most important quality criteria for any customer or partner solution and far more important than naming conventions for local variables for example. Of course many people love those discussions (“Is prefix lt_ for internal table ok or l_tab_? What about l_tas_ for sorted tables? What about l_tas_s_ for sorted tables with secondary index?”). In my opinion those discussions are a waste of time. For me it is far more important to emphasize that our solutions that are ready for the future is much more important.

 

How do you achieve this goal? What are your best practices?

Adding nodes in SAP Menu

$
0
0

Sometimes, it is required to add some custom menu in the main SAP Menu, as the customer wants to keep some specific reports and transactions in a certain Node. This can be easily achieved via the SAP Area Menu Transaction SE43:-

 

 

 

Transaction SE43 -> Area menu: Z_EXAMPLE

 

Press “change” and position cursor on the level to add to the area menu:

 

pic1.png

pic1.png

pic1.png

Press the button “add entry as subnode” or “add entry at same level”

 

 

 

pic1.png

 

Enter the Text and a transaction (or no transaction if only a folder should be created)

 

pic1.png

Press  “save”.

Run the Area Menu T-code: Z_EXAMPLE. Here, we can see the area menu called in the SAP MENU.

 

To integrate Area Menu Z_EXAMPLE with Standard SAP modules, the area menu Z_EXAMPLE Has to be created as a sub-node in the area menu for Standard SAP – ' S000'.

pic1.png

Skip the Authority Check with the ABAP Debugger Script

$
0
0

StopLight.jpgThis is my third post on practical uses of the ABAP Debugger Script you can find the first one at How to Create a Watchpoint for a Field Symbol in the ABAP Debugger and the second atTracing a Program with the ABAP Debugger Script.

 

Sometimes programs can have complicated authorization checks and we may not have authorization to do certain things while testing. When this has happened to me in the past, I selected to create a break point on the ABAP command AUTHORITY-CHECK. When the program stopped, I stepped the program forward once and changed sy-subrc to 0, so that I could test the secured area. While this process works, it can also be time consuming if there are many authority checks scattered throughout. To speed up the process, you can use the debugger script and automate tricking the authority check.

 

To create the script, go to transaction SAS and click on the SAS Script Editor Tab.png tab. Erase any code or comments that is currently in the script method. Now we need to add the command for moving the debugger forward one step. To do this, click on the Script Wizard Button.png and select the Debug Step option under the debugger control folder. The code for controlling the debugger will be copied to the script method. Copy the execute value (CL_TPDA_SCRIPT_DEBUGGER_CTRL=>DEBUG_STEP_OVER) to be passed as the P_COMMAND parameter. Remove the stars commenting out the try and catch block code.

Next, we want to add the code to change the value of sy-subrc to 0 so that the program thinks we passed the authority check. Place your cursor a couple of lines after the ENDTRY from the earlier command and click the Script Wizard Button.png button. Expand the folder titled "Variable Information" and then expand the folder titled "Change Variable Value" and select "Simple Variable or String". Set the P_NEW_VALUE parameter to '0' and the P_VARNAME parameter to 'sy-subrc' (both with single quotes). Now, remove the stars commenting out the try and catch block code.

Your script method should look like the below:

[abap]

  METHOD script.
*************************************************
* debugger commands (p_command):
* Step into(F5)   -> CL_TPDA_SCRIPT_DEBUGGER_CTRL=>DEBUG_STEP_INTO
* Execute(F6)     -> CL_TPDA_SCRIPT_DEBUGGER_CTRL=>DEBUG_STEP_OVER
* Return(F7)      -> CL_TPDA_SCRIPT_DEBUGGER_CTRL=>DEBUG_STEP_OUT
* Continue(F8)    -> CL_TPDA_SCRIPT_DEBUGGER_CTRL=>DEBUG_CONTINUE
*************************************************
****************************************************************
*Interface (CLASS = CL_TPDA_SCRIPT_DEBUGGER_CTRL / METHOD = DEBUG_STEP )
*Importing
*        REFERENCE( P_COMMAND ) TYPE I
****************************************************************

    TRY.
        CALL METHOD debugger_controller->debug_step
          EXPORTING
            p_command = cl_tpda_script_debugger_ctrl=>debug_step_over.
      CATCH cx_tpda_scr_rtctrl_status .
      CATCH cx_tpda_scr_rtctrl .
    ENDTRY.

****************************************************************
*Interface (CLASS = CL_TPDA_SCRIPT_DATA_DESCR / METHOD = CHANGE_VALUE )
*Importing
*        REFERENCE( P_NEW_VALUE ) TYPE STRING
*        REFERENCE( P_OFFSET ) TYPE I
*        REFERENCE( P_LENGTH ) TYPE I
*        REFERENCE( P_VARNAME ) TYPE STRING
****************************************************************

    TRY.
        CALL METHOD cl_tpda_script_data_descr=>change_value
          EXPORTING
            p_new_value = '0'
*           p_offset    = -1
*           p_length    = -1
            p_varname   = 'sy-subrc'.
      CATCH cx_tpda_varname .
      CATCH cx_tpda_scr_auth .
    ENDTRY.

  ENDMETHOD.                    "script

[/abap]

Now, uncheck the "Debugger Single Step" checkbox in the trigger selections and check the checkbox for "Breakpoint Reached" and click the pencil edit button as pictured below. Click the button to create a new break point and add one for the ABAP command "AUTHORITY-CHECK" and click the green checkmark button. This will make it so our script only runs when the debugger reaches the AUTHORITY-CHECK command.

 

debugger-trigger.png

 

You can now save your script. To run it, type /h in the transaction bar before running the program. Then click on the script tab in the debugger, click load script and enter your script. Lastly, click the Start Script Button.png button and that's it no more authority issues! Another use of this script is to change the sy-subrc value to 4 to test your program to see what happens when you do not have the proper authorization.

 

Stoplight image byUwe Hermann @ flickr

 

Got any ideas of other things we could do with the debugger script? Leave them in the comments below!


Writing dynamic where clause in ABAP Select query

$
0
0

This article illustrates how to write a dynamic where clause in ABAP SELECT queries using the function module 'RH_DYNAMIC_WHERE_BUILD'.

 

Calling the function module RH_DYNAMIC_WHERE_BUILD

 

CALL FUNCTION 'RH_DYNAMIC_WHERE_BUILD'

  EXPORTING

    dbtable                = v_table_name

  tables

    condtab               = t_condtab

    where_clause      = t_where_clause

EXCEPTIONS

   EMPTY_CONDTAB         = 1

   NO_DB_FIELD           = 2

   UNKNOWN_DB            = 3

   WRONG_CONDITION       = 4

   OTHERS                = 5.

IF sy-subrc <> 0.

MESSAGE ID SY-MSGID TYPE SY-MSGTY NUMBER SY-MSGNO

         WITH SY-MSGV1 SY-MSGV2 SY-MSGV3 SY-MSGV4.

ENDIF.

 

 

Input parameters

 

v_table_name = Table name

 

t_condtab = Internal table contains the fieldnames and their
values used in where clause.

 

 

Output parameters

 

t_where_clause = Output string which will be used along
with the 'WHERE' clause in Select query. This internal table contains one field
with type character.

 

 

Populating t_condtab internal table

 

SFLIGHT table is used in this example to fetch data using dynamic Select query. CARRID, CONNID and FLDATE are the 3 key fields used for fetching data from SFLIGHT (MANDT field is not considered in this example).  The internal table t_condtab will be populated using these 3 fields and passed to the function module. Function module will return the internal table t_where_clause, which can be used along with the where clause in the Select query.

 

Sample code for fetching data from SFLIGHT using dynamic where clause

 

 

TYPES : BEGIN OF ty_where_clause,

          line TYPE char72,

        END OF ty_where_clause.


DATA : t_condtab TYPE STANDARD TABLE OF hrcond,

       t_sflight TYPE STANDARD TABLE OF sflight,

 

 

t_where_clause TYPE STANDARD TABLE OF ty_where_clause.


PERFORM fill_condtab USING 'CARRID' 'EQ' 'AA'.

PERFORM fill_condtab USING 'CONNID' 'EQ' '17'.

PERFORM fill_condtab USING 'FLDATE' 'EQ' '20130724'.


CALL FUNCTION 'RH_DYNAMIC_WHERE_BUILD'

  EXPORTING

    dbtable              = 'SFLIGHT'

  TABLES

    condtab             = t_condtab

    where_clause    = t_where_clause

  EXCEPTIONS

    empty_condtab   = 1

    no_db_field     = 2

    unknown_db      = 3

    wrong_condition = 4

    OTHERS          = 5.

IF sy-subrc <> 0.

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

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

ENDIF.


SELECT *

  FROM sflight

  INTO TABLE t_sflight

WHERE (v_where_clause).


PERFORM display_output.


*&---------------------------------------------------------------------*

*&      Form  FILL_CONDTAB

*&---------------------------------------------------------------------*


FORM fill_condtab  USING    value(pv_field) TYPE dbfeld

                            value(pv_operator) TYPE char2

                            value(pv_low)   TYPE hrvalue


  DATA : lx_condtab TYPE hrcond.

  lx_condtab-field = pv_field.

  lx_condtab-opera = pv_operator.

  lx_condtab-low   = pv_low.

  APPEND lx_condtab TO t_condtab.

ENDFORM.  

Part II : OO ABAP Implementation [ Method Parameters ]

$
0
0

This in continuation to my first blog on SCN

Part I : Basics of OO ABAP Implementation

In this section we see the possibility of

 

  • Passing parameter to our method.
  • Various ways to receive values from methods

 

Well as we have already seen how to create a class and how to call methods I will directly jump in.

 

To pass value to a method probably there are 2 ways.

 

  1. Importing parameter of the class [Value is read-only cannot be changed in class].
  2. Changing parameter of the class [Value can be changed in class can be used as read write variable].

 

To receive value from a method probably there are 3 ways.

 

  1. Exporting parameter of the class
  2. Changing parameter of the class
  3. Returning parameter of the class [ This can be only one and the class cannot have Exporting / Changing parameter in this case].

 

You can explore the Exporting and Changing, the importing and returning are demonstrated below:

 

CLASS DEFINITION:

 

  CLASS lcl_material_list DEFINITION FINAL.

 

     PUBLIC SECTION.

 

           METHODS: display_material

                    IMPORTING i_matnr TYPE matnr.

 

     PRIVATE SECTION.


           DATA : BEGIN OF ls_material,

                   werks   TYPE werks_d,

                   lgort   TYPE lgort_d,

                   labst   TYPE labst,

                  END OF ls_material,

 

                lt_material LIKE TABLE OF ls_material.

 

            METHODS: get_description

                    IMPORTING i_matnr TYPE matnr

                    RETURNING VALUE(r_maktx) TYPE maktx.

 

  ENDCLASS.

 

Now our class declaration is complete.

 

As you have noted we have stepped one step up, the method "get_description" is a private method and cannot be called from our program.

 

But it can be called within the class for internal usage only.

 

THE PROGRAM :

 

PARAMETERS : p_matnr TYPE matnr OBLIGATORY.

 

DATA : go_material TYPE REF TO lcl_material_list.

 

AT SELECTION-SCREEN.

 

     SELECT SINGLE matnr FROM mara INTO p_matnr

                        WHERE matnr = p_matnr.

    

     IF sy-subrc IS NOT INITIAL.

          MESSAGE 'Invalid Material' TYPE 'I'.

     ENDIF.


START-OF-SELECTION.

 

     CREATE OBJECT go_material.

 

END-OF-SELECTION.

 

     go_material->display_material( p_matnr ).


 

CLASS IMPLEMENTATION:

 

 

CLASS lcl_material_list IMPLEMENTATION.

 

     METHOD get_description.

 

           SELECT SINGLE maktx FROM makt

                               INTO r_maktx

                              WHERE matnr = i_matnr

                                 AND spras = sy-langu.

 

     ENDMETHOD.

 

     METHOD display_material.

 

          DATA : lv_maktx TYPE maktx.

 

          lv_maktx = get_description( i_matnr ).

 

          SELECT werks lgort labst FROM mard

                                   INTO TABLE lt_material

                                  WHERE matnr = i_matnr.

 

          WRITE:/05 i_matnr,

                 25 lv_maktx.

 

        ULINE.

 

        WRITE:/05 'Plant' , 15 'SLoc' , 25 'Un-restricted Stock'.

       

        ULINE.

 

           LOOP AT lt_material INTO ls_material.

 

                WRITE:/05 ls_material-werks,

                       15 ls_material-lgort,

                       25 ls_material-labst.

 

           ENDLOOP.

 

     ENDMETHOD.

 

 

ENDCLASS.

Free Enhancement to ABAP Editor

$
0
0

Every day I get a "Google Alert" of all the news stories about SAP.

 

Yesterday one came up saying a company in Hungary were giving away a free add-on to the ABAP editor which improved the "pretty printer" function.

 

Without further ado, here is the link:-

 

http://www.hovitaga.com/buy-product?product=ADVPP

 

You notice it says "buy" - this is actually free, but the "price" you have to pay is to share the fact you have downloaded this on a social media site such as Facebook or Linked In. I chose Linked In because this is a work related thing.

 

Anyway, this does not do that much, but it does automate a few things I spend a lot of time doing manually, e.g.aligning DATA statements such as when I am declaring structures and what have you.

 

Anyway I downloaded it, it does what it says on the tin, and it has not blown up my sandbox system.

 

More interesting to me is seeing HOW it works, which is an implicit enhancement to function module PRETTY_PRINTER. I had to go into the enhancement and add a comment, and then activate the ehancement again before it worked, it might work for you straight off, if not try that method.

 

I won't be able to stop myself enhancing this further, the best example I can think of is the automatic commenting of local classes and methods, to match what standard SAP does with PERFORM routines.

 

Rant Time! If SAP was REALLY serious about making people use object orientated programming, they would make it as easy as procedural programming e.g. it would be good if when I double clicked on a method defintion then a skeleton implemenatton would be generated with comments saying what the signature was - basically I want the exact same thing that happens when you double click on a call to a PERFORM that does not yet exist.

 

ABAP OO came out in the year 2000, so SAP has had 13 years - THIRTEEN YEARS - to unify this behaviour i.e. improve the creation of local methods so they are as good as local PERFORM routines, and it has not bothered. What does that tell you about the level of commitment?

 

Someone is going to say "oh, you can do that in ABAP in Eclipse" well hooray, in five years time when we upgrade again I'll be able to use that. In the meantime I think playing with this new toy may well solve the problem.

 

Anyway, ranting aside, the link above takes you to a page where there is some documentation about what this free tool does, and videos and what have you.

 

This business model is getting more and more common - companies giving away free things to try and get you hooked, a bit like drug dealers do, for example SALT which is quite a reasonable product given it is free, and I wholeheartedly approve!

 

Cheersy Cheers

 

Paul

Are you writing "evil" ABAP code?

$
0
0

Working with Legacy Code – ABAP Style

 

Now is the table of our contents, made glorious summer by this Son of York.

 

Prologue

What is Legacy Code?

Why is Legacy Code “bad”?

If we do think it’s bad, what can we do about this?

What can you do about lack of clarity??

What can you do about all the dependencies??

Epilogue – Procedural Tests

Epilogue – OO Tests

 

Prologue

 

What I have been doing since I got back from Germany to Australia is reading famous academic articles which all “proper” people who studied computer science probably read 20 years ago, and see how I can take the concepts and apply them to the world of ABAP. I also try to take a different approach to the other blogs I see on SDN, so as not to repeat what other people say. Some people get puzzled as to why my blogs are also ten times as long as anyone else’s, but that’s just me. I know people nowadays are only supposed to have a ten second attention span, but I suspect a lot of people are capable of much more.

 

So let’s get going. “Working with Legacy Code” is all about taking an existing program which is “bad” and making it “better”. So, why is it bad in the first place, and how does making it better help us?

 

The seminal work on this subject was written by Michael Feathers back in 2004. You would think that nine years later the world would have moved on but I am willing to bet there are hundreds of SAP programmers out there who this very day during their work hours wrote heaps of “legacy code” according to his definition.

 

What is Legacy Code?

 

What then, is legacy code? Probably not what you would think at first glance. Let us look at some possible definitions

 

My Definition My Definition Is This.

 

-          Poltergeist

 

According to Wikipedia “Founded in England (where the Ruling House resides in London) in the 6th century, the Legacy was established to collect dangerous and ancient knowledge and artefact’s, solve paranormal problems, and protect humanity from supernatural evils.”.

That doesn’t sound right. Most likely very little of the SAP code written this day could be deemed as supernaturally evil, at least I would hope so, though I will give you an example in a minute.

 

-          Non-SAP Code

 

Before reading the article that was what I would have guessed as the answer. Right from the word go when I started working on SAP implementations in 1997, the term “legacy” meant the system that SAP was replacing, and thus by definition a Bad Thing, no matter how much the current user base liked it. Once again everything written in the old system must be supernaturally evil, so is this what we mean by legacy code?

If you think about it that can’t be right - we can’t be “working with legacy code” if the system with the code in it no longer exists due to being replaced by SAP.

 

-          Procedural Code

 

There are articles without number on the SDN and in magazines like SAP Insider which state in no uncertain terms that the code that is in fact supernaturally evil is procedural code, and that is given as the prime reason for swapping over to object orientated code, as opposed to logical reasons like “it helps you because…”. This is why I have been writing all these blogs about switching over to OO programming, to give myself some concrete reasons why it is better.

 

In fact no, not even procedural code is Legacy Code.  Even the OO evangelists admit that OO programs designed badly can be worse than procedural programs. There is so much more scope to do things badly in the OO environment and if you give people enough OO programming rope most of them seem to hang themselves. My first few attempts at OO programs certainly self destructed.

 

-          Non-Testable Code

 

That is the definition. Legacy Code is code that cannot be tested.

 

Why is Legacy Code “bad”?

 

“Code without tests is bad code. It doesn't matter how well written it is; it doesn't matter how pretty or object-oriented or well-encapsulated it is. With tests, we can change the behaviour of our code quickly and verifiably. Without them, we really don't know if our code is getting better or worse.”

― Michael Feathers, Working Effectively with Legacy Code

 

As you can see by that definition if a procedural program has unit tests and an OO one does not, then it is the OO program that is legacy code. That is why SAP did not restrict the ABAP Unit framework to just classes and methods. My very first use of ABAP Unit was to add tests to an existing procedural program, and the benefit was enormous. I come back to this at the end of the blog.

 

Another similar quote from Michael Feathers about tests, just to ram home the point:-

 

“Most of the fear involved in making changes to large code bases is fear of introducing subtle bugs; fear of changing things inadvertently. With tests, you can make things better with impunity. To me, the difference is so critical, it overwhelms any other distinction.”

So, since 99% of development work is changing existing programs, and the biggest problem with changing existing programs is breaking something unrelated to the change.

If we do think it’s bad, what can we do about this?

 

Answer – “Add Tests”.

OK, we want to add loads of tests to our programs to “cure” them. Let us see:-

·         Why we cannot add tests

·         Why we would like to add tests

·         What to do to change things so we CAN add tests

 

OO everywhere man

 

On the Internet a lot of wishful thinkers say that nobody in the SAP space does procedural programming any more and has not since the advent of version 4.6. My gut feeling, and observation at assorted companies, tells me however that there is not as much custom OO code about as you might think.

 

Even SAP, at first seemed to think that if you wrapped a class around a function module then suddenly everything was magically object orientated. I have seen a lot of standard classes like that in the system.

 

Monkey see, monkey do, so a lot of SAP customers did the exact same thing.I have seen lots of Z classes with all static methods, everything public, including the data. That is sort of missing the point.

 

Unbelievably some people have not even yet made the jump to procedural programming. I saw one program – written in 2010 – a report with lots of lines of code, using WRITE statements, with no subroutines at all, just the good old SELECTION-SCREEN followed by everything in one huge mass after START-OF-SELECTION.

 

Get to the point

 

All right, the point is, that IT IS generally easier to add tests to OO programs than procedural ones, and all the documentation and helpful articles you find on the internet – even going back ten years or more – presume you are starting with an OO program in the first place that you want to refactor. That is because they were all written with Java or C++ or whatever in mind, but now I see in the SAP space similar articles all once again starting with the premise that the starting point is an OO program with a bad design and no tests.

 

I put it to you, learned counsel for the defence, thatmany people are in a boat like me, where you constantly have to add improvements and fixes to monolithic procedural programs over ten years old, which are vital to the day to day running of the business. I imagine SAP employees feel the same way about SAPMV45A.

 

Big Bang Theory

 

If you attempt to rewrite the whole thing in one Big Bang, you will win no popularity contests, take forever, break the thing thoroughly and bring down the company, and when you are standing in the dole queue it is cold comfort that you were doing the “right” thing.

 

So, it’s impossible? The nature of such monolithic programs means you just cannot add unit tests as the various aspects of the program – user interface, business logic, database access, external interfaces etc. are all so hopelessly welded together.

 

This is somewhat reminiscent of the urban legend that says when you ask someone in Ireland for directions they say “you can’t get there from here”.

 

To counteract that, let’s have a Star Trek quote why don’t we. I love them - they re-enforce the nerdy image of us programmers.

 

“’You can’t get past light speed without getting to light speed first … can’t be reached. Can’t.Be. Done.’

 

‘Yet you did it Mr.Cochrane

 

That was from “Federation” by Judith & Garfield Reeves-Stevens, about solving another impossible problem.

 

Is this real, or just a dream – there’s nothing that is in between

 

As I mentioned this is not an academic exercise for me, this is something I have to deal with every working day, and often some of the things I have to do to make the required change make me want to cry, they work, and take a very small amount of time, but are so fundamentally wrong it beggars belief.

 

Today for example, after a change – that worked perfectly by the way and took no time at all – I commented my change thus:-

 

“I have just changed the above global variable in this subroutine, off it goes into the wild blue yonder, in many subroutines time it will get exported into a custom Z table, a table which exists for the sole purpose of storing data until this program SUBMITS another program to read back that same data from the Z table one second after it has been written”

 

This is all wrong on so many levels. It works NOW but what if one day someone makes a change to one of the many subroutines between me writing the value and it getting exported to the table? Or someone changes the program that gets submitted? There is so much scope for disaster in the future.

 

As mentioned earlier, the assumption is you can’t instantly rewrite the program to make it a bit more sensible, the real problem of course, the whole focus of this blog, is if someone does make such a change they will have no way at all of knowing they had stuffed up my fix. How could they? Their new change works, so they are happy bunny.

 

The only way they could know they had an adverse effect is if there were unit tests they could run to see if any existing functionality was compromised.

 

Uphill Battle

 

So, I can make a really fast change that works, and leave the program in a potentially unstable state, or I code take some more time and add some tests (if at all possible).

 

I was reading a blog where someone was talking about just this subject and advocating refactoring code and adding tests and got this response:-

 

Anonymous said...

one of the sure signs of a programmer...not a mention of costs

and time to completion

when you hire a painter to redo the kids bedrooms, you don't

expect them to "refactor" a hole in the back wall "to make it

easier to repaint the rooms when they are teenagers"

you're familiar with "don't reinvent the wheel" but how about

"don't redo tests"???

 

This is what we are up against – doing something properly does take time and money and in a world where most companies are only allowed to look three months ahead and, say, sack their USA head of sales of not meeting quarterly targets, the long term view does not get a look in, despite the fact that, in the example above, repairing the hole in the wall now might stop the house falling down in a few years and that cost saving will offset the current cost of hole repairing by a factor of a thousand.

 

Walk Don’t Run

 

So, I am going to assume a situation where you have such a monolithic application where you have to change it all the time, and you can’t do a wholesale rewrite because it is just too difficult and risky. However one tiny bit at a time, you can start to change things so that when you do try and re-organise things the change is LESS difficult and risky.

 

Staying with the definition the reason this program is legacy code is that there are no tests and we can’t add tests. So, if I am adding a feature or fixing a bug why can’t I just add a test there and then and eventually there will be tests for everything? It’s not quite as easy as that. I will go over two main categories of reasons why you can’t just pop in a test.

 

1.     You have no idea how in the world the code works. It just does like black magic. It is difficult to pin down what parts of the program to test. I call this category CLARITY. This is often 75% of the battle, and is going to be 90% of this blog.

2.     It is technically impossible to do a unit test on the function being added or fixed because there are DEPENDENCIES.

 

What can you do about lack of clarity??

 

Clarity Begins at Home

 

Sometimes you have to debug for ages through a complicated program to try and work out just where in the world an error is coming from. Eventually you fathom what is going on, fix the problem and all is well. Then six months later you have to come back and fix another problem and you can’t remember how this monster works as it is so illogical.

 

As a favour to yourself, and to your colleagues current and future, you could do what Robert C..Martin calls “The Boy Scout Rule” on the area of code you have just fixed or enhanced.

 

He says:-

It's not enough to write the code well. The code has to be kept clean over time. We've all seen code rot and degrade as time passes. So we must take an active role in preventing this degradation.

The Boy Scouts of America have a simple rule that we can apply to our profession.

·         Leave the campground cleaner than you found it.

If we all checked-in our code a little cleaner than when we checked it out, the code simply could not rot. The cleanupdoesn't have to be something big. Change one variable name for the better, break up one function that's a little too large, eliminate one small bit of duplication, clean up one composite if statement.

Can you imagine working on a project where the code simply got better as time passed? Do you believe that any other option is professional? Indeed, isn't continuous improvement an intrinsic part of professionalism?”

Here is a link to the advert for his book on the subject, with some other good quotes, just keep going back and forwards.

http://www.informit.com/articles/article.aspx?p=1235624&seqNum=5

When I showed this to one of my colleagues he responded thus:-

The boy scouts also have a saying “Be prepared”.

 

In the case of (other external contractors) and myself, that might mean Be Prepared for (the CIO) to march us out the door if we introduce a bug in an area that is not in scope that we are meant to be working on.

I see the argument – any change you make no matter how trivial has a chance of causing a bug, and when the finger pointing starts you get asked “why EXACTLY did you change that? Was that change in the specification?”

As an example one of my colleagues decided to change a commented out section of open SQL commands (i.e. where you access the database directly instead of using the normal SELECT) from having an asterisk at the start to having the quotation mark at the start. Little did we realise that when you “comment out” such open SQL commands with a “” you are not commenting it out at all, it tries to execute. Who would have thought? He was trying to make the code clearer and – oh dear!

However as might be imagined, I am on the side of the cleaner uppers, so let us look at some areas where we can improve the clarity of an existing program one tiny bit at a time.

DRY

 

That stands for “don’t repeat yourself”. This very day one of my colleagues was tasked with doing a final purge of all custom code that tried to call transaction ME23 as opposed to ME23N.  He noticed I had the monolithic monster of which I speak locked, as I usually do, and he said while I was there can a change the CALL TRANSACTION in a certain subroutine from ME23 to ME23N. The work of seconds.

 

After I told him I was done he said “oh I forgot, I also found it in the same program in subroutine XYZ as well”. Off I go to that subroutine, and to my puzzlement I find it exactly the same as the first subroutine I changed, clearly all of the code, dozens of lines, had been cut and pasted from one place to another, in the same program, resulting in two identical (large) subroutines concerned with drilling into assorted transactions. Presumably each one gets called half of the time by different areas of the program.

 

The obvious consequence is that they will diverge over time. – in this case if my colleague had not told me about the second subroutine then we would have ended up with a program that drilled into ME23N and sometimes ME23, seemingly at random from the users point of view.

 

The answer is this example is obvious – the subroutines were 100% identical, so delete one, then run the syntax check and I see where it gets called and replace the call with one to the remaining subroutine.

 

Far more common are blocks of code in the middle of a subroutine that have been cut and pasted, sometimes the block is identical, sometimes one value in one line out of thirty has been changed. Both cases are crying out to be encapsulated, the latter with a parameter for the varying bit.

 

As can be imagined this applies even more to cutting and pasting large chunks of code between different programs. It is so easy, and so fast, and the boss wants this new thing working TOMORROW so the temptation is overwhelming.

 

The mathematical equation here is does the extra time it takes you now to encapsulate the block of code outweigh the fact that next time you need this in another program ( and if you need it again once, then you’ll need it again sometime down the track) you just call the Z function ( or hopefully class) and you know it works as it works somewhere else, and more importantly any negative consequences if the multiple versions of the same code in your system fall out of synch.

 

As another real example, I got an email from a colleague once saying “you recall when we improved the XYZ logic? It turns out we changed four programs where the logic was, but missed program ABC and the salesmen are up in arms as it is price rise time next week”. If the code had been in just one place that literally could not have happened. Needless to say, it is in one place now.

 

When I find myself solving the same problem twice with code, I now find it physically impossible to just cut and paste the old code in. Maybe I am lucky in having an understanding boss.

 

The ultimate example is when you find yourself solving the same problem not just two or three times, but hundreds of times, in almost every program you encounter. The example I would like to share is that I find myself spending an inordinate amount of time getting the names of things like sales organisations, divisions, vendors, materials, equipment types etc. To make life fun for you SAP has no consistency at all, some names are in text tables, some are in a fixed value list in a domain, some like vendors are in the main table, as many different ways to store the name as there are stars in the sky.

 

As the years go by I find I memorise which tables to read for various data element key values, but I sometimes forget and have to go looking again, and it’s not always obvious. In the same spirit of doing a favour to myself I thought I’ll fix this once and for all.

 

001 Legacy.png                       

Legacy Figure 01

 

I did a once off exercise of building in a complex algorithm which did a runtime identification of the data element passed in and then searched for text tables, and then a domain value list to get the text name. If that did not work I subclassed the complicated ones like vendors and materials, not that the calling program knows this. I won’t go into detail on this now, but it is the principle of the thing.

 

I never have to worry about going hunting for the correct text table again. That is my ultimate example of a bit of work up front for a large saving later.

 

Life in Six Words

 

It was said that Shakespeare summed up human life in six words – “Bloom, Bloom, Bloom, Rot, Rot, Rot”.

 

Are our programs like that? We create things of beauty and then they gradually rot away over time as different people graft extra things onto them and fill them with workarounds and hacks until they are incomprehensible and unchangeable? Unchangeable in this context means one change breaks six existing things, and fixing those six break thirty six other things, which has been compared to the Hydra where you cut one head off and six more grow back.

 

One of my favourite examples of rotting code is the ever popular commenting out of the code you have just replaced. The first time this makes perfect sense, you think “when someone sees this, they get a clear picture of the before and after” usually without any explanation of WHY the code was replaced.

 

In no time at all, you get a huge sea of blocks of commented out code with little islands of live code in the middle feeling really lonely looking round wondering where all their friends are. I have seen this again and again. There is no way that makes things easier to understand, you have to page up and down like a lunatic to try and get a grip on what is going on.

 

When I first saw this I presumed the programmers had no idea there was a version management system for ABAP programs which gave you a complete history of changes. As time goes on I am forced to conclude people do this because “we’ve always done it this way”.

 

In the same way, people look at what OSS note code changes look like and do the same themselves:-

 

* IF SOMETHING THEN DO_SOMETHING                                     “Remove - Ticket 765438765

IF SOMETHING OR SOMETHING ELSE THEN DO SOMETHING “Replace - Ticket 765438765

 

Leaving aside the line of dead code that has arrived to turn into a bloated corpse and spread bubonic plague around the program, this putting ten digit numbers as comments trick happens often and people are pleased as punch and say “look I have given a reference number so if someone really wants to know why that change was made then they can go off and look it up”. True enough, unless of course the comments were written by your consultants on the implementation project referring to THEIR helpdesk system, and they have been gone ten years, but leaving aside how easy it is to wade through the change request system, I can think of two better ways to achieve the same thing straight off.

 

1.     If it is that important that someone looking at the code should know why it changed then, in my lunatic world at least, maybe you could TELL THEM. Right there and then. Maybe, and again I could be the fool here, you should not drop obscure hints in a secret code to make them think they are in a Dan Brown novel as opposed to writing a comment saying why you changed it

2.     Even better, if the code itself has variables with meaningful names and what have you, then using the version comparison then, hopefully, it is obvious at first glance why it changed. That only works if the code you removed had meaningful names as well, otherwise you do need a comment.

 

It is important to say why you changed something, especially if you are doing something odd to avoid a specific problem, otherwise some well-meaning person in the future will say “that’s odd” and change it right back again.

 

This very day I changed a line of code which said:-

 

M_FLAG = ‘X”. “Ticket 530333

 

And I changed it to…

 

M_FLAG = ‘X’. “M_FLAG means such and such, as we set it here because of such and such etc…

 

I once did some counter-intuitive logic I a complex database SELECT to avoid the wrong index being chosen, and years later, from the safety of another country, when I looked at my code in the system I used to work in I found a consultant had changed things back and written:-

 

* Above Code is very strange - cannot be understood without any background

* andfunctional specification. Therefore try end error to solve customer

* problem(cannot exclude any side effects):

 

He was right about the side effects. Since he was an advocate of leaving dead code in the program I can clearly see the problem I tried to avoid in the first place coming back and then about fifteen different ( to be precise - seventeen ) attempts to solve it before deciding it was impossible.

 

Ian Dury and the Code Block Heads

 

In the book by “Uncle Bob” I was talking about earlier he makes the observation that programmers are authors, insomuch as after you write something at some stage in the future someone else (maybe even yourself) will come back and look at it, to make a change or just to understand what on Earth is going on.

 

He did some sort of screen capture of a programmer at work, and found that 90% of the time was spent paging up or down in the same code block trying to get an overview of what was happening. The logical conclusion was to have each routine no bigger than one page and – hey presto – you can program ten time faster! Hooray! Hang out the flags!

 

Do you know, amazing as it may seem, there are some people who when confronted with that argument express doubts. Who would have thought?

 

Anyway, there is no smoke without fire, and in fact one the “ABAP programming guidelines” in the SAP Press book of the same name does in fact advocate having every subroutine (‘method see section such and such’ as the book says every two lines throughout its entire length) no bigger than one page.  Like all guidelines, that is most likely something to be aimed for rather than something that is actually possible as a matter of course.

 

Going back to 1981 when I first started programming you had to make the program as terse as you possibly could, as I only had 1K to work with. Clearly your first experience with something sticks with you in some sense for the rest of your life, as thirty years on I still cannot stand to have extra lines when they can be avoided.

 

For example I cannot stop myself changing:-

 

LOOP AT INTERNAL_TABLE .

  IF SOMETHING.

    GET_SOME_DATA USING INTERNAL_TABLE.

    IF SOMETHING_ELSE.

      GET_SOME_MORE_DATA USING INTERNAL_TABLE.

      IF SOMETHING_ELSE_YET_AGAIN.

        DO_SOMETHING.

      ENDIF.

    ENDIF.

  ENDIF.

ENDLOOP.

 

….into…

 

LOOP AT INTERNAL_TABLE WHERE SOMETHING = TRUE.

   GET_SOME_DATA USING INTERNAL_TABLE.

   CHECK SOMETHING_ELSE = TRUE.

   GET_SOME_MORE_DATA USING INTERNAL_TABLE.

   CHECK SOMETHING_ELSE_YET_AGAIN = TRUE.

   DO_SOMETHING.

ENDLOOP.

 

The two routines are functionally identical, it is just that the second one has fewer lines, so if having to page up and down less is important, and I actually tend to think it is, then you have enabled to casual viewer to see more of the routine (method, see section such and such, can you imagine how annoying that gets after the third time, let alone the hundredth?) on one screen.

 

In the programming guidelines book of which I speak one of the rules is to restrict control blocks like IF statements to a nesting level of five. Very sensible advice, I have seen, time and again, enormous IF statement blocks, deeply nested so much that when you do a “pretty print” the part in the middle gets indented right to the far right of the screen.

 

In such situations I can’t stop myself encapsulating the sections between IF and ENDIF into their own routine. This can change things that are literally impossible to understand into the blindingly obvious.

 

Here is an example that makes me want to cry, before I got to it there were not even any comments after each ENDIF:-

 

002 Legacy.png

Legacy Figure 02

 

Here is another example where I have tinkered around a bit more:-

 

003 Legacy.png

Legacy Figure 03

 

The above used to be one thousand lines of code. I got heartily sick of pressing page down loads of time to try and find the part that dealt with deletion.

 

That’s not my name, that’s not my name, that’s not my name…

 

One thing I have repeated in various blogs, over and again, is about having variable and routine names that don’t mean anything, often in the form of numbers e.g. routine GX09876. You may think no-one would call a routine that, so I’ll leave it to you – did I make that example up or is it a real one I actually saw?

 

Rather than repeat myself I’ll direct the interested to point 10 of my previous blog:-

 

http://scn.sap.com/community/abap/blog/2012/10/27/crossing-the-great-divide--procedural-vs-oo-abap-programming

 

This is one area where I have changed my behaviour. In the days of the ZX81 variables were called “X”, and in fact in a lot of the Java examples on OO programming many of the variables names there seem to follow the same convention.

 

I will just give two examples, one from the week just gone which was driving me up the wall.

 

In my monolithic program I am always changing (slowly)itis stuffed with global variables, and  I wanted to find where some global data got set. It took me ages because there are about a hundred routines where data of different sorts gets set and they are evenly split between routines with the following prefixes:- GET_, SET_, INIT_, POPULATE_, DETERMINE_, so I have to search through five different areas of the subroutine list to try and find what I want.

 

I imagine some people would be horrified by using GET_ to actually set the value of something!

 

I also had to struggle for days working out how something worked, I got it down to two routines which were called:-

 

GET_DELETE_USER_SELECTION

CHECK_DELETE_USER_SELECTION

 

This was all about deleting various things which the user had chosen, so far so good. However the routine starting with “GET_” performs assorted checks on what the user has selected, and the routine starting with “CHECK_” actually does the deletions. Eventually I found another routine starting with “DETERMINE_” which actually asks the user what they want to delete.

 

I would love to hear from the ABAP community with examples of the worst named routines and variables they have encountered…..

 

Signature Tune

004 Legacy.jpg

Legacy Figure 04

 

All OO people will tell you that global variables are the work of the devil and you should get rid of them at all costs.  One of my colleagues tells me he had a job interview with two interviewers and they told him one of them was strongly for global variables, one was strongly against. How to handle that one?

 

Let’s try to be objective for a second, and have a look at the pros and cons of global variables.

 

Pros - at least what people would SAY are the advantages.

 

·         It’s easy, you don’t have to keep passing values back and forth via routine parameters, you write your program a lot faster

·         Selection-Screens and DYNPROS are designed to work with global variables

·         You don’t end up with routines with gigantic signatures which could be deemed as confusing

·         We’ve always done it this way (a very common argument)

 

Cons

·         It makes unit testing a lot more difficult, you cannot guarantee the order unit tests are run in and so you have to make sure all global variables are set to the “right” values at the start of each test method

·         Without the variables being changed actually being in the signature you can end up with badly named methods which change SOMETHING but you don’t know what. I’ll come to this in a minute.

 

I tend to agree with minimising global variables – the premise at the very start of this blog that having unit tests is the most important thing in the universe would point you towards avoiding things that make such tests impossible, but you can take things to extremes – the example I would give is SELECTION-SCREENS – do you really have to go to the trouble of moving all the global variables into data inside local classes and then pretend the global variables (for such are selection-options ) do not exist. Maybe you do. Who am I to say. I can see the merit of moving them into re-usable Z classes.

 

Anyway, in my monolithic example there are so many global variables, trying to get rid of them wholesale is not only difficult but incredibly risky for a program vital to the day to day running of the business, so I have to live with them. The biggest risk you have is getting rid of a global variable only to find it’s used on a screen, and the syntax check doesn’t always warn you about that.

 

So, I find myself looking at a bunch of routines, all of which change the values of some global variables. Changing the names of the routines so they describe what they do goes some way to defeating the enemy (incomprehension) but only so far. I thought the next step might be to change the signatures of the routines so when you look at them you see what is getting changed.

 

Before:-

005 Legacy.png

Legacy Figure 05

 

In the above the name of the routine with IDOC in made sense when we used to send an IDOC, but we moved from sending an IDOC to using PI years ago, so the routine name makes no sense at all. This is an example of comments and routine names “rotting” when all someone does is change the code to fix the current problem at hand and does not look at the surrounding code / comments.

 

After:-

006 Legacy.png

Legacy Figure 06

 

Now we have a better idea of what the subroutine is expecting as input, and what data it returns. This also makes the individual subroutines more capable of being re-used, and paves the way for the eventual retirement of the global variables in question.

 

On the earlier matter of routines with huge signatures, an OO person would say if your method does have an enormous signature it is most likely doing too much.

 

Jumping round the program like a lunatic

 

Recently I have been translating some “pseudo-code” written by an older gentleman who most have been familiar with BASIC as I see GOTO statements here and there. I used to write in that myself and I can see the argument that GOTO 210 is not a very meaningful statement.

 

If we are trying to be writing a program which is easy to understand, then it could be said that understanding the flow is important. Maybe here is where your bog standard procedural program without screens shines – it is usually crystal clear in what order each procedure is called. A programmer could go bananas if they wanted and jump around illogically within procedures but generally the ones I have seen are usually fairly easy to follow.

 

Once you bring DYNPRO screens into the equation all bets are off. On a screen, after user input, the system process the AT EXIT-COMMAND, ON-USER_COMMAND, and to make things fun, the EVENT AT-USER_COMMAND is also triggered, so in some programs I have seen half of the user command processing was handled by a module in the PAI section, and half in the EVENT block responding to AT-USER_COMMAND.

 

In my example program there are 32 different screens, all of which are pop up boxes firing from user commands from the main ALV screen. A lot of older programs like this use the SUPPRESS DIALOG method for dialog boxes and then WRITE out a list of some sort on the screen, thus rendering the PAI modules useless, as they get triggered at once. The more modern way of doing things is to have an ALV grid on the screen instead, and then of course things become even more obscure – or do they?

 

Many years ago when I went on an ABAP OO course here in Australia the instructor said “the problem, of course, with OO code, is that you can’t tell what it does from reading it”. This was an official SAP training course I would add.

 

I hope that’s not true because what I have just been talking about is that if 99% of development effort is maintenance then it is VITAL that you can tell what a program does by looking at the code.

 

Well, ladies and gentlemen of the jury – is this true? Is the screen flow of a DYNPRO screen easier to follow than firing off loosely coupled events that could be handled anywhere? I don’t know the answer but I do know that debugging what happens after pressing a command in VA01 is easier to follow than the equivalent in ME21N. Or is that just because I am an OO novice? One moan I get repeated to me all the time by die hard procedural types is that when looking at OO code the forward navigation you get in ABAP when clicking on a procedure call is just not there with the largely dynamic calls you find in OO world.

 

Istanbul not Constant

 

One thing I mention again and again in my blogs are the so called “magic numbers” that pop up in programs. If we are talking about how to make things less confusing for the reader and at the same time easier to change as business requirements change then I am going to have do the broken record thing and bring this up again (I wonder if anyone below a certain age has the slightest idea what the term “broken record” originally referred to?).

 

As an example the other day in a specification I was given which had TONS of business logic within it, at one point I was supposed to multiply the result by one point six four five. Easy enough to do, but I thought to myself what is so wonderful about that number and more to the point, can it change?

 

A quick hunt all around the internet came back with the answer that one point six four five was the “famous” such and such’s constant, which he had come up with via an exhaustive serious of scientific experiments proving that the result is wrong unless you multiply it by this number. All well and good, so if it is a scientific constant like PI it can’t change can it? A-ha! Every country has a different interpretation of this result so this “constant” has a different value in calculations done in India than in Australia for example.

 

If it had been a universal constant then I would have declared it as a constant with the correct scientific name, and probably given a little explanation of what this was all about as a comment. In this case it varies from country to country so the value needs to be read out of some sort of customising table, but it still needs a meaningful name for the variable.

 

Immutables in Mega City One

 

A top tip that Horst Keller from SAP gives in his book is that when you do have a proper immutable constant, don’t declare it as a local constant but as a global one. That way if you have fifty four instances of the program running in different user sessions, the ABAP runtime system uses black magic to only have the value of the constant existing in one place in shared memory and all the user sessions read the value from there. The amount of memory this saves is miniscule, but as I mentioned much earlier I love making microscopic savings like this.

 

You could even have a global class just full of (related) constants – I see a lot of standard SAP classes like that. This is for when such a constant needs to be read by more than one program.

 

Constantly Amazed

 

I could not leave any discussion of constants without relating the habit some consultants used to have of if they were going to add ten to a value (say to determine the next line item of a sales order) they would declare a global constant called C_010. What value does that add?

Then of course, five years down the track the value we want to add changes to five and then they think “good job this is a constant, I only need to make the change in one place” and thus you end up with a constant called C_010 which has a value of five. I don’t think I need to expand.

 

The other thing which made me bang my head on the desk was that when confronted with an obscure code that SAP uses to denote a piece of equipment flagged for deletion – something like ISRU – then instead of declaring a constant called C_FLAGGED_FOR_DELETION they decided to call the constant C_ISRU. Well, thanks for that, everything is much clearer now. Of course, one day SAP might decide to change the code to JKML, and then you have one constant with a meaningless name pointing to a different meaningless value.

 

Enigma Code

 

So, by doing assorted cosmetic changes and minor restricting over a protracted period of time, hopefully you end up with something that at least you can understand how it works, even if everything is still 100% procedural.

 

Aiming lower, maybe now you have PART of a big program where it is clear how it works. If so, we are now half way to where all the other articles on this subject start.

 

What can you do about all the dependencies??

 

This state of DEPENDENCY will be

 

This next part has been written about at great length, including by myself, so I am just going to cover the basic idea, and give links to some proper articles about it.

 

In essence you want to do a test for your productive code but you can’t because the productive code reads from the database or writes to the database or accesses some external system or sends an email or needs some input from a user or one of a million other things you cannot or do not want to in the development environment.

 

Traditionally in an ABAP program if you want some information from the database you do a SELECT statement right there and then at the point you need it. Likewise updating a custom table, or maybe calling a PI proxy. Why in the world not? It’s so easy!

 

Well the reason why not is that then you can’t test the routine in question which does any of those sorts of things.

 

I am going to make up an example, based on real life, but changed a touch.

 

In this example my monolithic SAP program schedules feeding time at the zoo and makes sure all the animals get the right amount and type of food. Everything works fine. The keepers have some sort of mobile device which they can query to see what animals need feeding next and there is some sort of feedback they have to give after feeding the animals, and we want to keep track of the food inventory and the list goes on, none of the details are important.

 

All is well until one day we get two Pandas on loan from China, and the programmer has to make some changes to accommodate their specific Panda type needs (e.g. Bamboo Shoots) without stuffing up all the other animals. The programmer can do a unit test on the Panda specific changes they have made, but how can they be sure that an unforeseen side effect will not starve the Lions, causing them to break out and eat all the birds, all the monkeys and then break into the insect house and crush and eat the beehive?

 

Normally you can’t. There are just too many dependencies, the program needs to read the system time, the configuration details on what gets fed when, needs to read and write inventory levels, sends messages to the zookeepers, receives and processes input from them, interfaces with external systems etc.

 

However we really don’t want to risk the existing system breaking and the Lions having to dine on Finch, Chimps and Mushy Bees, so how can we enable tests?

 

The answer is to use the “separation of concerns” approach, where we have one class for database access, one for the user interface layer, one for talking to an external system etc. which initially I thought was good practice anyway as it enabled you to change the implementation of, say, your user interface layer without affecting anything else, but this also turns out to be vital for unit tests.

 

Also I have seen a whacking great example where someone split out all the database access into its own class, presumably following the separation of concerns model, but he made every single variable and method static, which proves he didn’t really understand the OO concept at all, as you can’t subclass static methods, and because of that you can’t test the methods of a such a class in your unit tests.

 

After you have isolated each dependency into its own class, you can use a technique called constructor injection (often called dependency injection) when creating an instance of the class under test, and then you can do unit tests on the productive code which will run in the development environment and not really try to read and write to the database, send emails etc., but which will test the business logic nonetheless.

 

For an example program where I use this technique to create a mock class for an external system please see my blog:-

 

http://scn.sap.com/community/abap/blog/2013/01/08/domain-specific-language-in-abap

 

When I started looking at this a few years back I thought “what a waste of time, 95% of my programs involve reading things from the database and updating other database tables, if you fake all of this using stubs what in the world is the benefit?”

 

It turns out that the remaining 5% of code is riddled with bugs and this shows up instantly during unit testing. If you don’t believe me, try it yourself and you will most likely discover the same thing on the very first attempt. Many times I have thought “this routine has only three lines of code – IT CAN’T BE WRONG – yet it was. In the ABAP2XLS project unit tests have started to be introduced, and there was a test on a three line method that failed and it took me ages to work out why.

 

Epilogue – Procedural Tests

 

Simon Says Put Your Hands On Your Head

 

I like to play a game, that is so much fun, and it’s not so very hard to do. The name of the game is “unit test for procedural programs” and I’d like for you to play it to. Tra la lala!

 

You may say to yourself “this procedural program works perfectly, it is business critical and reworking even slightly is far too dangerous” .You may well be right, this however does not rule out using the ABAP unit test framework. As we have said, if having tests is the most vital thing, then if your procedural code has unit tests and the OO programmer sticking his nose up at you does not, then the OO programmer is the one with the legacy code!

 

Often I have been in that very boat – let’s take a Z function module for example – in SE37 you can take the option “Goto -> Local Test Classes” and add the test class definition and implementation. In this situation, as the classes are local you can do PERFORM calls from within your test methods.

 

Now we find that having everything as a global variable actually works for us. At the start of each test method you need to do a bit of setup to make sure the global variables are in the state you want.

 

In the test driven development methodology you write the test first. This doesn’t make any sense in this context as we are dealing with an established program, but in my example I start with the actual problem we have found in production, which is causing me to change my monolithic program in the first place. I keep changing examples, in this one the application is one that checks to see if a supplier invoice will process correctly.

 

007 Legacy.png

Legacy Figure 07

 

As always, to quote Frank N Furter, do not get too hung up on the way my examples look, i.e. what the application is trying to do, it’s the principle that matters.

 

In this case the GIVEN section sets up the input to the function module, the WHEN section calls the function module itself, and the THEN section evaluates the result. If ABAP let you have method names longer than 30 characters like Java and the like seem to allow, then you could have the whole thing in plain English with no need for comments at all.

 

There are two possibilities here:-

 

-          Within your function module you have a clear separation between data retrieval (from the database or wherever) and processing of the data you just retrieved. In that case you are laughing. In the GIVEN section you set up those global tables with the fake values you want for the test, and in the WHEN section you call the PERFORMS from your function which deal with processing those global tables.

-          If everything in the function is hopelessly intermingled, which is usually the case, then you would have to make some changes i.e. move the database retrieval into its own class as described above. If you want tests there is no way around this.

 

008 Legacy.png

Legacy Figure 008

 

This is not the end of the world, it seems hopeless at the start, but if you break things up one little bit at a time you can get there in the end, though your colleagues may be really puzzled why you move all the database reads into one place.

 

Epilogue – Procedural Tests

 

Have I said too much? I haven’t said enough

 

If you get to the stage where you have broken all your dependencies by isolating various things into their own classes and you are in OO world to an extent, then there is nothing more for me to say as there is a flood of articles on the internet about how to proceed from that point. Here are some links to various articles on OO unit testing that I have found useful.

 

I have mentioned lots of these on my other blogs, but I cannot point people at them often enough:-

 

http://www.objectmentor.com/resources/articles/WorkingEffectivelyWithLegacyCode.pdf

 

http://misko.hevery.com/2009/07/07/why-are-we-embarrassed-to-admit-that-we-dont-know-how-to-write-tests/

 

http://dannorth.net/introducing-bdd/

 

http://scn.sap.com/community/abap/blog/2013/01/26/getting-the-brownfield-clean-but-not-green--part-i

 

http://scn.sap.com/people/stephen.pfeiffer/blog/2010/08/23/managing-dependencies-in-tdd-private-injection-in-abap-in-ehp1

 

As a last word, I may seem to say things as if I know it all, but that is far from the truth, that’s just me getting carried away, I am sure I have said a load of nonsense many times in the above blog, as well as jumping from subject to subject at random.

 

When I have made a mistake, or if you disagree in the slightest with anything I have said, please post a response. You will be doing me a favour, though to be honest I didn’t think the following comment I got was very positive – it was - “have you ever even USED SAP????”

 

Oh dear! Well anyway, I have in fact been using SAP every day since 1997, and the way I use it has changed dramatically in that period, but if I am still using it incorrectly, well I need to be told…

 

Cheersy Cheers from Down Under

 

Paul

 

Daleks with Textbooks.jpg

Understanding the events of OOPS ALV

$
0
0

*&---------------------------------------------------------------------*
*& Report  YSUV_ALV_EDITABLE_OOPS_003
*&
*&---------------------------------------------------------------------*
*&
*&
*&---------------------------------------------------------------------*

REPORT  ysuv_alv_editable_oops_003.

*&----------------------------------------------------------------------*
*               WHAT THIS REPORT DOES
*&----------------------------------------------------------------------*
* This Report tells the use of the five events in OOPS ALV .
*         1.) Event Toolbar .
*         2.) Event Double_click .
*         3.) Event Top_of_page .
*         4.) Event Hotspot_click .
*         5.) Event User_command .
* Also the colour of the particular cell is set based on a condition .
*
* -> The report is Editable and interactive .
* -> A button SAVE is defined in the toolbar .
* -> When that save button is pressed then the changed data will be saved
*    in the database using the event user_command .
* -> Double click on the entite row will call transaction ME23N .
* -> Hotspot Click on the first field will call ME23N .
*&----------------------------------------------------------------------*
*                   ESSENTIAL STEPS
*&----------------------------------------------------------------------*
*STEP 1: Create a screen with screen no. 0600 and create a custom
*        container and give it a name 'CUSTOM_CONTAINER' . Create a PF
*        status for this screen .
*STEP 2: Define a local class for event handling lcl_event_handler .
*        And define the methods for  each event within this class .
*        Implement the event handler methods .
*STEP 3: Link all the events with the event handler methods using
*        the statement SET_HANDLER before displaying the ALV
*&----------------------------------------------------------------------*
*                UNDERSTANDING THE REPORT
*&----------------------------------------------------------------------*
* NOTE 1: TOP_OF_PAGE event uses the object of class CL_DD_DOCUMENT.
*         In this class there are methods ADD_TEXT, ADD_PICTURE, and ADD_GAP
*         which are useful to to show the contenet in the TOP_OF_PAGE.
*         One important thing is to split the screen into two parts using the
*         splitter container and then use the first part to TOP_OF_PAGE and
*         the second one to show the Grid data.
* NOTE 2: If a tool is created in the toolbar of the ALV Grid using the
*         event toolbar , the functionality of that tool can be handeled in
*         the event user_command .
*&----------------------------------------------------------------------*


CLASS :lcl_event_receiver DEFINITION DEFERRED.

*Type pools for ALV Cell Attributes (Colour)
INCLUDE<cl_alv_control>.

*To use icons in the toolbar of the ALV .
*To see all the icons just go to the transparent table ICON .
TYPE-POOLS : icon .

* Type Pool for ALV
TYPE-POOLS : slis.

TYPES: BEGINOF t_ekpo,
ebeln
TYPE ekpo-ebeln,
ebelp
TYPE ekpo-ebelp,
statu
TYPE ekpo-statu,
aedat
TYPE ekpo-aedat,
matnr
TYPE ekpo-matnr,
menge
TYPE ekpo-menge,
meins
TYPE ekpo-meins,
netpr
TYPE ekpo-netpr,
peinh
TYPE ekpo-peinh,
field_style 
TYPE lvc_t_styl, "For Making the cell editable / non editable
color_cell
TYPE lvc_t_scol, " Cell color
ENDOF t_ekpo.

DATA: it_ekpo TYPESTANDARDTABLEOF t_ekpo INITIALSIZE0,
wa_ekpo
TYPE t_ekpo ,
wa_upd_ekpo
TYPE ekpo .

*ALV data declarations
DATA: it_fieldcat TYPE lvc_t_fcat, "slis_t_fieldcat_alv WITH HEADER LINE,
wa_fieldcat
TYPE lvc_s_fcat,
it_layout
TYPE lvc_s_layo,
it_color
TYPETABLEOF lvc_s_scol,
wa_color
TYPE lvc_s_scol.

*Data declarations for ALV
DATA: custom_container TYPEREFTO cl_gui_custom_container,   "Custom container
alv_grid
TYPEREFTO cl_gui_alv_grid.   "ALV grid object

* Reference to local class that handles events of ALV Grid .
* The Class lcl_event_receiver is a local class which will be defined in this program
DATA : event_receiver TYPEREFTO lcl_event_receiver.

* Data Declaration to handle the top_of_page Event .
DATA : dg_dyndoc_id TYPEREFTO cl_dd_document,
* Reference to split container
dg_splitter         
TYPEREFTO cl_gui_splitter_container,
* Reference to grid container
dg_parent_grid    
TYPEREFTO cl_gui_container,
* Reference to html container
dg_html_cntrl       
TYPEREFTO cl_gui_html_viewer,
* Reference to html container
dg_parent_html    
TYPEREFTO cl_gui_container.


*Start-of-selection.
START-
OF-SELECTION.

PERFORM data_retrieval.
PERFORM set_specific_field_attributes.
PERFORM build_fieldcatalog.
PERFORM build_layout.

CALLSCREEN0600 .

*----------------------------------------------------------------------*
*       CLASS lcl_event_receiver DEFINITION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_event_receiver DEFINITION.

PUBLICSECTION.
METHODS:
handle_toolbar
FOREVENT toolbar OF cl_gui_alv_grid
IMPORTING e_object
e_interactive,

handle_user_command
FOREVENT user_command OF cl_gui_alv_grid
IMPORTING e_ucomm ,

handle_top_of_page
FOREVENT top_of_page OF cl_gui_alv_grid
IMPORTING e_dyndoc_id ,

handle_double_click
FOREVENT double_click OF cl_gui_alv_grid
IMPORTING e_row
e_column ,

handle_hotspot_click
FOREVENT hotspot_click OF cl_gui_alv_grid
IMPORTING  e_row_id
e_column_id
es_row_no
sender.

ENDCLASS.                    "lcl_event_receiver DEFINITION

*----------------------------------------------------------------------*
*       CLASS lcl_event_receiver IMPLEMENTATION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_event_receiver IMPLEMENTATION.

METHOD handle_toolbar.
* § 2.In event handler method for event TOOLBAR: Append own functions
*   by using event parameter E_OBJECT.
DATA: ls_toolbar  TYPE stb_button.
*....................................................................
* E_OBJECT of event TOOLBAR is of type REF TO CL_ALV_EVENT_TOOLBAR_SET.
* This class has got one attribute, namly MT_TOOLBAR, which
* is a table of type TTB_BUTTON. One line of this table is
* defined by the Structure STB_BUTTON (see data deklaration above).
*

* A remark to the flag E_INTERACTIVE:
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*         'e_interactive' is set, if this event is raised due to
*         the call of 'set_toolbar_interactive' by the user.
*         You can distinguish this way if the event was raised
*         by yourself or by ALV
*         (e.g. in method 'refresh_table_display').
*         An application of this feature is still unknown... :-)

* append a separator to normal toolbar
CLEAR ls_toolbar.
MOVE'SAVE'TO ls_toolbar-function.
MOVE icon_system_save TO ls_toolbar-icon. " See this icon in the transparent table ICON .
MOVE'Save Edited Data'(111) TO ls_toolbar-quickinfo.
MOVE'Save'(112) TO ls_toolbar-text.
APPEND ls_toolbar TO e_object->mt_toolbar.

ENDMETHOD.                    "handle_toolbar

METHOD handle_user_command.
* If any changes were made in the editable cell of the ALV that will be
* stored during runtime
CASE e_ucomm .
WHEN'SAVE'.
LOOPAT it_ekpo INTO wa_ekpo.
SELECTSINGLE * FROM ekpo INTO wa_upd_ekpo
WHERE ebeln = wa_ekpo-ebeln AND
ebelp = wa_ekpo-ebelp .
wa_upd_ekpo-netpr = wa_ekpo-netpr .
UPDATE ekpo FROM wa_upd_ekpo .
ENDLOOP.
ENDCASE.
ENDMETHOD.                    "handle_double_click

METHOD handle_top_of_page.
PERFORM event_top_of_page USING dg_dyndoc_id.
ENDMETHOD.                    "handle_top_of_page

METHOD handle_double_click .
* The double click will work on the entire line(row) of the ALV
READTABLE it_ekpo INTO wa_ekpo INDEX e_row-indexTRANSPORTING ebeln.
*     Set parameter ID for transaction screen field
SETPARAMETERID'BES'FIELD wa_ekpo-ebeln.
*     Sxecute transaction ME23N, and skip initial data entry screen
CALLTRANSACTION'ME23N'ANDSKIPFIRSTSCREEN.
ENDMETHOD.                    "handle_double_click

METHOD handle_hotspot_click .
*     The hotspot is set in the field catalog level
READTABLE it_ekpo INTO wa_ekpo INDEX e_row_id-indexTRANSPORTING ebeln.
*     Set parameter ID for transaction screen field
SETPARAMETERID'BES'FIELD wa_ekpo-ebeln.
*     Sxecute transaction ME23N, and skip initial data entry screen
CALLTRANSACTION'ME23N'ANDSKIPFIRSTSCREEN.
ENDMETHOD.                    "handle_hotspot_click

ENDCLASS.                    "lcl_event_receiver IMPLEMENTATION
*&---------------------------------------------------------------------*
*&      Module  STATUS_0600  OUTPUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
MODULE status_0600 OUTPUT.
SET PF-STATUS 'STATUS100'.
*  SET TITLEBAR 'xxx'.
ENDMODULE.                 " STATUS_0600  OUTPUT
*&---------------------------------------------------------------------*
*&      Module  alv_grid  OUTPUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
MODULE alv_grid OUTPUT.

CREATE OBJECT custom_container
EXPORTING
container_name =
'CUSTOM_CONTAINER'.

CREATE OBJECT dg_dyndoc_id
EXPORTING style = 'ALV_GRID'.
* Create Splitter for custom_container
CREATE OBJECT dg_splitter
EXPORTING parent  = custom_container
rows    = 2
columns =
1.

* Split the custom_container to two containers and move the reference
* to receiving containers g_parent_html and g_parent_grid
"i am allocating the space for grid and top of page
CALLMETHOD dg_splitter->get_container
EXPORTING
row       =
1
column    =
1
RECEIVING
container = dg_parent_html.
CALLMETHOD dg_splitter->get_container
EXPORTING
row       =
2
column    =
1
RECEIVING
container = dg_parent_grid.

"you can set the height of it
* Set height for g_parent_html
CALLMETHOD dg_splitter->set_row_height
EXPORTING
id     = 1
height =
33.

CREATE OBJECT alv_grid
EXPORTING
i_parent = dg_parent_grid.
"* SET field for ALV

CALLMETHOD alv_grid->register_edit_event
EXPORTING
i_event_id = cl_gui_alv_grid=>mc_evt_enter.

CREATE OBJECT event_receiver.
SETHANDLER event_receiver->handle_user_command FOR alv_grid.
SETHANDLER event_receiver->handle_toolbar FOR alv_grid.
SETHANDLER event_receiver->handle_top_of_page FOR alv_grid.
SETHANDLER event_receiver->handle_double_click FOR alv_grid .
SETHANDLER event_receiver->handle_hotspot_click FOR alv_grid .

* setting focus for created grid control
* Call "set_focus" if you want to make sure that 'the cursor'
* is active in your control.
CALLMETHOD cl_gui_control=>set_focus
EXPORTING
control = alv_grid.

CALLMETHOD alv_grid->set_table_for_first_display
EXPORTING
is_layout                     = it_layout
CHANGING
it_outtab                     = it_ekpo
it_fieldcatalog               = it_fieldcat
EXCEPTIONS
invalid_parameter_combination =
1
program_error                 =
2
too_many_lines                =
3
OTHERS                        = 4.
IF sy-subrc <> 0.
MESSAGEID sy-msgid TYPE sy-msgty NUMBER sy-msgno
WITH sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ENDIF.

* Initializing document
CALLMETHOD dg_dyndoc_id->initialize_document.

* Processing events
CALLMETHOD alv_grid->list_processing_events
EXPORTING
i_event_name =
'TOP_OF_PAGE'
i_dyndoc_id  = dg_dyndoc_id.


CALLMETHOD alv_grid->set_toolbar_interactive.

ENDMODULE.                 " alv_grid  OUTPUT
*&---------------------------------------------------------------------*
*&      Module  USER_COMMAND_0600  INPUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
MODULE user_command_0600 INPUT.
CASE sy-ucomm .
WHEN'BACK'.
LEAVEPROGRAM.
ENDCASE.

ENDMODULE.                 " USER_COMMAND_0600  INPUT
*&---------------------------------------------------------------------*
*&      Form  data_retrieval
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*  -->  p1        text
*  <--  p2        text
*----------------------------------------------------------------------*
FORM data_retrieval .
SELECT ebeln
ebelp
statu
aedat
matnr
menge
meins
netpr
peinh
UPTO10ROWSFROM ekpo
INTO  CORRESPONDING FIELDSOFTABLE it_ekpo.

ENDFORM.                    " data_retrieval
*&---------------------------------------------------------------------*
*&      Form  set_specific_field_attributes
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*  -->  p1        text
*  <--  p2        text
*----------------------------------------------------------------------*
FORM set_specific_field_attributes .
DATA ls_stylerow TYPE lvc_s_styl .
DATA lt_styletab TYPE lvc_t_styl .

* Populate style variable (FIELD_STYLE) with style properties
*
* The NETPR field/column has been set to editable in the fieldcatalog...
* The following code sets it to be disabled(display only) if 'NETPR'
* is gt than 10.
LOOPAT it_ekpo INTO wa_ekpo.
IF wa_ekpo-netpr EQ555 .
ls_stylerow-fieldname =
'NETPR' .
ls_stylerow-style = cl_gui_alv_grid=>mc_style_disabled.
"set field to disabled
* APPEND has now been replaced by the INSERT command as it would error
* if entries are not added in correct order
**      APPEND ls_stylerow  TO wa_ekko-field_style.
INSERT ls_stylerow INTOTABLE wa_ekpo-field_style.
MODIFY it_ekpo FROM wa_ekpo.
ENDIF.

*Colouring the particular cell of the ALV Output .
IF wa_ekpo-menge EQ1.
MOVE'MENGE'TO wa_color-fname.
MOVE'6'TO wa_color-color-col. " 6 is for red colour .
MOVE'1'TO wa_color-color-int.
MOVE'1'TO wa_color-color-inv.
APPEND wa_color TO it_color.
wa_ekpo-color_cell[] = it_color[].
MODIFY it_ekpo FROM wa_ekpo TRANSPORTING color_cell .
ENDIF.

ENDLOOP.

ENDFORM.                    " set_specific_field_attributes
*&---------------------------------------------------------------------*
*&      Form  build_fieldcatalog
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*  -->  p1        text
*  <--  p2        text
*----------------------------------------------------------------------*
FORM build_fieldcatalog .
wa_fieldcat-fieldname   =
'EBELN'.
wa_fieldcat-scrtext_m   =
'Purchase Order'.
wa_fieldcat-col_pos     =
0.
wa_fieldcat-outputlen   =
10.
wa_fieldcat-emphasize   =
'X'.
wa_fieldcat-
key         = 'X'.
wa_fieldcat-
hotspot     = 'X'. " To set the hotspot on this field
APPEND wa_fieldcat TO it_fieldcat.
CLEAR  wa_fieldcat.

wa_fieldcat-fieldname   =
'EBELP'.
wa_fieldcat-scrtext_m   =
'PO Item'.
wa_fieldcat-col_pos     =
1.
APPEND wa_fieldcat TO it_fieldcat.
CLEAR  wa_fieldcat.

wa_fieldcat-fieldname   =
'STATU'.
wa_fieldcat-scrtext_m   =
'Status'.
wa_fieldcat-col_pos     =
2.
APPEND wa_fieldcat TO it_fieldcat.
CLEAR  wa_fieldcat.

wa_fieldcat-fieldname   =
'AEDAT'.
wa_fieldcat-scrtext_m   =
'Item change date'.
wa_fieldcat-col_pos     =
3.
APPEND wa_fieldcat TO it_fieldcat.
CLEAR  wa_fieldcat.

wa_fieldcat-fieldname   =
'MATNR'.
wa_fieldcat-scrtext_m   =
'Material Number'.
wa_fieldcat-col_pos     =
4.
APPEND wa_fieldcat TO it_fieldcat.
CLEAR  wa_fieldcat.

wa_fieldcat-fieldname   =
'MENGE'.
wa_fieldcat-scrtext_m   =
'PO quantity'.
wa_fieldcat-col_pos     =
5.
APPEND wa_fieldcat TO it_fieldcat.
CLEAR  wa_fieldcat.

wa_fieldcat-fieldname   =
'MEINS'.
wa_fieldcat-scrtext_m   =
'Order Unit'.
wa_fieldcat-col_pos     =
6.
APPEND wa_fieldcat TO it_fieldcat.
CLEAR  wa_fieldcat.

wa_fieldcat-fieldname   =
'NETPR'.
wa_fieldcat-scrtext_m   =
'Net Price'.
wa_fieldcat-
edit        = 'X'. "sets whole column to be editable
wa_fieldcat-col_pos     =
7.
wa_fieldcat-outputlen   =
15.
wa_fieldcat-datatype     =
'CURR'.
APPEND wa_fieldcat TO it_fieldcat.
CLEAR  wa_fieldcat.

wa_fieldcat-fieldname   =
'PEINH'.
wa_fieldcat-scrtext_m   =
'Price Unit'.
wa_fieldcat-col_pos     =
8.
APPEND wa_fieldcat TO it_fieldcat.
CLEAR  wa_fieldcat.


ENDFORM.                    " build_fieldcatalog
*&---------------------------------------------------------------------*
*&      Form  build_layout
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*  -->  p1        text
*  <--  p2        text
*----------------------------------------------------------------------*
FORM build_layout .
* Set layout field for field attributes(i.e. input/output)
it_layout-stylefname =
'FIELD_STYLE'.
it_layout-zebra             =
'X'.
it_layout-ctab_fname =
'COLOR_CELL'.

ENDFORM.                    " build_layout
*&---------------------------------------------------------------------*
*&      Form  event_top_of_page
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->P_E_DYNDOC_ID  text
*      -->P_0222   text
*----------------------------------------------------------------------*
FORM event_top_of_page USING dg_dyndoc_id TYPEREFTO cl_dd_document.

DATA : dl_text(255) TYPEc"Text
* Populating header to top-of-page
CALLMETHOD dg_dyndoc_id->add_text
EXPORTING
text      = 'Test Report'
sap_style = cl_dd_area=>heading.

CALLMETHOD dg_dyndoc_id->add_gap
EXPORTING
width =
200.

CALLMETHOD dg_dyndoc_id->add_picture
EXPORTING
picture_id =
'ENJOYSAP_LOGO'.

CLEAR : dl_text.
* Move program ID
CONCATENATE'Program Name :' sy-repid
INTO dl_text SEPARATEDBY space.

CALLMETHOD dg_dyndoc_id->new_line.

CLEAR : dl_text.
* Move User ID
CONCATENATE'User ID :' sy-uname INTO dl_text SEPARATEDBY space
.
* Add User ID to Document
PERFORM add_text USING dl_text.
* Add new-line
CALLMETHOD dg_dyndoc_id->new_line.

CLEAR : dl_text.
* Move Client
CONCATENATE'Client :' sy-mandt INTO dl_text SEPARATEDBY space.
* Add Client to Document
PERFORM add_text USING dl_text.
* Add new-line
CALLMETHOD dg_dyndoc_id->new_line.

CLEAR : dl_text.
* Move date
WRITE sy-datum TO dl_text.
CONCATENATE'Date :' dl_text INTO dl_text SEPARATEDBY space.
* Add Date to Document
PERFORM add_text USING dl_text.
* Add new-line
CALLMETHOD dg_dyndoc_id->new_line.

CLEAR : dl_text.
* Move time
WRITE sy-uzeit TO dl_text.
CONCATENATE'Time :' dl_text INTO dl_text SEPARATEDBY space.
* Add Time to Document
PERFORM add_text USING dl_text.
* Add new-line
CALLMETHOD dg_dyndoc_id->new_line.
* Populating data to html control
PERFORM html.

ENDFORM.                    " selection_screen_details
*&---------------------------------------------------------------------*
*&      Form  add_text
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->P_DL_TEXT  text
*----------------------------------------------------------------------*
FORM add_text USING p_text TYPE sdydo_text_element.
* Adding text
CALLMETHOD dg_dyndoc_id->add_text
EXPORTING
text         = p_text
sap_emphasis = cl_dd_area=>heading.
ENDFORM.                    " ADD_TEXT
*&---------------------------------------------------------------------*
*&      Form  commentary_write
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*  -->  p1        text
*  <--  p2        text
*----------------------------------------------------------------------*
FORM html .

DATA : dl_length  TYPEi,                           " Length
dl_background_id
TYPE sdydo_key VALUE space. " Background_id

* Creating html control
IF dg_html_cntrl ISINITIAL.
CREATE OBJECT dg_html_cntrl
EXPORTING
parent    = dg_parent_html.
ENDIF.

CALLFUNCTION'REUSE_ALV_GRID_COMMENTARY_SET'
EXPORTING
document = dg_dyndoc_id
bottom   = space
IMPORTING
length   = dl_length.
* Get TOP->HTML_TABLE ready
CALLMETHOD dg_dyndoc_id->merge_document.
* Set wallpaper
CALLMETHOD dg_dyndoc_id->set_document_background
EXPORTING
picture_id = dl_background_id.
* Connect TOP document to HTML-Control
dg_dyndoc_id->html_control = dg_html_cntrl.
* Display TOP document
CALLMETHOD dg_dyndoc_id->display_document
EXPORTING
reuse_control      =
'X'
parent             = dg_parent_html
EXCEPTIONS
html_display_error =
1.
IF sy-subrc NE0.
*    MESSAGE i999 WITH 'Error in displaying top-of-page'(036).
ENDIF.

ENDFORM.                    " commentary_write

XML Generation for Additional Fields in Audit Management

$
0
0

1.0   Introduction

     XML format is being used more often to exchange the data between different systems.For easy Readability of data the XML format is used. We can also import this XML Document for the future reference.In Audit Management XML Generation is used when importing and exporting audit components.

 

2.0   Steps for generating the XML

Click on the Audit from which you want to display the contents in XML format.

            Goto Audit Component --> Export --> Export Audit Component -->Export

 

1.jpg

2.jpg

Data of the Audit components will be present inside the XML Tags.

 

3.jpg

3.0   Process Overview

Whenever XML is generated it should contain the data of all additional fields added to the BASIC DATA Tab in the XML Format.

4.jpg

      XML Export of the new fields

  •    Export of audit XML file
  •   Export of corrective/preventive actions XML file

 

Additional Fields added to the BASIC DATA TAB should also have the XML format.

5.jpg

Whenever XML is generated it should contain the data of all additional text types added to the TEXTS Tab in the XML Format. Additional Text Types ‘CAUSE’ and ‘PLAN’ added to the TEXTS TAB should also have the XML Format.

 

CAUSE:

      Whenever a Corrective Action is created, the user has to mention the cause for creating it, which makes the End User to understand the reason for Creating the Corrective Action.

PLAN:

    Whenever a Corrective Action is created, the user has to mention the plan for correcting it ,so that  End User understands that some planning activities has been done for correcting it.

6.jpg

 

Additional Text Types should be displayed in a separate tag as well.

 

7.jpg

 

4.0  Creation of Text ID

The Text Types created in the Maintenance View V_TTXIDI will be reflected in the TEXTS TAB of PLMD_AUDIT Transaction.

             The Text ID’s for text types (Cause and Plan) should be created in Maintenance View V_TTXIDI in all the systems.

                                    Cause– Describe how root cause have been determined.

OBJECT ID : CGPL_TEXT

TEXT ID : CAUS

MEANING : Cause

 

                                    Plan  - Describe the plan for implementing the action.

OBJECT ID : CGPL_TEXT

TEXT ID : PLAN

MEANING : Plan

 

5.0   Append Structure

           “PLMT_AUDIT_ACT_BAPI_XML” is the Structure which contains all the fields to be displayed in the XML Format. We have to append the Additional  Fields and additional Text types to the Structure “PLMT_AUDIT_ACT_BAPI_XML” and Move the data of additional fields to the standard XML structure.

 

6.0  Implicit Enhancement: YPLM_XML

             XML of the additional fields   should be generated individually after the standard Items. An Implicit Enhancement “YPLM_XML” is created in the function module” MAP2E_PLM_COR_PRI_2_BAPI20370” in order to display the additional text types individually in the XML generation.

 

8.jpg

The Method CL_PLM_AUDIT_IF_SERVICES=>CONVERT_LONG_TEXT_2_STRING will read the data of the TEXT and move to the Corresponding string in the Structure ES_20370_FOR_XML.

Pseudo Code:

CALL METHOD CL_PLM_AUDIT_IF_SERVICES=>CONVERT_LONG_TEXT_2_STRING
        EXPORTING
          IT_TEXTDATA = LS_PRI_TEXT-TEXTDATA
          IV_TDID     = 'CAUS'
        IMPORTING
          EV_STRING   = ES_20370_FOR_XML-CAUS
        CHANGING
          CT_RETURN   = CT_RETURN.

 

CALL METHOD CL_PLM_AUDIT_IF_SERVICES=>CONVERT_LONG_TEXT_2_STRING
        EXPORTING
          IT_TEXTDATA = LS_PRI_TEXT-TEXTDATA
          IV_TDID     = 'PLAN'
        IMPORTING
          EV_STRING   = ES_20370_FOR_XML-PLAN
        CHANGING
          CT_RETURN   = CT_RETURN.

 

FINAL OUTPUT:

Additional Fields added to the BASIC DATA TAB  is having the XML format.

9.jpg

Additional Text Types added to the TEXTS TAB  is having the XML format.

10.jpg

 

Additional Text Types is  displayed in a separate tag as well.

7.jpg

 

7.0      Challenges

 

The XML is generated based on the fields in BAPI Structure. We have to append the Additional Fields in the BAPI Structure. But While Upgrade the BAPI Extension will create some performance issues so in order to avoid that an implicit Enhancement was created in the Function module for the same functionality.

Simple Remote Query

$
0
0
Summary:

 

INTRODUCTION


Few days ago I fixed a bug referring to my blog about runtime generation of structures and internal tables  defined on a remote dictionary (reachable by a rfc connection). I thought that this way to handle data type could be also useful for reading specific database records defined on backend system database. Remote database records can be read by means of META_READ_TABLE and RFC_READ_TABLE standard function modules;  these modules are useful and powerful but for sure they are not developer-friendly because of its table parameters: options, fields and data. In details:

  • options must be filled with a query specified as 72 characters long rows, so if your query is quite long you must convert it
  • fields must be filled with fields of your interest
  • data will be filled with records found but the result is returned as a flat line of 512 characters, so you can ask only for fields whose total length doesn’t exceed 512 characters and you must parse the result using fields information returned

Obviously that’s not a conventional/good way for a backend integration; this one should be performed by means of standard BAPIs/RFC enabled modules and custom development on backend. Anyway, sometimes, to perform some spot and simple checks or a quick bug fix these modules become necessary; the SRM standard uses them in many points too.


FIRST GOAL


So, I thought: since we haven't data redundancy problem on dictionary (thanks to dynamic data type handling), why not to make it easier, much more developer-friendly?


As i’ve shown in my blog mentioned above it is possible to handle a structure or an internal table defined in the data dictionary of backend sap system; so the need is only to build a software layer (encapsulating RFC_READ_TABLE module) for simplifying input and output parameters. The easiest way probably would be a slim interface asking as input only an RFC destination, a query string and a table name and returning as output an internal table built at runtime (so avoiding data redundancy problem) exactly as defined on remote system, so with a line containing all table fields (definitely the so called select *). So I started to develop new abap classes  to achieve the goal and I achieved it with relative ease.

 

I developed a class named ZCL_DYN_REMOTE with 3 main methods:

 

  • BUILD_QUERY to convert a query string into the 72-character table needed by standard modules
  • GET_REMOTE_STRUC_DATA that execute a “select single *” on backend
  • GET_REMOTE_TABLE_DATA that execute a “select *” on backend

 

Structure/internal tables for getting the result can be built by means of method BUILD_DATA in my ZCL_DYN_REMOTE_TYPE_BUILDER class. Let's try to make a comparison between statements needed for local static and dynamic remote database record selection usign that my new classes.

 

  DATA DECLARATION

* static local
  data: lt_itab type standard table of<<tabname>>,
        ls_wa   type<<tabname>>.

* dynamic remote
  data: wa_data   type ref to data,
        itab_data type ref to data.

  call method zcl_dyn_remote_type_builder=>build_data
    exporting
      i_rfcdest   =<<rfc>>
      i_struct    =<<tabname>>
    importing
      e_strucdata = wa_data
      e_tabledata = itab_data.

  QUERY

* static local     
* could be specified directly in the where clause of Open-SQL statement or
* runtime evaluated building a string containing it

* dynamic remote
* you must build it as a string and convert in 72 character table with a simple method call
  <<query_72c_tab>> = zcl_dyn_remote=>build_query( i_query =<<query_string>> ).

  SINGLE RECORD

* static local     
  select single * from<<tabname>> into ls_wa where<<logical_condition>>.

* dynamic remote
  field-symbols<fs_wa> type any.

  assignwa_data->*to<fs_wa>.

  call method zcl_dyn_remote=>get_remote_struc_data
    exporting
      i_rfc_destination =<<rfc>>
      i_table           =<<tabname>>
      i_query           =<<query_72c_tab>>
    importing
      e_struc           =<fs_wa>.

  MULTIPLE RECORDS

* static local     
  select * from<<tabname>> into table lt_itab where<<logical_condition>>.

* dynamic remote
  field-symbols<fs_itab> type standard table.

  assignitab_data->*to<fs_itab>.

  call method zcl_dyn_remote=>get_remote_table_data
    exporting
      i_rfc_destination =<<rfc_destination_name>>
      i_table           =<<tabname>>
      i_query           =<<query_72c_tab>>
    importing
      e_table           =<fs_itab>.

 

This simplifies a lot usual development with META_READ_TABLE and RFC_READ_TABLE module.

 

MACRO: A NEW PERSPECTIVE


Recently I  read a very interesting blog by Rüdiger Plantiko about the use of abap macros;  it explains perfectly advantages and disadvantages of using them. The main advantage is doubtless, especially by exploiting  colon/comma notation,  the resulting syntactic shortness and the improved readability. Why have I mentioned it? Because immediately after reading this blog I realized that in the end I was just half satisfied; what I was really looking for was a single abap statement that allows to run the query directly on remote system placing selected records in an internal table handled runtime. Something like a select * from <table> on <rfc_destination> into <itab> where <logic_condition>”. Definitely a simple instruction to perform remote queries. Obviously you cannot modify standard open-SQL syntax with new additions neither add new abap statements. The blog definitely lead me to a new perspective on so “mistreated” macros. For syntactic shortness they can also be interpreted exactly as if they were new instructions. Usually code within macros is really short and simple. Basically not only because it’s difficult to debug it (it is not possible to single-step through the instructions) but also because many lines of code mean many potential bugs inside and macro code must first be robust and reliable. But regardless of the complexity of the code within the macro by means of a manic exception handling it is possible to preserve the code both robust and reliable. To make it feeling like a standard instruction you can also set system field values (extended check warns you about it). For example the blog I mentioned above shows a very useful move-corresponding instruction improvement.


SIMPLE REMOTE QUERY


I decided to take up the challenge of being able to have very concise instructions that were similar to standard data declaration and Open-Sql statements. In the end it is just an encapsulation of the code seen above where every catchable exception is managed (“manic exception handling”) in order to ensure instruction robustness and system fields (basically sy-subrc and sy-dbcnt) are set depending on the result. In the end i got my "Simple Remote Query" macros in a ZSRQ include. Below I show an en excerpt of code comparison between local and remote queries after this work; suppose you’re asking for some header and item lines of a backend purchase order (pretending that there isn't any BAPI or forgetting about it).


* static local     
  data: ls_ekpo type ekpo,
        lt_ekpo type standard table of ekpo.

  select single * from ekko into ls_ekko where ebeln eq‘0123456789’.

  if sy-subrc eq 0.
    select * from ekpo into table lt_ekpo where ebeln eq‘0123456789’.
  endif.

* dynamic remote destination “remdest”
  data:lo_ekko  type ref todata,
        lo_ekpo  type ref todata,
        lv_querytypestringvalue`ebeln eq‘0123456789’`.

  _srq_build-structure remdest ‘ekko’ lo_ekko.
  _srq_select-single remdest ‘ekko’ lv_query lo_ekko.

  if sy-subrc eq 0.
    _srq_build-itab remdest ‘ekpo’ lo_ekpo.
    _srq_select remdest ‘ekpo’ lv_query lo_ekpo.
  endif.

As you can see it’s much more faster than:


·        converting your query in a 72 characters tab

·        preparing the field catalog table assuring that the resulting line will be at most 512 character long

·        calling the RFC_READ_TABLE_MODULE

·        parsing resulting lines


Taking advantage of the colon/comma notation it is also possible to group your queries in a single statement. Suppose you need records from 3 different tables named table1, table2 and table 3 and lv_query1, lv_query2 and lv_query3 are strings containing logical conditions needed; you can write:


  _srq_select remdest:‘table1’ lv_query1 lo_table1,
                       'table2’ lv_query2 lo_table2,
                       'table3’ lv_query3 lo_table3.

To manage resulting records friendly the best way is to dereference your data and assign it to a field symbol (type any if the result is a work area, type standard table if the result is an internal table). For example, for the ekpo selection above you code will be something like:


  field-symbols:<fs_ekko> typeany,
                 <ft_ekpo> type standard table.

  assign:lo_ekko->* to<fs_ekko>,
          lo_ekpo->* to<ft_ekpo>.

now you can take advantage of field symbols using standard instructions or using macros:


    _srq_move-corresponding
    _srq_move-mapping
    _srq_get-field

Suppose you have a structure named ls_pohead with several fields properly typed, in which to store related remote table ekko field values, but with different names (EBELN->PONUM, BUKRS->COMPANY, BSART->DOCTYPE). By means of _srq_move-mapping macro you can simply remap structures with a single statement. For example:


  _srq_move-mapping<fs_ekko> ls_pohead: ‘EBELN’ ‘PONUM’, 
                                         ‘BUKRS’ ‘COMPANY’,
                                         ‘BSART’ ‘DOCTYPE’.

Below a debug screenshot by my demo program (using adrc table).


 

WHERE TO GET THE CODE


You can find my job in this nugg.


Macros are all defined in include ZSRQ; to see them in action there’s a program named ZDYNSRQDEMO that shows you how to use all of them. N.B. First activate dictionary and message class, next the whole list of classes and programs.


To Print Multiple Smartform in a Loop at one Go and converting into PDF

$
0
0

Introduction:-

In this Document  we will explain how to display multiple smartfforms in a loop at one go.

There are Scenario when user has to Print multiple smartform in a loop. The problem with this is that if you will write the following code.

Fig 1.

 

Print window(Shown in fig below) will come multiple time. If hundreds of lines are present in the table it is very tough to see one by one and even tougher if you have to save all the smartforms into PDF.

 

Print Window.

Fig 2.

 

 

There is a way to Print all the smartforms  in one go, so that the Pint window(above) does not comes multiple times and all the smartforms will get displayed in one output on different pages.

Step 1. Use function module SSF_FUNCTION_MODULE_NAME to retrieve the name of the function module generated from the Smart Form.

Step 2. Call the Smart Form for the first time and set the NO_CLOSE parameter of the control structure. This prevents the print request from being closed after accepting the output of the Smart Form and allows you to include all other form output into this print request as well. Leave the NO_OPEN parameter empty.

Step 3.  For all other form output of the application program that you want to include into the print request, use a loop to set both the NO_OPEN field and the NO_CLOSE field of the control structure.

Step 4. To close the print request, in the call of the last Smart Form set the NO_OPEN parameter and unmark the NO_CLOSE parameter

 

All these steps are shown in the following steps of coding.

 

 

 

data:
control_parameters 
type ssfctrlop,
w_cnt
typeI,
w_cnt2
typeI,

     fname type  rs38l_fnam.

callfunction'SSF_FUNCTION_MODULE_NAME'
exporting
formname           =
'ZCUSTOMER_LEDGER'        " Smart program has same
" name as of driver program
importing
fm_name            = fname
exceptions
no_form            =
1
no_function_module =
2
others             = 3.
if sy-subrc <> 0.
message'Wrong Smartform Name'type'E'.
endif.
************************** CHANGE FROM HERE


describetable it_tab lines w_cnt.

loopat it_tab into st_tab.
w_cnt2 = sy-tabix .
case w_cnt2.
when1.
control_parameters-no_open   = space .
control_parameters-no_close  =
'X' .
when w_cnt .
control_parameters-no_open   =
'X' .
control_parameters-no_close  = space .
whenothers.
control_parameters-no_open   =
'X' .
control_parameters-no_close  =
'X' .
endcase.


callfunction fname
exporting
control_parameters         = control_parameters
w_kunnr                    = st_tab-kunnr
w_bukrs                    = p_bukrs
w_bdat_l                   = s_budat-low
w_bdat_h                   = s_budat-high
zcus_ledg_st               = st_tab
* IMPORTING
*   DOCUMENT_OUTPUT_INFO       =
*   JOB_OUTPUT_INFO            =
*   JOB_OUTPUT_OPTIONS         =
exceptions
formatting_error           =
1
internal_error             =
2
send_error                 =
3
user_canceled              =
4
others                     = 5
.
if sy-subrc <> 0.
message  'ERROR RELATED TO SMARTFORM OR PAGE FORMAT OR PRINTER'type'E'.
endif.
endloop.

 

 

Note:-NO_OPEN AND NO_CLOSE parameter of CONTROL_PARAMETERS are used to include several forms into one print request. When calling the generated functions modules, set the parameters as follows:

  • 1st call: NO_OPEN = SPACE.
    NO_CLOSE = 'X'.
  • nth call: NO_OPEN = 'X'.
    NO_CLOSE = 'X'.
  • last call: NO_OPEN = 'X'.
    NO_CLOSE = SPACE .

After doing this when you run the smart form, Print window will come only once and all the smartforms will come in single output in different pages. See the  fig below, different Smartforms(i.e. 7 here) are coming on diff pages. Keep on pressing PAGE DOWN and you will keep on getting diff Smartforms.

To convert all this into PDF Write PDF! On the command field of smartforms output and all these smartforms will get converted into PDF.

 

Fig 3.

 

To convert all the Smartforms into PDF at one go.


In SmartForm Output write PDF! on Command Field.

Object-oriented Sieve of Eratosthenes

$
0
0

Reading this blog - The Sieve of Eratosthenes by Ravi Bhatnagar - inspired me to do something I've been wanting to do for a very long time, but have never quite managed - go back to my Computer Science roots and write some proper object oriented code, doing an abstract task (i.e. nothing to do with journals, purchase orders, or anything else business-related) in ABAP. Perverse, maybe, but there must be a general purpose programming language hiding in there somewhere, amongst all the select statements and tables, right? So here goes...

 

As explained in Ravi's blog, the Sieve of Eratosthenes is very simple (in concept) way of generating prime numbers.

  1. First write out all the whole numbers from 2 upwards (2 is the first prime, so no point in looking at lower numbers).
  2. The lowest number (initially 2) on the list is a prime, but all of its multiples aren't so cross them off.
  3. Go back to step 2.

 

Next time around the loop, 3 is the lowest number, then 5 (4 was previously crossed off as a multiple of 2), then 7 (6 disappeared as a multiple of 2), etc. Try it with a piece of paper and a pencil.

 

One thing to note about this description - you need to decide up front what is the largest prime you want to find, because you have to write them all out before you start. Ravi's implementation has this problem and I was keen to avoid it. Conceptually, you should start with an infinite list and each time you find a new prime cross off the infinite number of multiples of it. Now "infinity" is a tricky thing to handle in code, especially infinitely sized data structures. The way to avoid it is to use a technique called "lazy evaluation" - only calculate numbers when you are asked for them.

 

I'm going to start with a class called Counter. This is constructed with a starting value and has a "get" method that gets the next value in sequence.

class Counter definition.  public section.    methods:      constructor importing start type i,      get returning value(r) type i.  private section.    data: value type i.
endclass.

class Counter implementation.
  method constructor.    value = start.  endmethod.  method get.    r = value.    value = value + 1.  endmethod.
endclass.

start-of-selection.
  data: n type i value 1.  data: c type ref to Counter.  data: p type i.  create object c exporting start = 2.  while n <= 10.     p = c->get( ).     write:/ p.     n = n + 1.  endwhile.

 

Run this and you should get the numbers 2..11. OK so far. We have a class that generates a list of numbers as long as we like, but only as many as we ask for. Now we need to start crossing off some of those numbers. Let's build a filter:

 class Filter definition.  public section.    methods:      constructor importing src type ref to Counter                  f type i,      get returning value(r) type i.  private section.    data: filter type i,          source type ref to Counter.
endclass.

class Filter implementation.
  method constructor.    super->constructor( ).    me->source = src.    me->filter = f.  endmethod.  method get.    data: n type i.    n = source->get( ).    while n mod filter = 0.      n = source->get( ).    endwhile.    r = n.  endmethod.
endclass.

This takes a Counter object and a number, and only returns values from the Counter stream that are not multiples of that filter number. So, call it like this and we get the odd numbers 3..21.

 start-of-selection.  data: n type i value 1.  data: c type ref to Counter.  data: f type ref to Filter.  create object c exporting start = 2.  create object f exporting src = c, f = 2.  while n <= 10.    data: p type i.    p = f->get( ).    write:/ p.    n = n + 1.  endwhile.

We're almost there, but we need a little trick before we can put these pieces together. I'm going to skip that for the moment and present one more class, the sieve class:

class Sieve implementation.  method constructor.    data: c type ref to Counter.    create object c exporting start = 2.    source = c.  endmethod.  method get.    data: n type i.    data: nfilter type ref to Filter.    n = source->get( ).    create object nfilter exporting src = source f   = n.    source = nfilter.    r = n.  endmethod.
endclass.

This starts by creating a Counter with start value 2, calling it source. Then each time its "get" method is called it takes the next value from the source and returns that as the next prime. It also builds a filter that takes the current source and filters out all multiples of this new prime, and replaces its source with this new filter. Each time, then we are building a chain of filters, each one removing multiples of new primes as we find them. This only works if the Filter object can filter not just Counters but also other Filters. And this is the trick - inheritance. We achieve that by having both Filter and Counter inherit from a common base class - I call this "Source". I won't repeat the class definitions using inheritance - the complete source to my program is attached.

 

In summary, a little object orientation, a little inheritance, a little lazy evaluation and you get a nice little prime number generator. I've quite enjoyed this little wander down memory lane, with a twist. I may do it again with another programming problem from my Computer Science past . Any suggestions? Any thoughts on using ABAP in this way? I still haven't got comfortable using object orientation in the code I write for business applications - somehow it doesn't seem to fit. Maybe if I do some more exercises like this I'll get better at it.

Local exception class in global class - the trick

$
0
0

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 private 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?)

Untitled.png

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

 

God's_Light.jpg

 

 

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:

Untitled.png

 

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

@zcust01

 

 

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.

OOPs ALV Simplified [without Module Pool Screen]

$
0
0

This in continuation to earlier blogs on SCN :

 

Part I : Basics of OO ABAP Implementation

Part II : OO ABAP Implementation [ Method Parameters ]

 

This is the III posts for people who are new to OOPs ABAP it is just to make things simpler for those who are shifting from procedural to OO ABAP.

 

As we all know that ALV is no longer an addition to the reporting it is the basis of reporting so WRITE statements are a things of the past.

 

So let's begin how to use ALV in the OO context for reporting. I will use the same example of the second blog for output in BASIC ALV. Now there are 2 ways, one is you create a module pool define a screen there and put a container on the screen. Then use this container to display your ALV. The second is using the SCREEN0 [Default Screen] to display our ALV. This is generally better when we are having a SINGLE ALV Report output to be generated.

 

CLASS DEFINITION:

CLASS lcl_material_list DEFINITION FINAL.

      PUBLIC SECTION.

      METHODS: display_material

            IMPORTING i_matnr TYPE matnr.

      PRIVATE SECTION.

     

      DATA : BEGIN OF ls_material,

               werks TYPE werks_d,

               lgort TYPE lgort_d,

               labst TYPE labst,
           
END OF ls_material,

           

            lt_material LIKE TABLE OF ls_material.

      DATA : lo_alvTYPE REF TO   cl_gui_alv_grid,

             lt_fcat TYPE TABLE OF lvc_s_fcat.

 

      METHODS: get_description

            IMPORTING i_matnr       TYPE matnr

            RETURNING VALUE(r_maktx) TYPE maktx,

 

            build_fcat.

 

ENDCLASS.

There is a global class provided by SAP for creating ALV : CL_GUI_ALV_GRID. Similar as we are creating instance of our own class locally, we can also use global classes defined in repository directly in our programs.

THE PROGRAM :

Will be same as it was in Part II no changes.

Well, thanks to Amit Gupta for noting it for the default screen to appear we need to initiate it with the WRITE statement.

After the call method of display_material( ). just put a "Write space."

 

CLASS IMPLEMENTATION:

 

CLASS lcl_material_list IMPLEMENTATION.

METHOD get_description.

      SELECT SINGLE maktx FROM makt

                          INTO r_maktx

                        WHERE matnr = i_matnr

                           AND spras= sy-langu.

ENDMETHOD.

METHOD build_fcat.

 

      DATA : ls_fcat TYPE lvc_s_fcat.

 

ls_fcat-scrtext_m = 'Plant'(001).

ls_fcat-fieldname = 'WERKS'.

ls_fcat-tabname  = 'LT_MATERIAL'.

APPEND ls_fcat TO lt_fcat.

ls_fcat-scrtext_m = 'SLoc'(002).

ls_fcat-fieldname = 'LGORT'.

APPEND ls_fcat TO lt_fcat.

ls_fcat-scrtext_m = 'Un-restricted Stock'(003).

ls_fcat-fieldname = 'LABST'.

APPEND ls_fcat TO lt_fcat.

 

ENDMETHOD.

 

METHOD display_material.

 

      DATA : lv_maktx TYPE maktx,

             lv_title TYPE lvc_title.

 

      lv_maktx = get_description( i_matnr ).

 

      CONCATENATE 'Material : ' i_matnr ' - ' lv_maktx

            INTO lv_title.

 

      SELECT werkslgortlabst FROM mard

                          INTO TABLE lt_material

                                WHERE matnr = i_matnr.

 

      build_fcat( ).

 

      CREATE OBJECT lo_alv

        EXPORTING

          i_parent= cl_gui_container=>screen0.

 

      CALL METHOD lo_alv->set_gridtitle

        EXPORTING

          i_gridtitle= lv_title.

 

      CALL METHOD lo_alv->set_table_for_first_display

        CHANGING

          it_outtab       = lt_material

          it_fieldcatalog = lt_fcat.

 

ENDMETHOD.

ENDCLASS.

 

 

Printing a PDF from URL on windows printer without opening.

$
0
0

Hi ,

 

Recently I've come across this development where we need to print a PDF file on windows default printer.

Seems pretty simple but challenge here is the file is at some server and you are having the path to file in the form of URL and not the file with you.

 

To summarize there is a URL link to PDF file i.e. if you copy paste this URL in explorer window you will get some PDF open.

Now you have to print this PDF .

The development can be broken into steps:

 

1. Identify the URL

2. Fetch the file from URL on your machine.

3. Fetch file from your machine and print it.

 

Now Identify the URL means:

You have authentication to access the URL link .It might happens with you that you are able to open the URL in browser but not able to fetch file in sap.

For that try running report RSHTTP20 in your GUI and if succesfull than proceed with second step.

 

To fetch the file from URL use FM HTTP_FILE_GET. For your understanding refer the code below.

Tables explanation is out of scope of this document.

 

P_URL is the URL link to PDF file.

P_FILE is the path of your system folder to save the file.

Tables response and response_headers not required here in our development.

 

CALL FUNCTION 'HTTP_GET_FILE'

  EXPORTING

    absolute_uri                = p_url

    document_path            = p_file

TABLES

   REQUEST_HEADERS             = response

   RESPONSE_HEADERS           = response_headers

EXCEPTIONS

   CONNECT_FAILED             = 1

   TIMEOUT                            = 2

   INTERNAL_ERROR             = 3

   DOCUMENT_ERROR          = 4

   TCPIP_ERROR                   = 5

   SYSTEM_FAILURE               = 6

   COMMUNICATION_FAILURE  = 7

   OTHERS                                = 8

          .

IF sy-subrc <> 0.

* Implement suitable error handling here

ENDIF.

Now once file is fetched use suitable method to print it at windows printer.

Call below method with parameters as shown here to print the PDF file at your windows default printer.

NOTE: But remember p_url here is your file path from your machine and not URL link.

 

PARAMETER p_url type string.

concatenate '/p' '/h ' p_url INTO url SEPARATED BY space.

.

CALL METHOD cl_gui_frontend_services=>execute

  EXPORTING

    application            = 'ACRORD32.EXE'

    parameter              = url

    minimized              = 'X'

  EXCEPTIONS

    cntl_error             = 1

    error_no_gui           = 2

    bad_parameter          = 3

    file_not_found         = 4

    path_not_found         = 5

    file_extension_unknown = 6

    error_execute_failed   = 7

    synchronous_failed     = 8

    not_supported_by_gui   = 9

    OTHERS                 = 10.

 

Now go and check you have the print outs waiting to be picked at your windows printer.

Disclaimer: All coding is executed successfully at my SAP GUI ECC 6.0.

Viewing all 948 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>