ABAP Objects – IWTB – Part 04 – The Curse of Frankenstein
Previous blogs in this series:-
Contents, said the Tensor
· Story So Far
· Song
· How to handle multiple input parameters?
· Repetitive Code
· Converting local classes into Z classes
· The Application Log Revisited
· To Kill a Mocking Framework
The Story So Far
After the creation of his first monster did not go as well as he might have expected, Baron Frankenstein was under attack by a mob of villagers armed with pitchforks and torches. Luckily his good friend H.G.Wells was visiting at that point, so they used his Time Machine to flee through time and space along with servant Fritz (known as Igor).
“There was no spark of gratitude in the hunchback; he hated his benefactor as much as he hated everyone else” – Monster or Man? Bernard Brett / Dorothy Ward
Anyway, they ended up in a Gothic castle overlooking SAP Headquarters in Waldorf.
If he had arrived in 2006 the SAP salesmen would have talked about ESOA and the “new, new economy” and tried to sell him an XAPP featuring the visual composer, but as it is 2013 no-one talks about such things any more.
Instead, SAP wasted no time in selling the Baron a mobile, in-memory, cloudbased monster making software system, based on Variant Configuration. Sadly, that did not work “out of the box” as promised, and the resulting monster went on a rampage the likes of which has never been seen before.
Like so many before him the Baron was confronted with the old “buy versus build” decision. It occurred to him that he did not BUY monsters he BUILT them, so using the same logic he really needed a major custom ABAP development to sit behind the VC framework.
The castle was relocated to Australia, and I was hired as ABAP programmer, having had experience of working in Germany. Count Dracula was hired as project manager, and an experienced VC consultant was located in Berlin, who turns into a wolf whenever the moon is full.
The project kick-off meeting is on the 27th of November in Brisbane. In the interim I have been writing a proof of concept Monster Making Simulator, trying to use my newly acquired knowledge of Object Orientated ABAP, and detailing the progress of this in a series of blogs, of which this is the fourth.
Hopefully later in the project we may get additional blogs from Count Dracula and the Wolfman showing the project from their perspectives, and at the end an army of Monsters risen from their graves will attack and lay waste to the annual Configuration Workgroup (CWG) conference on Marco Island, United States, as a sort of “thank you” to the Variant Configuration technology which made this whole thing possible.
Time to Raise the Curtain, On the Muppet Show Tonight
I have now finished my first pass through “Head First – Design Patterns” which is very impressive. Near the end the ever popular “Model View Controller” rears its ugly head, and the authors mention that if Elvis was a Design Pattern he would be a Model View Controller, and then they present a song in the style of Elvis called, strangely enough, “Model View Controller”
So if this blog is “Frankenstein the Musical” then really I ought to start with a song as well. I picked Kenny Rogers’ song “The Gambler” but the trouble is the word “developer” has far too many syllables in it to fit. Even “programmer” has too many, but then it occurred to me that if I abbreviated this to “’Grammer” then all would be well. So, here we go:-
Project Manager Kenny Rogers meets The ‘Grammer
On a warm summer's evenin' on a project bound for failure,
I met up with the ‘grammer; we were both too tired to sleep.
So we took turns a starin' out the window at the darkness
'Til boredom overtook us, and he began to speak.
He said, "Son, I've made my life out of readin' people's faces,
And knowin' their requirements by the way they held their eyes.
So if you don't mind my sayin', I can see you’ve no use cases
For a taste of your whiskey I'll give you some advice."
(Key Change)
So I handed him my bottle and he drank down my last swallow.
Then he bummed a memory stick, and asked me for a light.
And the night got deathly quiet, and his face lost all expression.
Said, "If you're gonna write a program, ya gotta learn to write it right.
You got to know when to subclass, when to compose 'em,
To program to an interface to make your program run.
You gotta hide your logic, in some private methods,
That’s called encapsulation, when the day is done.
(Key Change)
Now Ev'ry ‘grammer knows that the secret to survivin'
Is knowin' what to throw away (dead code) and knowing what to keep.
Requirements change so often, you’ll always be a loser,
And the best that you can hope for is to die in your sleep (in a meeting)."
(Key Change)
So when he'd finished coding, he turned back towards the window,
He switched off his tablet, and faded off to sleep.
And somewhere in the darkness the ‘grammer became a mentor,
But in his final words I found a blog that I could tweet.
(Key Change)
You got to know when to subclass, when to compose 'em,
To program to an interface to make your program run.
You gotta hide your logic, in some private methods,
That’s called encapsulation, when the day is done.
Chorus x 3
Painter Man, Painter Man, Who Wanna Be, a Painter Man © BoneyM
In my last blog, I showed some screen shots of the logging mechanism I had written so we could show the business users each step of the monster making algorithm, and I showed some code samples.
Australian SAP expert Chris Paine raised a very good point, here it is:-
“Have you thought of making your add_calc_logmethod take in an array of variables? Or is Igor not that creative with his formulas?”
That is a very good point, in some languages such a thing is easy, you can pass in an arbitrary number of parameters into an array at the same point you are passing this to a method. This is a bit more painful in ABAP; you have to fill up the array first, which means a helper variable, and all in all, a lot of “boiler plate” code which distracts from the main purpose of the method.
Here was my initial response:-
Every time someone at work asks me "is it possible to do that" I can't stop until I've made whatever it is possible, especially if one of my colleagues has claimed it is impossible.
In this case we want to pass in a variable amount of values, without having to declare an array and then fill it up.
I just wrote a little test program where I defined a class which had some data and an instance variable of the same class as itself.
Then I gave this class a factory class method where you pass in the data and get back an instance of the class.
DATA: lo_recursive_thing TYPE REF TO lcl_recursive_thing.
CREATE OBJECT lo_recursive_thing
EXPORTING
id_st = 'String One'
io_rt = lcl_recursive_thing=>get_thing(
id_st = 'String Two'
io_rt = lcl_recursive_thing=>get_thing(
id_st = 'String Three' ).
If my recursive thing was an object which held the details of an input parameter I could pass into my logging method as few or many values as I liked into a single input parameter of the class type "recursive thing".
Once inside my logging method I could then call a single operation on the object that was passed in and it would "unpack" itself, rather than me calling the same method repeatedly on each of the fixed input parameters. This is a variation on the decorator method as I understand it.
Hip Hip Array!
Unpacking...
METHOD print."of lcl_recursive_thing
IF md_stringIS NOT INITIAL.
WRITE😕 md_string.
ENDIF.
IF mo_predecessorIS BOUND.
mo_predecessor->print( ).
ENDMETHOD.
Then after the LO_RECURSIVE_THING has been set up above I just call
lo_recursive_thing->print().
and out comes
String One
String Two
String Three
Back in Frankenstein world, I wasted no time trying to write a re-usable Z class that I could use in this instance and in other instances. It is not actually re-usable yet, I still have it tightly coupled to my LOGGER class, but I need to find a way round this, I can see other applications already, having an arbitrary number of parameters is such a common thing in IT – think about the MAXIMUM function in EXCEL where you pass in a list of values and get the highest back. I also have a corker of a use for this which I will come back to later in the blog.
The traditional way round this problem (if you don’t want an array) is just to have a bucket load of optional parameters and then in your method have the same code over and over to deal with each one, here is some code from my LOGGER class.
add_input_field_to_log( EXPORTING id_input = id_input1
id_count = 1
CHANGING cd_formula = ld_formula
cd_message = ld_message ).
add_input_field_to_log( EXPORTING id_input = id_input2
id_count = 2
CHANGING cd_formula = ld_formula
cd_message = ld_message ).
add_input_field_to_log( EXPORTING id_input = id_input3
id_count = 3
CHANGING cd_formula = ld_formula
cd_message = ld_message ).
Etc…
Taking the Java approach that “everything is an object” then what if I made the parameter an object and tried to apply my recursive logic to this. The result inside the method comes out as:-
io_input_parameter->add_input_field_to_log(
EXPORTING io_logger = me
CHANGING cd_formula = ld_formula
cd_message = ld_message
cd_count = ld_count ).
Inside the “parameter” object we have:-
METHOD add_input_field_to_log.
* Local Variables
FIELD-SYMBOLS: <value> TYPE any.
ASSIGN mo_do_value->* TO <value>.
CHECK <value> IS ASSIGNED.
io_logger->add_input_field_to_log( EXPORTING id_input = <value>
id_count = cd_count
CHANGING cd_formula = cd_formula
cd_message = cd_message ).
CHECK mo_predecessor IS BOUND.
ADD 1 TO cd_count.
mo_predecessor->add_input_field_to_log( EXPORTING io_logger = io_logger
CHANGING cd_formula = cd_formula
cd_message = cd_message
cd_count = cd_count ).
ENDMETHOD.
That has the same effect as looping through an internal table, each parameter object checks to see if it has a predecessor and if so, calls the same method being processed on its predecessor before processing the method itself.
From the outside, it gets called like this:-
mo_logger->add_calc_log_entry(
io_input_parameter = zcl_bc_parameter=>get( value = md_blood_volume
predr = zcl_bc_parameter=>get( value = md_blood_percent
predr = zcl_bc_parameter=>get( value = mo_pl->get_specific_gravity( md_source_material ) ) ) )
id_formula = '&V1& * &V2& * &V3&'
io_output_parameter = zcl_bc_parameter=>get( value = md_quantity ) ).
I am sure I will revisit and redesign this many times, in order to decouple it, I will end up with some sort of ITERATOR thing where any method can call the input parameter and it will loop through a list of values of the sort desired with the exact data type being decided at runtime.
General Pattern
I have now converted all the key parts of the spreadsheet detailing the monster making algorithms into a whopping great local class with loads of methods. As I go through the spreadsheet, turning the EXCEL calculations into ABAP code, after a while I notice certain patterns repeating themselves at various points throughout the algorithm.
I have a wish list of utilities I want to write for myself, and one of these is to scan the source code of a program and look for such patterns and flag them up to me saying “look – that is just the same set of keywords, again and again”. You would look for repeating patterns of keywords, with just the variables different.
Ironically I wrote something very similar when I was 16 for the BBC Microcomputer the motivation at the time was to scan all the text in the program to look for patterns of characters to compress into a single digit code i.e. a text compression mechanism. In those days it was said that you could never get text compressed to under 50% of its original size and then as now I always wanted to do those impossible things.
The compression routine was in BASIC but the runtime decompression routine was in machine code. I have things so much easier now.
For making a monster you need several basic categories of components for the BOM, and each category needs one or more SAP materials.
The database lookups for finding what exact materials you need, how many and the percentage split, for each category is virtually identical, but the algorithm for calculating the exact quantity is very different for each component type. This is a job for the good old “separate what varies from what stays the same” and I try to do this in such a way that if the DB lookup for one of the component categories does change it won’t be the end of the world.
The algorithm that keeps repeating is as follows:-
· Each body part from the monster is assembled from one to four bodies we dig up
· We loop through the algorithm four times
· We see if the component number X is required of body part Y
· We get the relevant SAP material number and look up the percentage for component number X
· We calculate the exact quantity for the BOM (logic very different per type of body part)
Apart from the last step every other step is pretty much identical in terms of what data you need to get the result i.e. the signature of the individual methods I wrote for each different body part is the same.
Following the instructions from the Gang of Four we try to program to an interface, not an implementation, so I create an interface with the IS_COMPONENT_REQUIRED and GET_SOURCE methods with the same interface as the individual methods I have been writing, and a CALCULATE_BOM_QUANTITY method with no signature at all.
That is because there is no CONSTRUCTOR in the abstract class, and all the subclasses have a different set of member variables, which get set up in their constructors. These member variables then get processed in the CALCULATE_BOM_METHOD in the subclass. In the other two methods all that varies is which table gets looked up, the signature can remain the same. I don’t actually need a signature, but as I said before when I can I like to declare what is going in and what is going out of a given method, that’s just me. If this is obvious madness, someone tell me, I am still finding my way here.
Now, it is time to go diving down a rabbit hole.
We All Live in a Yellow Subclass
This whole interface vs. subclass thing is so very difficult to get my head around; I think I am slowly getting there.
As I understand it, the idea is try not to ever mention “concrete” classes in your code, as in:-
DATA: lo_thing TYPE REF TO zcl_concrete_thing.
… instead it should be……
DATA: lo_thing TYPE REF TO zif_abstract_thing.
At first I thought ZCL_CONCRETE_THING would implement the interface ZIF_ABSTRACT_THING but oh no, it is more complicated than that.
We want to have the variable in the program pointing to an interface so we can swop whatever we pass in with a totally different class if need be with the program being none the wiser, but on the other hand interfaces don’t have any code so where do we put the code that is always the same between the low level subclasses?
The answer seems to be to have a ZCL_ABSTRACT_THING that implements ZIF_ABSTRACT_THING, that class has the code that does not vary, the classes which have code that does vary i.e. ZCL_CONCRETE_THING inherit from ZCL_ABSTRACT_THING.
Then in the main program (the so called “client” ) you have a factory method which works out the exact type of object that comes back.
At this point, you probably feel like the chicken in the Alan Partridge TV series which has been fed chemicals until it is twenty feet tall and it shouts out “WHY AM I SO BIG? WHY? WHY?”
The Nutcracker Sledgehammer Suite
It does seem a bit of overkill, you can imagine the argument going like this:-
Ernie: Why all this effort, it looks like you add a million lines of extra code, to avoid duplicating three lines of code?
Bert: This way, you follow the “open closed” principle. When things change, as they always do, then you only have to change the code in one place, and the other code, which you know works, cannot break because you have not changed it.
Ernie: You know Bert, and this may come as a shock, changing code in lots of places is not actually that difficult. Have you heard of “CONTROL C” and “CONTROL V”? That’s what most people assume us programmers spend all our day doing.
Bert: But what if you forget to change one of those areas of duplicate code?
Ernie: I tend to write little comments to myself in each routine saying “if you change this remember to change X, Y and Z” but I see your point. My question is, if, to avoid this risk, you make the code so complicated no-one has a chance of understanding it, are things better or worse? It’s like when you wrote you own “MVC for DYNPRO” system of classes.
Bert: Everyone does that – thousands of ABAP developers all round the world have written their own MVC for DYNPRO framework because they work for companies that won’t use Web Dynpro.
Ernie: Why?
Bert: DYNPRO is hopelessly old-fashioned. You have all the business logic mixed with the display logic.
Ernie: So you re-write all the functionality that the “old fashioned” PBO/PAI framework gives you, using hacks to try and get the same functionality that was given to you on a plate before? And thousands of developers all around the world have spent weeks doing this, just to end up with something that did the same as before they started? Do their employers know?
Bert: You are missing the point – once I write my MVC for DYNPRO framework once, it is re-usable, whenever I have a new program that does a CALL SCREEN.
Ernie: It was re-usable before. You could re-use the standard DYNPRO functionality you had spent so much money on.
Bert: Right - outside. Now.
Those two clearly can’t agree, this is a very emotive subject, I don’t know the answer to this, maybe we should call in an OO expert in for some advice? Here is one!
Bert: So, expert, what is the answer here?
Expert: NOM, NOM, NOM, NOM, COOKIE!
WAIT UP TO 10 SECONDS.
If you need more in-depth OO and design analysis then please click on the link below:-
OO, OO, OO – There’s no need to laugh!
When I am not working for the Baron I am in an environment where there are six programmers, one is gung-ho about OO and does all programming that way, one describes such programs as that OO rubbish”, one is mad to learn but has a bit of a fear of such things, one is a “call to arms” programmer as per Graham Robinson http://scn.sap.com/community/career-center/blog/2012/10/24/a-call-to-arms-for-abap-developers, there is me, and the last one was discussing OO with me the other day.
“I don’t use it that much, except where I have to” he said “the problem is it is really difficult to maintain, and software maintenance is 95% of the software cycle”.
“Oh my god” I thought to myself “Is that what people think? Even worse, is it true?”
My understanding is that the reason we are supposed to move from procedural to OO programming, the sole reason, the only reason, is that it is EASIER to maintain, which in a world of constant change requests is no small thing.
It’s like back in the 1950’s when the medical profession advised people with asthma to take up smoking as that would cure their condition. Then twenty years later they say “oh sorry, not only won’t it cure asthma, it will in addition give you cancer”. The whole house was built on sand.
That can’t be the case with OO, surely? It can’t be the Emperor’s new clothes?
This in many ways is one of primary goals in trying to write my new application following all these OO principles. When the changes come, and they will come in droves, will having designed the application this way make the code easy to change and robust?
This is what I want to know, and I am going to find out. When I do, I will shout out whatever I encounter to the rooftops.
Class Warfare
I had been doing everything in local classes, the idea being to get them right before migrating them to Z classes, and also I had in my head that really Z classes should only be re-usable classes, you don’t want to bloat the repository with half a billion Z classes which only are used by one application.
Many times before I have bitched and whinged about how difficult SAP makes it to program local classes as opposed to FORM routines. In my last blog I got the following comment from Matthew Billingham:-
I have used local classes a few times, but only to deal with data that truly is specific to the global class I'm constructing. And "main" classes in reports. But otherwise, it's global classes all the way for me. I'm regularly finding I'm re-using that one-off global class I created 2, 3 or more years ago.
… and I thought to myself, why am I banging my head against a brick wall? SAP are never going to fix this problem, it is time to bite the bullet and convert all the local classes in the monster making application into Z classes, then your productivity will go up. In some sense this is the “wrong” thing to do, but if realistically there is no choice, then what must be must be.
World War Z Class
The other bonus of converting things into Z classes is to bring to the front any circular dependencies. By this I mean local classes in an executable program are a “hybrid”. They can call procedures, they can use global variables, refer to global structures (in the program at hand) they can call on other local classes.
Once those classes formerly known as local become a Z object they cannot do any of that of course, you get a compile error. So you try to see what dependencies are really needed, such things bubble to the surface. Do you need Z structures, which of your other local classes need to be global etc., and are any circular dependencies? A circular dependency would be when class A does not compile because class B does not exist, and class B will not compile because class A does not exist.
According to the textbooks you are supposed to get rid of such things:-
If there is a circular dependency you can get round it by activating them both at the same time, but I try to spot them by activating one class at a time rather than everything at once, and if I do find one I ask myself is it really needed?
SAP has given me an easy way to convert a local class to a Z global class. You just go into SE24 and then take the menu path Object -> Import from Program. I had never done this before; let me write a review on how easy this.
A Code Inspector Calls
You put your program name in and you get an option to include any INCLUDE programs that might be there.
The first thing I notice is that whilst al the local classes appear – sometimes, the thing seems to break without reason more often than not - the proposed names are in the SAP namespace, presumably so you are forced to change them.
There is a green tick in the dialog box – hover over it, it says it means “close”, usually the Red Cross means close… in fact the green tick means nothing really… you click it and nothing happens.
When you say “check names” box you get no errors but if you try to create the class with the proposed name of course you get an error as the proposed name is in the SAP namespace … I am being a bit unfair here, how are SAP supposed to know your naming conventions - but I bet most companies start global classes with ZCL_
Then when you have imported the data you get an SE24 screen without the name of the class that has just been created! Maybe I am a madman but I would like to go back into the newly created Z class to do a syntax check and then activate it. At the very minimum you need to give it a text description before it will compile.
I also notice that descriptions of input/output parameters is the parameter name e.g. ID_THING as opposed to the data element definition e.g. “Sales Order Number” … this again is a tough choice ere how is SAP supposed to know what you want to call an input parameter, the point I would make is the behaviour is different than when you create a parameter normally using SE24. Either you should always copy over the parameter name to the description, or always copy over the DDIC description, but surely not choose one technique in different situations?
Lastly, because my local class had inherited from an abstract class which implemented an interface, that bizarre combination threw the whole process for six, and I got all sort of bizarre error messages, the best being “abstract methods cannot be redefined” which is cuckoo, abstract methods have no code so they MUST be redefined, someone must have wrote that error message, why?
To the rescue, is the source code based view of the SE24, this is the answer - that is a huge leap forward from what I was used to before in SAP V4.7. All I needed to do was change the code to the way it looked when it was a local class and all was well.
Never Ending Repository
Anyway, after a while, all the classes formerly known as “local” were now repository objects, apart from the test class which remained local, inside the main class which does all the BOM calculation logic.
Hopefully this should speed things up going forward, I will no longer have to deal with the agony that is dealing with local classes. I am already at a stage where I can produce a rudimentary BOM, but I am nowhere near putting this in the real VC framework yet, and I want to show my progress to the world. So, it is time to enhance the application log.
The example program to look at here is SBAL_DEMO_04_SELF. There are two things I want to do, firstly add an extra button on the application toolbar so when a database read fails the user can click on the button and view the current contents of the database table in question.
That was really easy – I won’t go into his, just look at the example program – it looks like I am going to have to call a function module to respond to the user pressing this, I imagine when the application log was created, calling a method was not yet even a glint in the Milkman’s eye.
Adding your own fields was not too much of a bother either, you create your own structure, and fill it up with values before adding each relevant entry to the log, and then you need to set up your own “profile” just before displaying the application log.
In my case I want the final step to show the contents of the calculated BOM.
This is all from standard SAP without having to do anything very dramatic. I have seen presentations from the configuration workgroup conferences where external companies try to sell you something like this for ten billion dollars, and it was right behind your sofa the whole time.
Mock the Casbah
That’s as far as I’ve got, I will keep you posted as my project progresses, but I couldn’t leave you without mentioning what I think is a really major leap forward in the test driven development ABAP world.
This is a – now – open source framework to help you write unit tests faster. It is called MOCKA – it takes its name from the Red Sea coastal town of Mocha, Yemen, which as far back as the fifteenth century was a dominant exporter of coffee, especially to areas around the Arabian Peninsula. Oh hang on, that’s MOCHA which is a sort of coffee.
Anyway, I encourage you to read the blog(s) for yourself, download the thing, and DEBUG IT to see how it works. Uwe has written a few blogs on how to use this, for more background look on the internet to see how similar mocking frameworks work in other languages like RUBY, which is a dynamic, reflective, object-oriented, general-purpose programming language which takes its love to town.
As far as I can see the ABAP mocking framework works pretty much the same. I am going to give it a whirl, and what I try to do in all these open source projects is not just take, take, take, but try to give something back.
Here is something I can think of straight off – here is some code from one of the MOCKA classes, which takes 8 optional input parameters:-
CASE lv_index.
WHEN 1.
GET REFERENCE OF i_p1 INTO lr_ref.
ls_exporting-value = copy_value( lr_ref ).
WHEN 2.
GET REFERENCE OF i_p2 INTO lr_ref.
ls_exporting-value = copy_value( lr_ref ).
WHEN 3.
GET REFERENCE OF i_p3 INTO lr_ref.
ls_exporting-value = copy_value( lr_ref ).
WHEN 4.
GET REFERENCE OF i_p4 INTO lr_ref.
ls_exporting-value = copy_value( lr_ref ).
WHEN 5.
GET REFERENCE OF i_p5 INTO lr_ref.
ls_exporting-value = copy_value( lr_ref ).
Etc…
Now one day you may find yourself in a beautiful house, with a beautiful wife, wanting to mock a method with nine parameters. You have to change that method thus slapping the “open-closed” principle around the face with a wet fish.
Can you look back through this very blog, to see a possible way around this?
It’s hard to part I know, but I’ll, be, tickled to death to go
OK, that is it for now, I’ll be back when I have further progress to report on the good old monster making project…
Cheersy Cheers
Paul
As always, want I want is feedback, tell me when I am wrong, I’m bound to be wrong in something I’ve said, I need to know what. Any suggestions to the problems I outline would be even better!