Application Development Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
hardyp180
Active Contributor
0 Kudos

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

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

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.

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.

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.

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...