1 6 7 8 9 10 46 Previous Next

ABAP Development

690 Posts

Change Number Two

Now we come to the so called “boundary condition”. In this case, we don’t want to accept any money if the machine is out of gumballs. More precisely whilst in the “no quarter” state normally the event of a coin being entered would cause a “transition” to the “has quarter” state. We want to introduce a check at that point, and abort the change of state. Naturally we would want to trigger the “eject” event in this case to give the user back their money.

 

Time for the OO version first. One of the core OO rules is “program to an interface, not an implementation” so let us create an interface. Over time I have moved from wondering what on Earth the purpose of an interface was to thinking that every single class I create should be based on an interface. It’s taken a lot of experimentation and examples to make me get my head round this. These boundary condition checks are likely to be very different from application to application, so you define an interface for the tiny bits which are always going to be the same.

 

07 Boundary Condition Interface.png

I’ll leave that for the minute just want to re-iterate what it is I am trying to achieve. In a big system I could have assorted programs based on the state machine framework and they all have different boundary conditions.

 

A Place for Everything, Everything in its Place

 

The point of OO programming is to separate the things that change from the things that stay the same. The thing that stays the same is that in different programs some nebulous rule could stop an event-based state change. The thing that changes is what that nebulous rule is. That is why I just declared the abstract “concept” of a boundary condition in an interface, as interfaces don’t have any code.

 

The other side of the coin is to specify the exact rule in any given system. So that would have to live in the class describing the system I presume. So, in the case at hand, I will give the gumball machine class an instance variable to say how many gumballs it has in stock, and set that during the initial configuration of the system. I could put that in the constructor, but the domain specific language concept makes me want to put all the rules in the same place.

 

CREATE OBJECT out_of_gumballs
     
EXPORTING
        gumball_machine
= me
        trigger_event  
= quarter_was_inserted
        error_message  
= 'Machine is out of Gumballs'
        new_state      
= no_quarter_state.

CREATE OBJECT system_resetter
     
EXPORTING
        io_start_state
= no_quarter_state.

    system_resetter
->add_reset_event( machine_was_refilled->md_code ).
   
    me
->current_gumball_stock = 30.

 

As far as I can see the wonderful thing about subclasses is that they can have different constructors and take in all sorts of different extra variables. Why is that good? It helps in areas like the one I am now exploring. In this case I define a local class which is not even a subclass, it is a class that implements an interface. As I may have mentioned earlier the funny thing about learning OO is that the SAP training pushes you to create subclasses, and all the academic books say subclasses are the work of the Devil, instead use interfaces. Anyway, now to define and implement the local boundary condition class.

 

CLASS lcl_boundary_condition DEFINITION.
 
PUBLIC SECTION.
   
INTERFACES zif_sm_boundary_condition.

   
ALIASES: boundary_condition_fulfilled FOR zif_sm_boundary_condition-boundary_condition_fulfilled.
   
   
METHODS: constructor IMPORTING trigger_event TYPE REF TO zcl_sm_event_in
                                   error_message
TYPE string
                                   new_state    
TYPE REF TO zcl_sm_state,
             set_gumball_stock
IMPORTING id_current_stock TYPE sy-tabix.
   
 
PRIVATE SECTION.
   
DATA: current_gumball_stock TYPE sy-tabix.
ENDCLASS.

 

I intend to add this class as a member variable of the state class, in the same way I did for the illegal combination class. The reason this is a local class is I need to pass in the system at hand as an input variable. I have no idea if I am doing this the correct way, I am just playing, and then HOPEFULLY someone more intelligent than me will suggest a better way.

 

CLASS lcl_boundary_condition IMPLEMENTATION.

 
METHOD constructor.
*--------------------------------------------------------------------*
* IMPORTING trigger_event   TYPE REF TO zcl_sm_event_in
*           error_message   TYPE string
*           new_state       TYPE REF TO zcl_sm_state
*           gumball_machine TYPE REF TO lcl_gumball_machine
*--------------------------------------------------------------------*
    mo_trigger_event  
= trigger_event.
    md_error_message  
= error_message.
    mo_new_state      
= new_state.
    mo_gumball_machine
= gumball_machine.

 
ENDMETHOD."Constructor

 
METHOD boundary_condition_fulfilled.

   
IF mo_gumball_machine->current_gumball_stock > 0.
      rf_yes_it_is
= abap_true.
   
ELSE.
      rf_yes_it_is
= abap_false.
   
ENDIF.

 
ENDMETHOD.

ENDCLASS."Local Boundary Condition

 

At the end of this blog I want to talk about so called “functional programming” where you pass In these sort of rules as parameters to a function, but I am getting ahead of myself. The concepts seem to gel to me but that is a diversion, and of course I would never, ever, ever, go off topic in the middle of a blog.

We add an extra instruction to the “configuration” part of the program to add in the extra behaviour we want.

 

* No Quarter State
    no_quarter_state
->state_changes_after( event           = machine_was_refilled
                                           to_target_state
= no_quarter_state ).
    no_quarter_state
->state_changes_after( event           = quarter_was_inserted
                                           to_target_state
= has_quarter_state ).
    no_quarter_state
->needs_check_after( event         = quarter_was_inserted
                                         for_condition
= out_of_gumballs ).
    no_quarter_state->responds_to( event      = crank_was_turned
                                   with_error
= 'Please enter money before turning crank' ).

 

I am happy bunny at how much like natural English this reads.

 

METHOD needs_check_after.
* Local Variables
 
DATA: ls_conditions LIKE LINE OF mt_conditions.

  ls_conditions
-event_code = event->md_code.
  ls_conditions
-condition  = for_condition.

 
APPEND ls_conditions TO me->mt_conditions.

ENDMETHOD.

 

Debugsy Malone

 

To show how this is going to work, I will write a unit test, then we will step through it as if we were in the debugger to see what is going on.

 

METHOD not_sell_when_empty.

* GIVEN....
    mo_class_under_test
->current_gumball_stock = 0.

* WHEN user tries to enter some money.....
   
TRY.
        when_quarter_is_inserted
( ).

     
CATCH zcx_sm_illegal_combination.
        cl_abap_unit_assert
=>assert_equals( act = mo_controller->mo_current_state
                                           
exp = mo_class_under_test->no_quarter_state
                                            msg
= 'Machine in incorrect state' ).
       
RETURN.
   
ENDTRY.

    cl_abap_unit_assert
=>fail( msg = 'Gumball Machine accepted money when machine was empty' ).

 
ENDMETHOD.

 

This is very similar to the last unit test – the same exception should be thrown, the difference is that this combination of state and event is only illegal in certain circumstances.

 

  METHOD when_quarter_is_inserted.
    mo_controller
->handle_inbound_event( mo_class_under_test->quarter_was_inserted->md_code ).
 
ENDMETHOD.

 

No change, so far, though the method to handle the inbound event needs some surgery to enable it to cope with boundary conditions.

 

METHOD handle_inbound_event.
* Local Variables
 
DATA: lo_failed_condition    TYPE REF TO zif_sm_boundary_condition,
        lo_illegal_combination
TYPE REF TO zcl_sm_illegal_combinations.

* Preconditions
 
CHECK id_event_code IS NOT INITIAL.

* Make sure we actually have a current state...
 
IF mo_current_state IS INITIAL.
    transisition_to
( mo_system_resetter->mo_start_state ).
 
ELSEIF mo_system_resetter->is_reset_event( id_event_code ) = abap_true.
    transisition_to
( mo_system_resetter->mo_start_state ).
 
ENDIF.

* Now respond to the combination of the external event and the current state...
  lo_failed_condition
= mo_current_state->causes_check_after_event( id_event_code ).

 
IF mo_current_state->does_not_allow_event( id_event_code ) = abap_true.
   
"This combination causes an error every time
   
RAISE EXCEPTION TYPE zcx_sm_illegal_combination
     
EXPORTING
        io_illegal_combination
= mo_current_state->illegal_combination( id_event_code ).
 
ELSEIF lo_failed_condition IS BOUND.
    transisition_to
( lo_failed_condition->mo_new_state ).
   
CREATE OBJECT lo_illegal_combination
     
EXPORTING
        io_source_state 
= mo_current_state
        io_trigger_event
= lo_failed_condition->mo_trigger_event
        id_error_message
= lo_failed_condition->md_error_message.
   
"This combination causes an error only if the boundary condition has failed
   
RAISE EXCEPTION TYPE zcx_sm_illegal_combination
     
EXPORTING
        io_illegal_combination
= lo_illegal_combination.
 
ELSEIF mo_current_state->changes_state_after_event( id_event_code ) = abap_true.
    transisition_to
( mo_current_state->target_state_after_event( id_event_code ) ).
 
ENDIF.

ENDMETHOD.

METHOD causes_check_after_event.
* Local Variables
 
DATA: ls_conditions LIKE LINE OF mt_conditions.

 
READ TABLE mt_conditions INTO ls_conditions WITH KEY event_code = id_event_code.

 
CHECK sy-subrc = 0.

 
CHECK ls_conditions-condition->mo_trigger_event->md_code = id_event_code.

 
IF ls_conditions-condition->boundary_condition_fulfilled( ) = abap_true.
   
RETURN.
 
ENDIF.

  ro_failed_condition
= ls_conditions-condition.

ENDMETHOD.

 

Hopefully you can follow the above; I have tried to make the code read as much like natural language as possible. It has been said that the acid test is when you don’t need comments at all because it is so obvious what is going on.

 

So, the unit test passes, all is well. There are probably about twenty different ways to do this, I just picked the first one that came to me, I am not 100% happy with it, but it works and that’s what is important. If anyone wants to suggest a better way - please do so.

 

I did not need to add much extra code, but it did take a lot of thinking about. If I wanted to add another boundary condition I would have to do the following:-

-       Add another class to contain the logic of the boundary condition

-       In the configuration section create that object

-       In the configuration section add a line to the configuration of the state being changed

 

Procedural at your own risk

 

Now is the time to make the same change to the procedural version of the program. I don’t have to agonise over what goes in what class because in procedural programming you only have one hammer – the internal table – so everything is a nail.

 

TYPES: BEGIN OF g_typ_boundary_conditions,
         condition_name   
TYPE string,
         source_state     
TYPE string,
         event_name       
TYPE string,
         error_message    
TYPE string,
         new_state        
TYPE string,
      
END OF g_typ_boundary_conditions.

 

gt_b_conditions   TYPE STANDARD TABLE OF g_typ_boundary_conditions,
gs_b_conditions  
TYPE                   g_typ_boundary_conditions,

gd_gumball_stock  TYPE i      VALUE 30,

 

DEFINE state_needs_check_after_event.
 
clear gs_b_conditions.
  gs_b_conditions
-source_state   = &1.
  gs_b_conditions
-event_name     = &2.
  gs_b_conditions
-condition_name = &3.
  gs_b_conditions
-error_message  = &4.
  gs_b_conditions
-new_state      = &5.
 
append gs_b_conditions to gt_b_conditions.
END-OF-DEFINITION.

 

* Behaviour
* No Quarter State
  state_changes_after_event
:     'no_quarter' 'quarter_was_inserted' 'has_quarter'.
  state_needs_check_after_event
: 'no_quarter' 'quarter_was_inserted'
                                
'are_we_out_of_gumballs'
                                
'The machine has run out of Gumballs'
                                
'no_quarter'.
  state_errors_after_event
:      'no_quarter' 'crank_was_turned'
                                
'Please enter money before turning crank'.

 

This is the half way through stage and it is already obvious I need less code to achieve the same thing. The question has to be is this just because I don’t yet know enough about OO to do the OO change in a more efficient way?

 

The unit test is going to look very similar to the OO version, not surprisingly.

 

  METHOD not_sell_when_empty."     FOR TESTING.

* GIVEN....
    gd_gumball_stock
= 0.

* WHEN user tries to enter some money.....
   
PERFORM when_quarter_is_inserted.

* THEN_the system state does not change....
    cl_abap_unit_assert
=>assert_equals( act = gd_subrc
                                       
exp = 4
                                        msg
= 'Gumball Machine accepted money when machine was empty' ).

    cl_abap_unit_assert
=>assert_equals( act = gd_current_state
                                       
exp = 'no_quarter'
                                        msg
= 'Machine in incorrect state' ).

 
ENDMETHOD."Not Sell When Empty

 

Once again, let’s step through this to see what is going on.

 

FORM when_quarter_is_inserted .
 
READ TABLE gt_events INTO gs_events WITH KEY event_name = 'quarter_was_inserted'.
 
PERFORM handle_event USING gs_events-event_code.
ENDFORM.                    " when_quarter_is_inserted

 

Look at that – the system added the name of the FORM routine at the end of the routine without me having to type it myself. The day that SAP gets serious about OO programming I imagine they will introduce the same thing to local classes. However it has been fourteen years now, so I am not holding my breath.

 

FORM handle_event USING pud_event_code TYPE char04.
* Preconditions
 
CHECK pud_event_code IS NOT INITIAL.

 
READ TABLE gt_events INTO gs_events WITH KEY event_code = pud_event_code.

 
CHECK sy-subrc = 0.

 
PERFORM check_for_errors USING    gs_events-event_name
                                    gd_current_state
                          
CHANGING gd_subrc."Oh look, a return code!

 
CHECK gd_subrc = 0.

 
PERFORM check_for_conditions USING    gs_events-event_name
                              
CHANGING gd_current_state
                                        gd_subrc
.

 
CHECK gd_subrc = 0.

 
PERFORM transition USING    gs_events-event_name
                    
CHANGING gd_current_state.

ENDFORM.                    " HANDLE_EVENT

FORM check_for_conditions  USING    pud_event_name    TYPE string
                          
CHANGING pcd_current_state TYPE string
                                    pcd_subrc        
TYPE sy-subrc.

  pcd_subrc
= 0.

 
READ TABLE gt_b_conditions INTO gs_b_conditions
 
WITH KEY source_state = pcd_current_state
           event_name  
= pud_event_name.

 
CHECK sy-subrc = 0.

* Now we need to do the boundary condition check
 
CASE gs_b_conditions-condition_name.
   
WHEN 'are_we_out_of_gumballs'.
     
IF gd_gumball_stock LT 1.
        pcd_subrc
= 4.
     
ELSE.
        pcd_subrc
= 0.
       
RETURN.
     
ENDIF.
   
WHEN OTHERS.
      pcd_subrc
= 0.
     
RETURN.
 
ENDCASE.

 
IF pcd_subrc = 4.

    pcd_current_state
= gs_b_conditions-new_state.

   
PERFORM send_error_to_ext_system USING gs_b_conditions-error_message.

 
ENDIF.

ENDFORM.                    " CHECK_FOR_CONDITIONS

 

An object orientated person would start squawking that this is a clear breach of the “open closed” principle, since every time I add a new boundary condition I would have to change the above routine by adding some extra logic in a new branch of the CASE statement. A “case” statement is called a “switch” statement in Java, by the way, and I think SAP are introducing this into ABAP as well in version 7.4. They like Java. They think its tops. It’s rather like the Orangutan in “The Jungle Book” who wants to walk like you, talk like you etc.

 

I'm the king of the swingers, the Jungle ERP etc

 

You see it’s true-ooh-ooh,

An ABAP like me,

Can learn to be just like Java too (take me home daddy!)

 

I’m Judge Project Manager and you creeps are Under Budget and On Time

 

So what’s my verdict? That took a lot less thinking about than the OO equivalent, and a lot less time. The procedural program throws all the rules in books like “Clean Code” right into the dustbin. It has global variables all over the place, breaks the “open closed” principle etc etc.

 

I’m not much for rules, as assorted German managers would attest to, but they are there for a reason and when I am reading books like “Clean Code” all those rules make perfect sense. The question is – can they survive in the real world?

 

Anyway the aim of the game was to test the theory that OO programs give you the advantage that they are far easier to change than procedural ones. My experiment seems to prove the exact opposite. To be honest that is not what I was expecting. This is like the Michael Moorcock novel “Behold the Man” when he travels back in time to observe the events in the middle east around the time of Jesus because he wants the events described in the New Testament to be true.

 

This could be because the example programs are so simple and in real life we sometimes write mega-applications. In a blog it is very difficult to show two versions of a million line program. However as Jelena points out whilst I might be writing these gigantic complex programs a lot of ABAP programmers are writing GL upload programs or ALV reports to dump out the contents of BSEG.

 

So, I throw the question to the world – was this a sensible example? I stress this was not an example I made up, I got both the “gothic” and the “gumball” examples from famous articles/books. If I was making up the example then I could skew the result right from the start.

 

I’m still not going back to procedural programming though, I have become addicted to OO even if it is more difficult, it just seems right to me somehow, and a thing of beauty. I had hoped to back up this gut feeling with some empirical evidence as opposed to “everyone says this is good”. Alas alack, this seems to be beyond me. Am I forever doomed to be one of the many who sticks to an opinion regardless of any evidence presented to me that disagrees with my point of view? Even when I am the one who produced the evidence? Does that make me a madman? The giant rabbit in the corner of the room which only I can see thinks so.

 

And now for something completely different

 

This is going to seem like going down the world’s biggest rabbit hole – i.e. totally off topic – but something I read the other day seemed to have a lot of relevance to both the “domain specific language” concept and the example I was programming where you have a boundary condition where the exact logic is going to be different each time but you want to have a common framework.

 

On the SCN was this blog:-

 

http://scn.sap.com/community/abap/blog/2014/02/18/javascript-for-abap-developers

 

That was talking about how ABAP is an “imperative” language and Javascript being a “functional” language. Oh Grandma! What big words you use! “All the better to talk nonsense at you” said the Big Bad Wolf.

 

Functional languages are supposed to revolve around saying “how” you solve a problem as opposed to “what” you do to solve. That sounds like a trivial difference, but it ties in with the whole “domain specific language” concept of separating the behaviour from the mechanics of a program.

 

I found the example almost impossible to get my head round – once again this was something I felt in my gut was important but could not understand it – so I wrote a program in ABAP to try and simulate the Javascript example. Here it is!

 

This is all about a FIBONACCI sequence which is all about adding numbers to each other - another obscure example with no relevance to anything in the business world, just like all its friends.

 

REPORT  y_functional_test.

DATA: gd_result TYPE i.

*----------------------------------------------------------------------*
*       CLASS lcl_functional_programming DEFINITION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_functional_programming DEFINITION.
 
PUBLIC SECTION.
   
METHODS: do_fib IMPORTING number_to_add  TYPE i
                              result_so_far 
TYPE i
                              loops_left    
TYPE i
                    RETURNING
value(resultTYPE i,
             functional
IMPORTING loops_left    TYPE i
                        RETURNING
value(result) TYPE i.

ENDCLASS.                    "lcl_functional_programming DEFINITION

DATA: go_lcl_functional_programming TYPE REF TO lcl_functional_programming.

START-OF-SELECTION.
*--------------------------------------------------------------------*
* Imperative Programing
*--------------------------------------------------------------------*
 
PERFORM imperative USING    8
                    
CHANGING gd_result.

 
WRITE:/ gd_result.

*--------------------------------------------------------------------*
* Functional Programming
*--------------------------------------------------------------------*
 
CREATE OBJECT go_lcl_functional_programming.

  gd_result
= go_lcl_functional_programming->functional( 8 ).

 
WRITE:/ gd_result.

*&---------------------------------------------------------------------*
*&      Form  IMPERATIVE
*&---------------------------------------------------------------------*
FORM imperative  USING    pud_loops  TYPE i
                
CHANGING pcd_result TYPE i.

 
PERFORM imperative_fibonacci USING     0
                                        
1
                                        
8
                              
CHANGING  gd_result.

ENDFORM.                    " IMPERATIVE
*&---------------------------------------------------------------------*
*&      Form  imperative_fibonacci
*&---------------------------------------------------------------------*
FORM imperative_fibonacci  USING    pud_first_number    TYPE i
                                    pud_second_number  
TYPE i
                                    pud_number_of_loops
TYPE i
                          
CHANGING pcd_result          TYPE i.
* Local Variables
 
DATA: ld_number_to_add TYPE i,
        ld_result_so_far
TYPE i,
        ld_loops_left   
TYPE i.

 
CLEAR pcd_result.

  ld_number_to_add
= pud_first_number.
  ld_result_so_far
= pud_second_number.
  ld_loops_left   
= pud_number_of_loops.

 
WHILE ld_loops_left GT 1.

    pcd_result
= ld_number_to_add + ld_result_so_far.

    ld_number_to_add
= ld_result_so_far.

    ld_result_so_far
= pcd_result.

   
SUBTRACT 1 FROM ld_loops_left.

 
ENDWHILE.

ENDFORM.                    " imperative_fibonacci
*----------------------------------------------------------------------*
*       CLASS lcl_functional_programming IMPLEMENTATION
*----------------------------------------------------------------------*
*
*----------------------------------------------------------------------*
CLASS lcl_functional_programming IMPLEMENTATION.

 
METHOD do_fib.

   
IF loops_left = 1.
      result
= result_so_far.
     
RETURN.
   
ENDIF.

    result
= me->do_fib( number_to_add = result_so_far
                         result_so_far
= ( number_to_add + result_so_far )
                         loops_left   
= ( loops_left - 1 ) ).

 
ENDMETHOD.                                               "do_fib

 
METHOD functional.

    result
= do_fib( number_to_add = 0
                     result_so_far
= 1
                     loops_left   
= loops_left ).

 
ENDMETHOD.                                               "functional

ENDCLASS.                    "lcl_functional_programming IMPLEMENTATION

 

So what does this tell us? Firstly that you can pass in formulas as arguments using OO programming and you cannot do the same with FORM routines. However that is just a decision SAP made to try and push you into using OO programming. I am sure if they really wanted they could change the compiler to accept formulas as parameters in FORM routines, or optional entries come to that.

 

My understanding from the article was that “functional” programmers don’t like loops and WHILE blocks and prefer recursion. OK, but what I found strange was the blanket assertion that IF statements are bad because it somehow distracts someone reading the program.

 

Somehow this

 

var now = new Date();

 

var greeting = "Good" + ((now.getHours() > 17) ? " evening." : " day.");

 

is supposed to read more like proper English than

var now = new Date();
var greeting = "Good";
if (now.getHours() > 17)
   greeting += " evening.";
else
   greeting += " day.";

Things seem to go round in a circle. We moved from machine code to languages which read like English and then back to commands like “?”. Whenever I see code examples on the internet which read like “ X ?? Y > **Q => Z” I wonder if the programmers are trying to safeguard their jobs by making things appear more complicated than they really are.

 

Anyway, has anyone ever tried to pass a conditional expression into a method using ABAP? I have a vague me

mory there is a standard class for such a thing. If you could then maybe I could simplify my “boundary condition” class that I created earlier in the blog.

And now, the end is near, and so I face, the final curtain

 

This is going to be the last blog I write for a year I imagine, as from now on I will have to devote all my spare time to writing that book for SAP Press. I hope what I have written above contributes to the ongoing “procedural vs OO” debate. Some people have even said this debate is a complete waste of time because the “future” is coming towards us at one hundred miles an hour and that will all be in languages like Javascript and “River” and what have you. Well, we shall see, but I am not 100% convinced that gigantic corporations are going to throw out all the years of custom ABAP development they have paid so much money for overnight….

 

08 Turkey.jpg

This is the bush turkey that walks past our office in Brisbane each morning…

 

Cheersy Cheers

 

Paul

 

 

State Machine Part 3 : Team Procedural vs. Team OO Take 2

 

01 Dog Grenades.jpg

 

Table of Contents

 

Introduction

Pretend new user requirements

Change the OO Program

Change the Procedural Program

Verdict

Epilogue – Functional Programming

 

This is going to be a long, long blog…

 

Most of it will be ABAP code though, which makes it seem longer than it actually is… I also have the horrible feeling the formatting will vanish the instant I publish it. It look OK on the screen I am looking at, but it is always a bit of a roulette wheel how it looks once published, it certainly is not a WYSIWYG situation. I think all the gaps between the paragraphs will vanish.

 

The Story so Far….

 

A long long time ag, in a galaxy far, far away, Sumanth Kristam wrote the following blog:-

 

http://scn.sap.com/community/abap/blog/2014/01/09/classical-way-to-abap-oo-style-of-coding

 

and instantly the whole SAP programming world were at each other’s throats in a life and death struggle between procedural programing and object orientated programming.

 

I thought I would enter the fray, and as everyone was screaming out for examples I thought as an experiment I would write two versions of the same program, one in OO, one in a procedural style, and then see how easy it was to change them.

 

The OO version lives in this blog:-

 

http://scn.sap.com/community/abap/blog/2014/01/15/enemy-of-the-state

 

And the procedural one in this:-

 

http://scn.sap.com/community/abap/blog/2014/01/29/team-procedural-vs-team-oo

 

The both have SAPLINK nuggets so you can see the relevant code.

 

When I left you I was half way through - I can’t leave you hanging so I have to finish this off, and then I most likely won’t write many more blogs this year as I’ve got a book to write for SAP Press (I still can’t believe this is happening to me) - anyway the two programs were both fairly simple and did the exact same thing and thus far there is not much to choose between them, the procedural version is shorter and looks more straightforward (to me) and the OO one is following all the OO rules I see in all the books.

 

That’s the 10% of the software lifecycle where you create the program dealt with. The acid test is what happens during the 90% of the software lifecycle where you users decide they want more, more, more. To quote the Pointer Sisters, who did a lot of software development:-

 

Oh users, I'll take your requirements down, I'll take them down
Where no one's ever wanted this before
And then you want more, yes you want more, more, more

 

I’ll JUMP to change the code, JUMP IN , oh ho ho ho
JUMP if you want to add requirements
In the night then, JUMP, JUMP off we go…..

 

Head First for Knowledge

I mentioned in the earlier blogs that the original two programs were based on a Java example by Michael Feathers and were about a Gothic Security System. My original thought was if this is such a good re-usable framework I can re-use it in the Head First Design Patterns example, which is all to do with using the State Pattern to write a program to control a Gumball Machine.

 

So step one, is to copy both programs and then change what they do. This part should actually be quite trivial in both cases, as each one has the “what does the program do” isolated from the “how do I do it”.

 

I also mentioned in the prior blog there is no point going into detail about what the “Gumball” program is supposed to do, as the configuration section and unit tests should make this obvious. In essence a Gumball is a spherical piece of chewing gum, you put your “quarter” in the machine, turn the handle and out one pops.

 

Doctor Who and the Procedurals of Doom

 

I’ll do the procedural one first … the first thing is to change the “configure” part of the code….

 

FORM configure_gumball_machine .
* Events
  possible_events_are
:
 
'quarter_was_inserted'    'QWIN',
 
'eject_button_pressed'    'QWEJ',
 
'crank_was_turned'        'CWTN',
 
'ball_was_dispensed'      'BWDI',
 
'machine_was_refilled'    'MWRF'.

* Commands
  possible_commands_are
:
 
'dispense'      'DISP'.

* States
  possible_states_are
:
 
'sold',
 
'sold_out',
 
'no_quarter',
 
'has_quarter'.

* Behaviour
* No Quarter State
state_changes_after_event
:'no_quarter' 'quarter_was_inserted' 'has_quarter'.

* Has Quarter State
state_changes_after_event
:'has_quarter' 'eject_button_pressed' 'no_quarter',
                         
'has_quarter' 'crank_was_turned'    'sold'.

* Sold State
  state_reached_sends_command
: 'sold' 'dispense'.
  state_changes_after_event
'sold' 'ball_was_dispensed' 'no_quarter'.

* Sold Out State
  state_changes_after_event
: 'sold_out' 'machine_was_refilled' 'no_quarter'.

ENDFORM.                    " configure_gumball_machine

 

The idea is that this should read like natural language, so you should be able to guess what the program does from having a look at the above…. As might be imagined changing that did not take very long at all, and now I am half way through … I just need to rewrite the unit test.

That involved about ten “find / replace” commands. The end result is thus:-

 

LASS lcl_test_class IMPLEMENTATION.

 
METHOD setup.
   
PERFORM configure_gumball_machine.
 
ENDMETHOD.

*--------------------------------------------------------------------*
* Actual Test Methods
*--------------------------------------------------------------------*
 
METHOD sell_people_gumballs.
* GIVEN.....
   
PERFORM given_machine_has_gumballs.

* WHEN user excutes the steps in the correct order
   
PERFORM when_quarter_is_inserted.
   
PERFORM when_crank_is_turned.

* THEN_the user has been sold a gumball....
    cl_abap_unit_assert
=>assert_equals( act = gd_current_state
                                       
exp = 'sold' ).

 
ENDMETHOD.

ENDCLASS."Test Class Implementation

 

That was trivial … this framework can clearly be used by a totally different program with next to do effort.

I run the unit test to confirm all is working as expected.

 

Who is Mr.Orientated, and to what does he object?

 

I will know do the exact same thing with the OO version. I expected the change effort to be pretty much identical, as once again I am only changing the configuration and unit test sections.

 

This took almost exactly the same amount of time i.e. virtually none at all.

 

The fact that in both cases the changes were so easy is a testament to the “domain specific language” idea that Martin Fowler was trying to demonstrate in the first place. You don’t want to keep writing the “boiler plate” code again and again, you want to concentrate on what makes the program unique. This is the ever popular “separate the things that change from the things that stay the same”.

 

Here are some extracts from the OO Gumball program:-

 

* Events
    possible_events_are
:
    quarter_was_inserted   
'quarter_was_inserted'    'QWIN',
    eject_button_pressed   
'eject_button_pressed'    'QWEJ',
    crank_was_turned       
'crank_was_turned'        'CWTN',
    ball_was_dispensed     
'ball_was_dispensed'      'BWDI',
    machine_was_refilled   
'machine_was_refilled'    'MWRF'.

* Commands
    possible_commands_are
:
    dispense     
'Dispense'      'D1UL'.

* States
    possible_states_are
:
    sold_state       
'sold_state',
    sold_out_state   
'sold_out_state',
    no_quarter_state 
'no_quarter_state',
    has_quarter_state
'has_quarter_state'.

   
CREATE OBJECT system_resetter
     
EXPORTING
        io_start_state
= no_quarter_state.

    system_resetter
->add_reset_event( machine_was_refilled->md_code ).

* No Quarter State
    no_quarter_state
->state_changes_after( event          = machine_was_refilled
                                          to_target_state
= no_quarter_state ).
    no_quarter_state
->state_changes_after( event          = quarter_was_inserted
                                          to_target_state
= has_quarter_state ).

* Has Quarter State
    has_quarter_state
->state_changes_after( event          = eject_button_pressed
                                            to_target_state
= no_quarter_state ).
    has_quarter_state
->state_changes_after( event          = crank_was_turned
                                            to_target_state
= sold_state ).

* Sold State
    sold_state
->state_reached_sends_command( dispense ).
    sold_state
->state_changes_after( event          = ball_was_dispensed
                                    to_target_state
= no_quarter_state ).

* Sold Out State
    sold_out_state
->state_changes_after( event          = machine_was_refilled
                                        to_target_state
= no_quarter_state ).

And

METHOD sell_people_gumballs.

    given_machine_has_gumballs
( ).

* WHEN user excutes the steps in the correct order
    when_quarter_is_inserted
( ).
    when_crank_is_turned
( ).

    then_gumball_has_been_sold
( ).

 
ENDMETHOD.

 

I run the unit test; I can be sure the OO program works. That was the easy bit.

 

Like a dream alive, a reason, everything must change, everything must change

 

OK let’s inject some extra requirements into the equation. Some people have been saying recently that if you just write your program properly in the first place and then cross your fingers and hope REALLY HARD then that will stop the end users asking for extra things.

 

Does anyone want to say to me “at MY Company we write things correctly and there are never any change requests ever. The users take what they are given and are so filled with joy that everyone lives happily ever after” ?

 

I am going to make the assumption that sometimes there is a need to add something extra to a program; no matter how well written it was in the first place. Maybe that makes me a lunatic.

 

In this case the extra requirements take the following form – the example program in the Head First Design Patterns book has two extra requirements that the “Gothic” example did not need.

 

The first is that in the Gothic example secrecy was the order of the day. If a burglar was trying to break in, and did the steps that opened the safe in the wrong order the last thing you want to do is say “that was wrong, you need to do it like this”. With a Gumball machine and the like though if the user forgets to enter money before turning the handle to get a gumball you want to tell them what they did wrong.

 

In IT terms this means that if an event is triggered which is not on the list of events which do anything, sometimes – and only sometimes – you want to send a message to the external system so it can inform the user what they have done wrong. In the nineteen sixties that would have involved flashing a light or – if you were really lucky – displaying some sort of blocky text on an LED screen. Nowadays even fridges have video screens and passport photograph machines talk to you.

 

In the Gothic Security system the safe action (event) always caused the same reaction. In the Gumball program we need some conditional logic – sometimes – e.g. you cannot buy a gumball if the machine is sold out. In the formal documentation of the State Pattern this is known as a “boundary condition”.

 

Wimbledon Common Requirements

 

If these new requirements were deemed so unusual they were unlikely to never be repeated then modifying the new program would be the go. However, if, as in this case, you think to yourself “these are quite reasonable things to ask for, I bet in the future lots of new programs will want to do this sort of thing” then it is the time to add some new features to the framework.

 

OO and a Bottle of Rum

02 Pirates.png

I’ll change the OO framework first. I need to look at this from the point of view of the programmer who, in six months’ time, needs to add another illegal combination of current state and user triggered event to the program. I want them to do that in the “configure” section by adding another line to the area where macros are used to say in plain English what the program should do.

 

So, as well as:-

 

no_quarter_state->state_changes_after( event          = quarter_was_inserted


                                      to_target_state
= has_quarter_state ).

I want to add a line like:-

 

NO_QUARTER_STATE->RESPONDS_TO(  EVENT = CRANK_WAS_TURNED

                  WITH_ERROR = ‘Please enter money before turning crank’ ).

 

It’s not perfect English, but its close enough for government business. I’m working backwards here. I started with a macro – what would that macro do? The same as all the macro before it, it would trigger an event of the STATE class which in turn creates an object to handle the instruction you have just given it.

The “state changes” method creates a TRANSITION object. The immediate question comes up, should I enhance the transition object so that it sends out error messages when the wrong combination (as specified) is supplied? Or create a new class?

 

Well this is a tricky one, so as always I defer to the experts on a subject I am just learning about. So I turn to the definitive work on the subject “Advanced Mega-Principles of Paradigm Shifting Game Changing In-Memory Mobile Cloud Based Object Orientated Programming with Special Attention to Big Data” by UK boy band “One Direction”. I am sure every serious programmer has that on their bookshelves. I thought Boney M did a particularly good job of writing the foreword.

 

In the chapter entitled “Hey Now, Hey Now, Get Out of My Head” the 1D gurus advise that any given class has to do “that one thing” only. I could have sworn I had heard that principle somewhere else before, but anyway that’s clear enough.

 

The job of the “transition” class is to move the system from one state to another. The job of the class I am considering is to STOP the system moving from one state to another. Is that the same? Is that different? I could make arguments for both sides. I am going to say it is different.

 

I create a new class ZCL_SM_ILLEGAL_COMBINATIONS (after spending fifteen minutes agonizing over the name). Then I think – this is all to do with errors – should this be an exception class? However I think the idea is to separate code that works out if something is in error from the mechanism of declaring that an error has occurred from the code the deals with the error situation. That’s what it says in the “egg book” anyway.

 

http://www.sap-press.com/products/Enhancing-the-Quality-of-ABAP-Development.html

 

The State Machine framework I have been creating consists of really tiny classes that don’t do very much. This is line with Robert Martins’ “extract till you drop” approach of splitting up programs into tinier and tinier pieces until you split the atom. He got a lot of grief on the internet for suggesting this, so opinion is strongly divided.

 

03 Illegal Combinations.jpg

This is just a “data object” which to my mind is a structure with ideas above its station. However, unlike a structure it can be enhanced with behaviour at a future date if I so desire e.g. I can check for combinations of attributes I don’t like etc… I don’t want to do anything like that at the moment, but the point is, you never know what the future may bring….

 

Now I have this lovely new class, I need an attribute in the STATE class to store a table of them, and a method to add a new illegal combination, which will get called by the macro.

04 State Class.jpg

05 Responds to Method.jpg

Then I change my configuration routine in the main program as I wanted to do earlier:-

 

* No Quarter State
    no_quarter_state
->state_changes_after( event          = machine_was_refilled
                                          to_target_state
= no_quarter_state ).
    no_quarter_state
->state_changes_after( event          = quarter_was_inserted
                                          to_target_state
= has_quarter_state ).
    no_quarter_state
->responds_to( event      = crank_was_turned
                                  with_error
= 'Please enter money before turning crank' ).

 

I am only half way there. I have told the system how I want it to re-act, but it also needs to read back that instruction at the correct point. I add another method into my global “state” class:-

 

METHOD does_not_allow_event.

 
READ TABLE mt_illegal_combos TRANSPORTING NO FIELDS
 
WITH KEY event_code = id_event_code.

 
IF sy-subrc = 0.
    rf_this_causes_an_error
= abap_true.
 
ELSE.
    rf_this_causes_an_error
= abap_false.
 
ENDIF.

ENDMETHOD.

 

Now I will create a good old exception class, ZCX_SM_ILLEGAL_COMBINATION, which takes in the “illegal combination” object. This is obviously overkill, but I am trying to do this the “right” way according to all the books on the subject.

 

I now adjust the method in the “controller” which responds to an inbound event coming in from the outside world:-

 

METHOD handle_inbound_event.
* Preconditions
 
CHECK id_event_code IS NOT INITIAL.

* Make sure we actually have a current state...
 
IF mo_current_state IS INITIAL.
    transisition_to
( mo_system_resetter->mo_start_state ).
 
ELSEIF mo_system_resetter->is_reset_event( id_event_code ) = abap_true.
    transisition_to
( mo_system_resetter->mo_start_state ).
 
ENDIF.

* No respond to the combination of the external event and the current state...
 
IF mo_current_state->does_not_allow_event( id_event_code ) = abap_true.
   
RAISE EXCEPTION TYPE zcx_sm_illegal_combination
     
EXPORTING
        io_illegal_combination
= mo_current_state->illegal_combination( id_event_code ).
 
ELSEIF mo_current_state->changes_state_after_event( id_event_code ) = abap_true.
    transisition_to
( mo_current_state->target_state_after_event( id_event_code ) ).
 
ENDIF.
ENDMETHOD.

 

This is all easy and straightforward isn’t it? I don’t know what all the fuss is about. I am still not there yet, I have decided that there is an error, and I have shouted out that there is an error, that just leaves the third part, which is doing something about it.

 

I am sending out the error message to the external system, so I need to modify the external system interface.

 

06 External System Interface.jpg

At long last it is time to put the cherry on top of the cake. I need to tell the “main” routine in the controller to respond to the exception thrown.

 

  METHOD run_gumball_machine."of class gumball_machine
* Local Variables
   
DATA: lo_external_system    TYPE REF TO lcl_external_system,
          lo_controller         
TYPE REF TO zcl_sm_system_controller,
          lo_error             
TYPE REF TO zcx_sm_illegal_combination,
          ld_inbound_event_code 
TYPE string.

   
EXIT."To stop anybody actually running this test program!
   
CREATE OBJECT lo_external_system.
   
CREATE OBJECT lo_controller
     
EXPORTING
        io_external_system
= lo_external_system
        io_system_resetter
= me->system_resetter.

   
WHILE ld_inbound_event_code NE 'STOP'.
      ld_inbound_event_code
= lo_external_system->poll_for_event( ).
     
TRY.
          lo_controller
->handle_inbound_event( ld_inbound_event_code ).
       
CATCH zcx_sm_illegal_combination INTO lo_error.
          lo_external_system
->send_error_message( id_event_code    = lo_error->io_illegal_combination->mo_trigger_event->md_code
                                                  id_error_message
= lo_error->io_illegal_combination->md_error_message ).
     
ENDTRY.
   
ENDWHILE.

 
ENDMETHOD."Run Gumball Machine

 

Did I mention I love this sort of thing? All the team procedural people will be looking on in horror, but take it from me, this OO stuff becomes addictive really quickly. Mind you, so does crack cocaine apparently, so that is not a wonderful analogy, but there you go.

 

How do I know this works? I need to add a unit test.

 

  METHOD not_sell_to_poor_people."for testing

    given_machine_has_gumballs
( ).

* WHEN user tries to turn the crank before entering money...
   
TRY.
        when_crank_is_turned
( ).

     
CATCH zcx_sm_illegal_combination.
       
"This is what I am expecting
       
RETURN.
   
ENDTRY.

    cl_abap_unit_assert
=>fail( msg = 'Gumball Machine sold gumballs with no money entered' ).

 
ENDMETHOD."Not Sell to Poor People

 

That works perfectly so that part is done. That was nice and easy (???) so let’s see how this stacks up to making the same change in the procedural program.

 

It’s the Same Old Song

 

In exactly the same way, I start by needing a new macro in the “configuration” routine in the program. True to the “domain specific language” principle, this is going to look pretty much the same as the OO version.

 

* Behaviour
* No Quarter State
  state_changes_after_event
: 'no_quarter' 'quarter_was_inserted' 'has_quarter'.
  state_errors_after_event
'no_quarter' 'crank_was_turned'
                           
'Please enter money before turning crank'.

 

Just like with TDD I am working backwards here. I have not even written the macro yet, let alone any actual code to do what I want. So, the macro comes next, it’s all global variables here, I don’t need to worry about the philosophical considerations about class design that plagued me in the last example.

 

DEFINE state_errors_after_event.
 
clear gs_illegal_combos.
  gs_illegal_combos
-source_state  = &1.
  gs_illegal_combos
-event_name    = &2.
  gs_illegal_combos
-error_message = &3.
 
append gs_illegal_combos to gt_illegal_combos.
END-OF-DEFINITION.

 

I then define a global type, then structure, then table, then my program compiles. So far so good.

 

Now I move onto the actual code to handle errors I no longer have to walk backwards for Christmas, in procedural world you can declare the procedure you want, then double clik on it and a skeleton is produced – what WILL they think of next?

 

FORM handle_event USING pud_event_code TYPE char04.
 
* Preconditions
 
CHECK pud_event_code IS NOT INITIAL.

 
READ TABLE gt_events INTO gs_events WITH KEY event_code = pud_event_code.

 
CHECK sy-subrc = 0.

 
PERFORM check_for_errors USING    gs_events-event_name
                                    gd_current_state
                         
CHANGING gd_subrc."Oh look, a return code!

 
CHECK gd_subrc = 0.

 
PERFORM transition USING    gs_events-event_name
                   
CHANGING gd_current_state.

ENDFORM.                    " HANDLE_EVENT

*&---------------------------------------------------------------------*
*&      Form  CHECK_FOR_ERRORS
*&---------------------------------------------------------------------*
* See if the event triggered by the user is not allowed for the
* current state the system is in
*----------------------------------------------------------------------*
FORM check_for_errors  USING    pud_event_name    TYPE string
                                pud_current_state
TYPE string
                     
CHANGING pcd_subrc        TYPE sy-subrc.

  pcd_subrc
= 0.

 
READ TABLE gt_illegal_combos INTO gs_illegal_combos
 
WITH KEY source_state = pud_current_state
          event_name 
= pud_event_name.

 
CHECK sy-subrc = 0.

  pcd_subrc
= 4.

 
PERFORM send_error_to_ext_system USING gs_illegal_combos-error_message.

ENDFORM.                    " CHECK_FOR_ERRORS
*&---------------------------------------------------------------------*
*&      Form  SEND_ERROR_TO_EXT_SYSTEM
*&---------------------------------------------------------------------*
FORM send_error_to_ext_system USING pud_error_message TYPE string.
* Code to send out an error message to be displayed by an external system
* Most likely a proxy call to PI, which is of course OO, but there is no
* escape from some things...
ENDFORM.                    " SEND_ERROR_TO_EXT_SYSTEM

 

This appears to be going faster because I am doing this all within one program … but hang on, am I not comparing apples with oranges? When I did the OO version I had all Z repository objects. The reason is, there is virtually no support for local classes in the ABAP editor, so the fastest way to proceed OO wise is with global classes.

 

Lastly, it is unit test time, to prove this works.

 

CLASS lcl_test_class DEFINITION FOR TESTING
  RISK LEVEL HARMLESS
  DURATION SHORT
  FINAL
.

 
PRIVATE SECTION.
   
METHODS: setup,
           
"IT SHOULD.....................
            sell_people_gumballs   
FOR TESTING,
            not_sell_to_poor_people
FOR TESTING.

ENDCLASS."Test Class Definition

  METHOD not_sell_to_poor_people.

* GIVEN.....
   
PERFORM given_machine_has_gumballs.

* WHEN user tries to turn the crank before entering money...
   
PERFORM when_crank_is_turned.

* THEN_the system state does not change....
    cl_abap_unit_assert
=>assert_equals( act = gd_subrc
                                       
exp = 4
                                        msg
= 'Gumball was sold with no money entered' ).

 
ENDMETHOD."Not sell to poor people

 

The unit test passes, all is well.

 

I’m Judge Dredd, and You Creeps are Under Arrest

 

So, that is the first change made, what’s the verdict? It was certainly faster to change (enhance) the procedural version. However the OO version seems to lend itself better to reading more like natural language (which is one of the main aims of the game) and the error handling, which some would describe as more cumbersome, does tend to convey a clearer idea of what actually went wrong.

 

I will break off here and finish off in s separate blog.... I have already written this, so it won't be long in coming...

 

 

Hi SCN community!

 

Ever wanted to get the line in a table where the max value for a certain field was? I had to last week, and I remembered just how awful it was to do it. Declare an auxiliary table of the same structure, copy the entire contents, sort for the right field, get the first line, correct?

 

I thought that there had to be a better way to do this, and I searched for it, and couldn't find it. And I was actually challenged by Manish Kumar to create a class for this, and so I did.

 

What I'm sharing is a simple class for internal table manipulations. So far it has 5 methods:

  • GET_MAX_LINE - Get the line with the maximum value for a certain column
  • GET_MIN_LINE - Get the line with the minimum value for a certain column
  • GET_MAX_VAL - Get the maximum value for a certain column
  • GET_MIN_VAL - Get the minimum value for a certain column
  • GET_AVG_VAL - Get the average value for a certain column

 

I'm trying to set up github so that we can all work on this together, hopefully we will be able to work on other utility classes like this one.

 

EDIT: Github successfully set up! You can follow this project here:

EsperancaB/sap_project_object · GitHub

 

Thanks!

 

For revision 3 I have changed the exception throwing strategy (much cleaner now) and taken Shai Sinai's suggestion of using the component's name instead of looking for its index. GREAT catch!!

 

If you have any suggestions or contributions please feel free to join

 

Best,

Bruno

Previously we have discussed how to generate QRcode with SAP standard functionality:

 

1. Generate QRcode with ABAP Webdynpro

2. Generate QRcode with ABAP Code plus Adobe form template

 

Both solutions will generate QRCode within a PDF file. If your requirement is to generate the QRCode in a picture file, you can use the solution described in this blog.

 

There are lots of websites which provide free QRCode generation service. Just google it:

clipboard1.png


I prefer to use this one http://www.qrstuff.com: just choose "Plain Text" and we can input some characters in Text field. There is a AJAX call which will generate QRCode immediately after some characters are typed.

clipboard2.png

Now question is how we automate the character input via ABAP.

 

I use Chrome to explore what has happened when we input some char in webpage. Click F12 to launch Chrome developer tool, input some char and observe the information displayed in the "Network" tab:

clipboard3.png

The request URL is found: http://www.qrstuff.com/generate.generate

 

The request method is GET.

 

Also it is necessary to identify http form fields and they are also listed in Chrome:

clipboard4.png

We need to copy all of those form fields into our ABAP code except preview=1, since we don't need the preview functionality, we just need the generated png file.

 

here below is the simple report to generate QRCode from hard coded string "testforQRCode". ( The bold text are just copied from Chrome developer tool )

The source code of zcl_http_tool could be found from attachment.

 

DATA:
     ls_form   type zcl_http_tool=>ty_name_pair,
     lt_form   type zcl_http_tool=>tt_name_pair,
     lv_code   type xstring.
ls_form = value #( name = 'type' value = 'TEXT' ).
APPEND ls_form TO lt_form.
ls_form = value #( name = 'foreground_color' value = '000000' ).
APPEND ls_form TO lt_form.
ls_form = value #( name = 'ecc_level' value = 'L' ).
APPEND ls_form TO lt_form.
ls_form = value #( name = 'width_pixels' value = '200' ).
APPEND ls_form TO lt_form.
ls_form = value #( name = 'dpi' value = '72' ).
APPEND ls_form TO lt_form.
ls_form = value #( name = 'file_type' value = 'png' ).
APPEND ls_form TO lt_form.
ls_form = value #( name = 'text' value = 'testforQRCode' ).
APPEND ls_form TO lt_form.
zcl_http_tool=>send_request( EXPORTING iv_url      = 'http://www.qrstuff.com/generate.generate'
                                       iv_req_type = if_http_request=>co_request_method_get
                                       it_form     = lt_form
IMPORTING ev_response = lv_code ).


And this is QRCode scanned from my mobilephone:

2c0e3e5ff0c6eded33e3f498631b0c95.jpg

Uwe Kunath

No comment!

Posted by Uwe Kunath Feb 23, 2014

Are Comments overrated?

Comments are a wonderful tool to document what you intended with certain program logic. You can place it directly where that logic resides: In the source code. As comments are non-functional, they are not getting compiled and hence, not executed. However, I’ve got the feeling that sometimes we should really think about how to use comments and how not to use them. These points are neither fundamentally new nor innovative to developers at all, but I still have the feeling, that these are the most commonly violated principles.

 

Antipattern #1 – Comments do not replace program structure

Usually, developers should tell the reader of their code why they are doing something, not what they are doing. Before you start to comment by describing what the next few lines of code should do, stop and think about it.

Consider the following example. What do you think would be better to read?

 

*   parse header from excel
    lv_row = 2.
    lv_column = zcl_excel_common=>convert_column2int( lv_range_start_column ).
    lv_current_column = 0.
    WHILE lv_column <= lv_highest_column.
      lv_current_column = lv_current_column + 1.
      READ TABLE mt_fcat INTO ls_fcat INDEX lv_current_column.
      CHECK sy-subrc = 0.
      lv_col_str = zcl_excel_common=>convert_column2alpha( lv_column ).
      io_excel_worksheet->get_cell(
        EXPORTING
          ip_column = lv_col_str
          ip_row    = lv_row
        IMPORTING
          ep_value = lv_value
      ).
      ASSIGN COMPONENT ls_fcat-fieldname OF STRUCTURE ls_scale TO <lv_value>.
      CHECK sy-subrc = 0.
      CASE ls_fcat-inttype.
        WHEN 'D'.
          <lv_value> = zcl_excel_common=>excel_string_to_date( lv_value ).
        WHEN OTHERS.
          <lv_value> = lv_value.
      ENDCASE.
      lv_column = lv_column + 1.
    ENDWHILE.
    lv_column = zcl_excel_common=>convert_column2int( lv_range_start_column ).
    lv_row = lv_row + 1.
*             …Some more coding…
    lv_highest_row = io_excel_worksheet->get_highest_row( ).
*   parse items from excel
    WHILE lv_row <= lv_highest_row.
      lv_column = 2.
      lv_col_str = zcl_excel_common=>convert_column2alpha( lv_column ).
      io_excel_worksheet->get_cell(
        EXPORTING
          ip_column = lv_col_str
          ip_row    = lv_row
        IMPORTING
          ep_value = lv_value
      ).
      lv_catyp = lv_value.
      lv_column = 1.
      lv_col_str = zcl_excel_common=>convert_column2alpha( lv_column ).
      io_excel_worksheet->get_cell(
        EXPORTING
          ip_column = lv_col_str
          ip_row    = lv_row
        IMPORTING
          ep_value = lv_value
      ).
*                           some more coding…
      lv_row = lv_row + 1.
    ENDWHILE.

Basically, the comments tell me what the next few lines of code will do. But there is no benefit. You just don’t understand immediately, what the code does, no matter if there are comments available or not.

Even worse, more comments would not make clear what the code does. Furthermore, as you shift code to another part of your program, the comments might lose their relationship to the code – as they are non-functional and not taken into account by the compiler. This will eventually lead to comments that are misleading users in the best case.

Consider this refactored example:

 

    parse_header_from_excel( io_excel_worksheet = io_excel_worksheet io_scale = ro_scale ).
    parse_items_from_excel( io_excel_worksheet = io_excel_worksheet io_scale = ro_scale ).

Actually, replacing comments by special methods with a useful name does not reduce complexity – but it increases readability dramatically.

By the  way, that’s why I think that code scanners which measure the ratio of comments and source code are useless – as the goals that they proclaim to address cannot be addressed by such simple measurements.

Normally, I try to produce code which has no comments in the middle of some logical program unit like a method. If there are comments, they are at the top of the code – or nowhere.

 

Antipattern #2 – Commented code

Having code that is getting useless by the time will lead to the end of its lifecycle. Usually most of the developers just comment this piece of code out. How often have they ever read this program logic again?

I personally do not read it as it might lose track of its relationship to the surrounding source code by the time. My eyes just went over these comments. Whenever I need to delete some coding that I might miss in the future, I generate a new version of the ABAP source code and delete the code. Code Versioning systems are meant for this kind of tasks. When I miss something in the code, I take a look at the versions – This immediately allows me to compare two versions by the changes that have been applied in the meantime.

Hi, please be gentle, as I dare to enter my first blog-post.

 

The following might be a bit simple and child knowledge for you all, but I wish someone would have told me this little trick, when I first started developing Smartforms.

 

I used to get this question a lot: “Martin – we have a problem with the invoice print, and we can’t re-create the error in the development system. Can you please debug and find the error in the production system?”

 

I really needed to create a session break-point directly into the Smartform in the production system, but how? – where was the button in the smartforms transaction?.No way was I going to create a transport request with the smartform holding a coded breakpoint.

 

Sure I could go to VF03 and use ‘/h’, but that is kind of a tedious way to go.

 

Instead I did this:

 

     1. Go to Smartforms transaction and type in the smartform name and hit “test”. Then you get the function module name that triggers the smartform print in      the given system.

 

01.png

 

     2. Copy the FM name to clip holder and go to SE80. 

 

02.png

 

     3. Select program and paste the name in.

 

03.png

 

 

4. Put an ‘L’ just before the last name and end it with a ‘F01’ and hit enter.

 

    04.png 

 

You now get the include, that holds all form routines representing every node in the smartform. Search for the one you need, and create your session breakpoint in the productive system.

 

(Global initialization in the smartform is in subroutine ‘GLOBAL_INIT)

 

Please note that this does not work for all smartforms but in my experience most SD forms can be debugged this way in productive and QA-systems.

 

/Martin

Calculate Dunning charges through BTE ‘00001071’ at the time of Dunning(Tcode-F150)

This is the scenario regarding calculate dunning charges and posting in customer line item. This calculates the charges of dunning on the basis of the dunning levels of the customers.

Following is the job log when we Dunn the customer through the transaction code F150.

Dun1.png

We can see the posted dunning charges for first level in FBL5N transaction code.

Dun2.png

This is implemented by using the BTE ‘00001071’. Following is the enhancement details.

Got o transaction code FIBF. Then Settings->Products->of a customer. Suppose we created product here as ZDUNN.

Dun3.png

 

 

Then Go to again settings->process modules->of a customer .And activate the BTE.

Create function module by coping ‘SAMPLE_PROCESS_00001071’ to Z_EVENT_001071

So assign the product ZDUNN to above created FM. This is shown as below:

Dun4.png   

Write the below code in function module Z_EVENT_001071

 

DATA : l_is_documentheader   TYPE bapiache09,                "Document Header
         l_is_customer        
TYPE bapiacar09,
         l_it_customer        
TYPE TABLE OF bapiacar09,  “account relievable
         l_is_currencyamount  
TYPE bapiaccr09,
         l_it_currencyamount  
TYPE TABLE OF bapiaccr09,  "Currency amount
         l_is_accountgl       
TYPE bapiacgl09,
         l_it_accountgl       
TYPE TABLE OF bapiacgl09,
         l_it_return          
TYPE TABLE OF bapiret2,  "Bapi return Messages

"Fixed G/L Account
    l_gl_account =
'123456789'.

   
"Conversion for the G/L account
   
CALL FUNCTION 'CONVERSION_EXIT_ALPHA_INPUT'
     
EXPORTING
       
input  = l_gl_account
     
IMPORTING
       
output = l_gl_account.

"Filling the structure of BAPI_ACC_DOCUMENT_POST
   
"Document header
    l_is_documentheader-bus_act    =
'RFBU'.
    l_is_documentheader-username   = sy-uname.
    l_is_documentheader-header_txt =
'Charges'.            
    l_is_documentheader-comp_code  = i_mhnk-bukrs.
    l_is_documentheader-doc_date   = i_mhnk-laufd.
    l_is_documentheader-pstng_date = i_mhnk-laufd.
    l_is_documentheader-doc_type   =
'XY'.

    l_item_count             = l_item_count +
1.

   
"Acount Recievable (Customer)
    l_is_customer-itemno_acc = l_item_count.
    l_is_customer-customer   = i_mhnk-kunnr.
    l_is_customer-comp_code  = i_mhnk-bukrs.
   
APPEND l_is_customer TO l_it_customer.

   
"Currency Amount
    l_is_currencyamount-itemno_acc = l_item_count.
    l_is_currencyamount-
currency   = i_mhnk-waers.
    l_is_currencyamount-amt_doccur = c_mhngh.
   
APPEND l_is_currencyamount TO l_it_currencyamount.

    l_is_currencyamount-itemno_acc = l_item_count +
1.
    l_is_currencyamount-
currency   = i_mhnk-waers.
    l_is_currencyamount-amt_doccur = -
1 * c_mhngh.
   
APPEND l_is_currencyamount TO l_it_currencyamount.

   
"GL Account
    l_is_accountgl-itemno_acc =  l_item_count +
1.
    l_is_accountgl-gl_account =  l_gl_account.
    l_is_accountgl-pstng_date = sy-datum.
   
APPEND l_is_accountgl TO l_it_accountgl.


   
" Document posting
   
CALL FUNCTION 'BAPI_ACC_DOCUMENT_POST'
     
EXPORTING
        documentheader    = l_is_documentheader
     
TABLES
        accountgl         = l_it_accountgl
        accountreceivable = l_it_customer
        currencyamount    = l_it_currencyamount
       
return            = l_it_return.

 

DELETE ADJACENT DUPLICATES FROM l_it_return COMPARING type id number
                                                       message_v1 message_v2
                                                       message_v3 message_v4.


   
LOOP AT  l_it_return INTO l_is_return.
     
IF l_is_return-type EQ 'S'.
       
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
         
EXPORTING
           
wait = l_c_x.

     
ELSEIF l_is_return-type EQ 'E' OR l_is_return-type EQ 'A'.
       
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
       
"MESSAGE 'Error in posting the document' TYPE 'E'.
     
ENDIF.
   
ENDLOOP.

 
ENDIF.

In the Application Performance Engineering, all phases are equally crucial right from identifying the performance test scenario to the Analysis & tuning. Performance improvement is normally an iterative process until the product reaches a stable performance state abiding by all the performance standards.

     1. Identifying what to test

In this phase, if we do not identify the precise performance test scenarios the complete cycle will focus on inappropriate areas for performance tuning. In this step we need to establish the performance tuning goals and performance baseline. The difference between both is the performance gap which the process is targeted to eliminate.
While choosing the performance test scenario, we should also consider that different business scenarios would involve different portions of program code. If performance test case is not related to performance issue, then the “bad” code would not be traced. So it is critical to performance tuning process that a right performance test case is identified.

     2. Performance tracing & Measurements

In this phase, with the help of performance tools, measurements & tracing for the scenario is done. The various traces and tools available are, ST30 for capturing the statistical data from STAD, ST05 trace for capturing SQL, RFC, Buffer, HTTP, Enqueque trace, SE30/SAT for the ABAP Trace.

While tracing any scenario, you should first execute the scenario twice or thrice in order to eliminate the buffering impact on performance.
If the scenario is too time consuming, you should do several traces at different time during the test execution instead of one trace.

     3. Identifying Performance Issues & tuning opportunities

In this phase we need to analyze performance traces to identify the violations in ABAP logic or SQL operation
To pin point the problem in ABAP coding, we can analyze the SE30 ABAP trace, the major performance issues which causes the high CPU time are mostly inappropriate implementation of Internal tables either in terms of loop or read or sort.

If the DB Time of your application is high, we can refer to the SQL trace for getting to the root cause of the issue.

3.1 Analysis Steps for DB Time Analysis

      3.1.1 Identical Selects:

Get into the ST05 trace and summarize it by SQL statements, now sort descending the column for identical selects. All the statements with identical selects value greater than zero can be the candidate for performance tuning.

1.png

The various issues due to which identical selects occur

     1. Buffering not allowed on the table, solution can be to buffer the table according to the scenario, if this is not possible we could           implement Buffer Module. Also, if the same data is fetched again and again we could append the data into an internal table on the first           access to the data base and later read it from the internal table.

     2. Buffering is allowed but bypassed, there are certain queries which bypass the buffer and hit the data bases. For eg.[refer the list below].

2.png

     3. Buffering allowed but there is not data fetched by where clause and thus no records are found in the buffer and database table is           accessed multiple times. Solution is to implement a No found buffer.


3.1.2 Buffer Bypass:

Arrange the column BfTp in descending order; the statements on the tables with buffering switched off will have this column as blank, if otherwise the statements are the candidate for performance optimization. Here we need to analyze why the buffers are bypassed and what can be done to avoid the same.

3.png

3.1.3 Proper Indexes

Arrange the Summarized SQL statements in descending order of time/execution.


4.png

 

If the time/execution of the statement is greater than 10ms, it can be the candidate for performance tuning. The various issues due to which this occurs:

     1. No index is present on the table, which a where clause can use. Solution: if possible a new index needs to be created, or we can re-write           the where clause in order to use the existing index.

     2. The index is available, but the statement is written in such a way that is doesn’t use the index. For. Eg. A query with order by a,x,c           whereas the table has an index a,c,x; such a query will not use the index . Solution: is to re-write the query in such a way it uses the           index.

     3. Insert/Update statements on a few tables might take a lot of time, which may be because the table has too many indexes and the           time/execution also includes the time to update these indexes.


3.2 Analysis Steps for CPU Time Analysis

If the CPU time for the application is too high, the analysis can be done using the ABAP trace (SAT/SE30),

 

3.2.1 ABAP trace (SAT/SE30),

The SAT Traces help to identify hot spots in an application. This can be done by getting into the trace and sorting in descending order by net time. Here you may encounter issues due to

  1. Expensive operations on internal tables
  2. Unnecessary calls to processing blocks or entire code branches
  3. Long running modularization units
  4. Nested loops

 

3.2.2 Volume Scalability Single User Check

The major performance issues occur in an application when it handles large volume of data, (Customer like data), such huge data volume scenarios are generally difficult to simulate and analyze for the flow of data.
But, the SAT Runtime Analysis tool provides the option of testing the scalability of the application without creating huge volume of data. This is done by comparing the traces of the same scenario where different amount of data is handled.

 

5.png

This tool displays the comparison of various statements in both traces with respect to data processed in the statement/call and the net time taken by it, if both of these are in the same ratio then the application is scalable else that statement/call is the candidate for performance tuning.

 

Mostly these issues are due to the erroneous implementation of the internal table for e.g. sorting each time after append, using different & multiple sort order for an internal table etc.., which can be easily analyzed using the debugger.

 

3.3.3 Roundtrips & Network Delay

If the application has roundtrips of more than 2, the CPU time as well as response time would be high depending upon the location of your test system & server, due to network delay.

In SAT trace you can analyze the same, the net time of event RFC wait would be high and could be located in the SAT trace, or else the flush call could be searched in the trace.

Such calls directly for the application programs should be avoided.

 

4. Performance Tuning, Re-test & Analysis


In this phase the solutions need to be implemented to improve the performance.
If testing shows that program performance with the change is still short of expectation, you have to repeat the whole process again. It is important that we should execute the same test scenario and in the same SAP system with exactly same data before and after the change. Using different testing box is just adding another variable which makes performance measure harder.

SAP transaction SCI known as “SAP Code Inspector” can be used to scan through ABAP code to identify common performance pitfalls like SQL statement without where-clause etc. This is a static performance check. Output from this can be a part of improvement proposal together with what from trace analysis. Static code check is relatively simple is a good habit to use SCI.

I was recently debugging a sales order user exit and wanted to see data present in all the internal tables at that point.

Since all internal tables had header line, their records count was not visible in Variable Fast Display of New Debugger.

As a result, it was inconvenient to double click on 226 internal tables only to find that most of them are empty.

 

This blog will cover:

  1. How to filter the internal tables' names from a huge list of variables in New Debugger.
  2. How to easily see the record count of a given list of internal tables (esp. the ones with header line)

 

Scenario demonstration

A breakpoint is set in USEREXIT_READ_DOCUMENT of program MV45AFZZ and a sales order is displayed.

Navigate to Variable Fast Display > Globals tab in New Debugger.

 

 

 

The table icon in second column indicates that it is internal table.

We can't know whether the table has any record by just looking at Globals tab.

To see the number of records, we need to suffix the internal table names with [].

 

 

 

Entering the names manually is time consuming, adding the suffix is even more time consuming.

 

 

 

Filtering internal tables from global variables list

We can first sort the globals list so that all internal tables are together.

There is no sort button visible, but it can be done by clicking on settings button on right side.

 

 

 

Select the second column, click on settings and do sort.

 

 

 

The sorted list now shows 20+ items at a time.

Since there were 226 internal tables, Block selection (Ctrl+Y) and PageDown cycle wasn't so good to copy the names.

So, I used same settings button again to save the list to local spreadsheet.

We now have the list of internal tables, but they need to be suffixed with [].

 

 

 

Adding suffix to a list

I used Notepad++ to select every line using (.*) and regex replaced it with \1[] to add [] to all names.

However, MS Excel can also be used to do it easily using concatenation.

Just like ABAP has concatenate and && operator, MS Excel has & operator to join values.

Below screenshot shows the formula used to add suffix. Column A has the list, Column B has [].

Column C has formula =A[RowNumber]&B[RowNumber].

 

 

 

If I didn't know concatenation operator, I would have pasted Column A and B to Notepad and replaced TAB character with nothing.

 

Variables 1 tab will be able to show records for this data.

 

 

 

Unfortunately "paste from clipboard" option is not present in Variables 1 tab.

The visible area allows me to display only 25 variables at once.

I copied the suffixed names in batches of 25 to see the records.

This method may seem lengthy, but it is better than double-clicking on 226 internal table names.

An easter egg always is a nice way to let others enjoy your programming. But placing evil hacks inside the code can be slightly too-much:


evil_hack.png

 

And for those who think that all this internet stuff is eternal, here comes a warning:

close_internet.jpg


Thanks God there is no commit after the internet closing. Last one turns the lights off.

Calling all ABAP developers who need/want to learn JavaScript...

 

In Kevin Small's excellent blog he informed ABAP developers that:

a) Its OK to take other programming languages seriously   :-)

b) JavaScript is one such language

c) You really need to learn this language

 

So for those ABAPers who now want to take the next step and plunge into the low-level details of the language, I have revamped and released some training slides I wrote about a year ago.

 

One of the main conceptual differences ABAPers will need to understand is that fact that JavaScript is a highly dynamic language!  This then requires you to think in a completely different manner about how you construct your software.  I attempt to explain the differences in a step-by-step manner, without asking you to make any leaps of understanding.

 

These slides cover the JavaScript language from the ground up and have been designed with the assumption that the reader has no prior knowledge of the language.  You will be guided in gradual steps from the simplest concepts of language syntax and data types, right up to advanced topics such as creating prototype chains and the use of the functional programming style (as opposed to the more familiar imperative programming style used by ABAP)

 

Chapter 1: Introduction

Chapter 2: Data Types

Chapter 3: Syntax

Chapter 4: Scope

Chapter 5: Functions

Chapter 6: Inheritance

Chapter 7: Functional Programming

 

Since these slides are focused only on the JavaScript language itself, they do not cover the use of JavaScript within the specific context of a browser (E.G. DOM programming and event handling are not covered); neither are JavaScript frameworks such as jQuery, Sencha or SAPUI5 covered. These subsequent topics should be addressed only after you have built a solid foundation in the language itself.  For instance, once you have gone through these slides, you will be completely ready to start SAPUI5 training.

 

All seven chapters are contained in this ZIP file in PowerPoint SlideShare format.  Because the low level details of learning a language can be rather dry, I've taken a somewhat tongue-in-cheek approach and thrown in a few amusing comments and asides just to lighten things up.  :-)

 

Unfortunately, I have had no time to create any exercises to accompany these slides; however, if you open the Chrome or Firefox browser and then open the Developer Tools, you will have access to a JavaScript console in which you can execute JavaScript commands and create simple objects.

 

Alternatively, if you're feeling somewhat more adventurous, you could install NodeJS and then have a JavaScript runtime environment that does not require a browser (server-side JavaScript).

 

Hope that helps!

 

Chris W

Hi all,

I just thought making a new blog about some good behavior when getting a ABAP-developer.

Ok, most of the points hit all developers, I think.

First I thought, maybe this is a blog for all the freshers out there, but when I was collecting the facts and what I think, everybody should do, I recognized, that I work through a lot of coding and it is more than just the new developers, which need to know that (again). No, I’m not saying, they don’t do so, but I think, some maybe have to get the focus on such points again, so that they (includes me, of course) remember the facts.

Ok, let us have a look at the list.

Of course, everybody is invited to add additional points in the comments. A very cool thing would be, if we get a document in the end. I searched SCN, but all I found, was a lot of good content, but not a big guide or wiki.

 

1st of all, know your guidelines!

Make yourself familiar with the programming guidelines given you by your customer or company. Don’t mess up with the guidelines. I f there are rules in, follow them. It’s not up to you to change the rules. If there might be a mistake, and yes, there might be some, report them to the person, which is responsible for that. I’m sure, the person will give a hand and explain or change the rule.


The 2nd point is, introduce yourself to the consultants.

Make sure, you and the guys given you input talk on a same level. You can easily achive that, by repeating his concept in your own words and send it to the consultant. It confuses me everytime I see people developing things, which are not that, what the other side imagine. Make sure, that not you are the wrong guy in the chain!


The 3rd point: Be a smart developer and do not start coding as the world collides tomorrow.

Most of our developing will run for more than just a few months. Make sure, if you have to enhance or rebuild something, that you took the correct spots. You can easily prove the spot by searching SCN. If I wasn’t that sure, I found most of the times another, which get answered the question already. If not, be the first asking. By the way, talking to colleagues about things like that always helps and gives another perspective on it. Luckily I’m surrounded by  some

 

 

The 4th point is, think about your developing twice.

Before starting, as mentioned above and afterward. It is not wasted time, to work through your coding again and make comments, where comments are missing. It is not a good programming style, to be the only one to understand what it’s going on inside. You will get in trouble if you do so, for sure!

Use variables, that speech to the people and saying things like that:

“Hi, I’m Mr. Table, I store the dataset, which is used for proving the orders”. (Ok, I'm a bit silly at the moment, you know what I mean)

 

5th Use good technics inside your written code

Today it is more and more important, that we have to make a cut between the view and the coding itself. Use the Patterns and technics which people teached you. MVC is a big thing right now and it might be more important than ever before. What technics to prefer and stuff like that, I (we) can’t just summarize in such a few sentences here, that’s why I’m not trying it.

6th Use the codeinspector!

This is the easiest way to prove the coding. Back to the 1st point. Use it, if there is a profile in the system. If not, ask the author of the guidelines, why there isn’t one. If there is none, use the default variant, better use that one, than not using it at all.

The Codeinspector helps to make everybody’s development saver and easier. Again, use this awesome feature. If you got a newer release, there might be the ATC (ABAP Test Cockpit) available. But that would be too much at the moment.

 

7th Implement unit tests if possible

You know, unit test might be a messie work at the moment, but there is no program, which is guarded against changes. Most of the developing lives and the customer/users get new ideas, what they want to do with it. So the unit-tests help you right at the moment not that much, but if the object returns,

you needn’t to fight what done earlier, just press the button and see, if all scenarios work fine, after developing new cases with it. (That is another big story to tell, there are a lot out there, perhaps I bring another to the binaries, someday)


8th Make performance tests.

Don’t just say yourself, it’s working with my data. Think about the scenarios hitting your coding in productive areas. Make sure, that you tested or better told other to test your developing in different scenarios. You are the developer and you know the critical things inside. So do not leave them alone, make it public and tell the people what they have to focus in your eyes.


9th Don’t waste too much time with a technical documentation.

     Only waste time with TDs if your're going to make AWESOME ones

  Give the result to another developer and just let him look at it for 10 minutes. It just should be a feeling, if it is understandable what you are doing there. It doesn’t matter if he can understand everything, but the feeling should be there. When writing the technical docu remember this blog here:

Stop writing Technical Documentation nobody will ever read

We discussed about it in the office and yes, he is absolutely right.

 

As usual, a question in the end:

Who knows, when working through the guidelines of your company last time. How often is this document updated? Is there also a styleguide included?

 

Thanks for reading to the end. If I'm wrong with a point, let me know and I can rethink it.

Regards

Florian

 

PS: It just popped up in my mind, so I added the Star Wars picture*haha*


18.02.14: Updated point number 9 out of the comments. I agree with Mauricio Cruz  and Bruno Esperanca that the mentioned description gives a better clue, what is meant

Recently, have come across a requirement to send output of report developed based on SALV as an excel attachment. To achieve the same we can use the method TO_XML of class CL_SALV_TABLE. This method will have the formatted output content as XSTRING value.

 

Here are the steps.

 

a. Variable Declaration.

scn_blog_alv_decl02.png

b.Data Selection/ALV Customizing calls.

 

scn_blog_alv_call02.png

scn_blog_alv_call03.png

scn_blog_alv_call04.png

 

c.Call to Convert ALV Output as internal XML Format.

scn_blog_alv_call05.png

 

d.E-Mail Data Declaration.

scn_blog_alv_call06.png

e.EMail - Content Conversion/Body/Attachment Creation

scn_blog_alv_call07.png

Send E-Mail

 

scn_blog_alv_call08.png

 

 

Excel Output

scn_blog_alv_call09.png

 

we can also use TO_XML method to download the content as an XLS or XML documents  in Abap Web dynpro based applications .

 

Here is the sample source code for this approach.

Hi SCN community,

 

This blog post is a logical follow-up from this blog post of mine, where I share my design for a region specific implementation framework using ABAP OO and the factory pattern strategy design (what a fancy name!). From the discussion that followed the idea came up to try and make use of the BAdI technology available in SAP, and this is my proposal for a design using this technology!

 

This proposal is based in a lot of other blog posts, like this one by Andrea Olivieri, this one by Thomas Weiss, and many others. Check my bookmarks, I usually bookmark interesting stuff. These blogs show a more or less static view over BAdIs. What I found out while designing and implementing this approach, is that actually the BAdI technology is amazingly flexible and powerful. Hopefully you'll share my point of view after reading this post.

 

Thanks to Debopriyo Mallick and Suhas Saha and their valuable contributions in the comments I have revised my design and, consequently, this blog post. I believe the design is now one step closer to that "idyllic", probably non-existent, perfect solution. By the way, if you're looking for an interesting way to activate/deactivate several BAdI implementations at once on different levels you should check this blog post that Suhas shared in the comments.

 

The premise

 

The premise remains pretty much the same as before, so I'm not going to go into it again in great detail. If you're working in a system shared internationally, or by different regions, eventually you'll get user-exits with so many IF and CASE statements, and shared by so many developers, that you will definitely want to have a framework put in place to cope with the specific requirements for each region/country/sales organization/whatever.

 

In my previous blog, the solution presented doesn't have any flaws "per se" (not that I can think of), but it does represent a very strict and formal solution. If you want more flexibility and freedom, while retaining the same advantages, I think this BAdI design might be the way to go. It also doesn't feel like you're redesigning the wheel

 

Let's get to it! I'm going to showcase this design with the user-exit INCLUDE MV45AFZZ. Anyone that has ever developed anything for SD should probably know this user-exit, so I think it's the perfect candidate.

 

Oh, by the way, this is NOT meant to teach you how to create a BAdI. If you are unfamiliar with BAdIs, please take a look into the blog posts I mentioned above.

 

Let's go!

 

We start by creating the BAdI definition. The multiple use definition is arguable. For this example I will leave it on, and I guess this would be pretty much standard unless you want to make sure that ONLY ONE implementation of the BAdI is executed. You should keep in mind that when using this option you cannot have parameters defined as exporting/returning, as this would be against the idea behind it. Read in the comments for a more detailed explanation, or press F1 on it . Developers implementing this (and any multiple use) BAdI should pay attention to the fact that other implementations could also be executed and affect variables they are trying to determine, so proper documentation and descriptions of the BAdIs is not a bad idea. Also, in my first version of this blog post, I had defined one BAdI for the entire MV45AFZZ include, with one method per routine. After Debopriyo pointed out, and rightfully so, that this would mean many unimplemented methods in the BAdI implementations, I decided to revise this and have one BAdI definition per routine. I agree this makes more sense. Now, nabheet madan asked in the comments if it would also be a good idea to define one BAdI per method when applying this design on a standard BAdI. Quite honestly I'm not sure. I guess that would depend on the BAdI... if the BAdI has methods that are related to each other and should all be implemented, I guess one BAdI definition with the same interface as the standard BAdI would make more sense. Otherwise, to avoid having several unimplemented methods, it might not be a bad idea to define one BAdI per method. If you have something to say about this leave a comment below!

 

Picture1.png

Figure 1 - BAdI definition

 

Now, in my situation, we have different clients per region, so a filter that will come very handy for sure will be the client filter. So I set it up straigh from the start.

 

Picture2.png

Figure 2 - BAdI filter definition

 

In the discussion in the comments it was also pointed out that a BAdI with many parameters in the interface was a BAdI poorly designed, and this could be a problem in user-exits from include MV45AFZZ, since there is no formally defined interface. To "solve" this problem, I thought of defining one structure for the BAdI's interface, like this.

 

Picture3.png

Figure 3 - Defining a structure to use in the BAdI's interface

 

In this case I've already defined internal item table XVBAP, but if you don't think you're going to need anything special for now, you can just declare some dummy field, or don't create this structure at all and create it only when you need it. It will not cause you any pain afterwards, even if you already have BAdI implementations created, as they will simply not use the newly created parameters. So, after revising the design thanks to the discussion in the comments, the BAdI interface now has only one method (in this example I'm showing the method for userexit_move_field_to_vbak).

 

Picture4.png

Picture5.png

Figure 4 - BAdI method definition

 

And that's it for the BAdI definition (for now)! It wasn't that difficult. Now all you have to do is call it from your routine.

 

Picture6.png

Figure 5 - Calling the BAdI from your user-exit

 

Ok so now we implement it! For this implementation, the requirement is specific to client 077. Couldn't be easier.

 

Picture7.png

Figure 6 - Implementing a BAdI with a filter value

 

The rest of it is standard, yes? All you have to do is implement the method with the requirement you want. Personally I think the best would be one implementation per requirement. Major advantage is total independence per requirement. You can have one developer per requirement working at the same time on as many requirements as you'd like, no problems with object locking. The disadvantage could be low-awareness between developments. Meaning that a developer implementing a new requirement should take a look at already existing implementations to check if there's not going to be some conflicting implementations (like a field being overwritten or something).

 

Now comes the really interesting part for me. What if a new requirement comes along which needs a new parameter? At first glance I would think this would mean a lot of trouble. Going through every implementation and adjusting. Well, not really... or at least I could do it without much trouble (but I guess you'd better not change the parameters already existing, just add new ones)! You change the structure we created earlier and add your new parameters:

 

Picture8.png

Figure 7 - Adjusting the BAdI's interface

 

Now we need to populate this variable in the code and that's it. The interface will update every implementations method signature, and if the parameters aren't used... well... they're not used, no problems there.

 

Picture9.png

Figure 8 - Adjusted BAdI call

 

You can now implement your second requirement easily. So, we now have implemented two BAdIs, filtered to be executed only for client 077. What if now we get a third requirement, which is to be executed globally? A core requirement? It also couldn't be easier, we implement a filterless BAdI!

 

Picture10.png

Figure 9 - Implementing a filterless BAdI

 

The rest you already know. What happens in runtime? In runtime, regardless of which client you are on, the filterless BAdI is executed, and if you happen to be in client 077, the previous BAdIs get executed as well. As you should already know, there is no guarantee to which BAdI runs first, so make sure one implementation does not rely on a result from another implementation, and also try not to change the same fields, because only one value will prevail, and you have no idea which one. It means you can't implement some default values for the "core" implementation and hope that the specific implementations will prevail, you have to implement this accordingly. Actually, as Sougata Chatterjee pointed out in the comments, there is a way to sort BAdIs in the new Enhancement Framework. To do this you implement BAdI "BADI_SORTER". You can find documentation on this here. Personally, I would try to avoid this. Even though it could be interesting to have a BAdI implementation for a global requirement being executed first, and then the local/specific requirements executed overriding the global requirements, I think this adds too much complexity and could be hard to maintain. I would rather have a requirement either global or specific, and if it's not global, each region will have to implement it its own way. But now I know (and you know) this option is available. There's no such thing as too much flexibility, is there?

 

Ok, last but not least, let's say that even if we have successfully separated implementations per client, we're still getting many conflicts. We want a new filter, per sales organization. That's also not a problem, we change the BAdI's definition! This is mostly valid if you are implementing multiple requirements in the same BAdI implementation, but I'll keep this here for educational purposes.

 

Picture11.png

Figure 10 - Adding a filter to the BAdI's definition

 

The existing implementations will not care! They will keep being executed as long as the values for their filters match. How cool is that? But of course, you will have to adjust the BAdI's call, otherwise you'll get a nasty runtime error!

 

Picture12.png

Figure 11 - Adjusting the BAdI's instantiation for the new filter

 

Done properly, this will allow you to have a nice overview of your enhancements and requirements implemented in your system. You can also use the search feature in the enhancement spot implementation "implemented BAdIs" tab to search for filter value! Which is nice.

 

Picture13.png

Figure 12 - Enhancements overview

 

 

Picture14.png

Figure 13 - Checking BAdI implementations for a certain filter value combination

 

Conclusions

 

That's it from me! I'll admit, I think this approach is very elegant and powerful. Some care must be taken to make sure there are no conflicting implementations, but I don't think there's any way you can avoid that risk.

 

Let me know what you think and what you would do differently!

 

All my best,

Bruno

The IDOc WPUWBW for goodsmovement, has a Basic Type WPUWBW01 which has some segments:



 

 

 

As you can notice in the image, the IDOc has three segments:

 

  • E1WPG01: Header segment which will have the information of the transaction (IT_TRANSACTION information)
  • E1WPG02: Item segment which will have the information of Goodsmovement (TRANSACTION-GOODSMOVEMENT)
  • E1WXX01: This segment is used to map some extra information (customer enhanmecent).

 

 

In my POSDM system, the information of GOODSMOVEMENT is not mapped so I have to map them with the information of RETAILLINEITEM. For this purpose I need to implement the /POSDW/TASK badi, and in the CALL method of my implementation, I receive these parameters:

 

 

   

 

 

Furthermore, in this method I call the FM  /POSDW/IDOC_OUTPUT_WPUWBW, which creates the IDOc.

 

 

 

 

At this point, the IDOc will be generated and for each transaction we will be able to see two segments (one E1WPG01 header and one or more E1WPG02 segments with goodsmovement information) in the IDoc. If you need to add some enhancement data to the IDoc, you will need to map the GOODSMOVEMENT-EXTENSIONS table in the CALL method that I showed before. This table has three main fields:

 

 

 

 

 

And the E1WXX01 segment has the same fields but with other names:

 

 

 

 

For each EXTENSIONS entry that you map in the CALL method there will be a E1WXX01 segment in the IDoc.

 

 

For example: I have mapped one EXTENSIONS entry in the GOODSMOVEMENT-EXTENSIONS table:

 

 

 

 

The result of my IDoc is:

 

 

 

 

 

 

 

In addition, if you need to do some extra modifications on the IDoc information, you can implement the /POSDW/IDOCOUTPUT badi to modify EDIDC  or EDIDD data. The CALL method of this badi receives as parameters the CT_EDDID table which contains all segments that have been created for all transactions.

 

 

 

Actions

Filter Blog

By author:
By date:
By tag: