Personal Insights
Getting adventurous – planning to switch a function module from using forms to using methods
Those of you who have been following my blog posts and reading my questions are well aware that I’m having a hard time wrapping my head around anything ABAP-OO. Over the last months, I’ve however taken the first tentative steps and at least written a couple of report programs utilising local classes and methods instead of doing everything with forms. Of course, these were nothing fancy, just some data retrieval, processing and generating ALV-lists or output files, learning about and using some neat RTTS classes while doing that to quickly get things like field descriptors for column headers.
Fair warning of what’s to come
This blog-post is perhaps a bit unusual in that I first describe what I already have and will then ask for help to – potentially – switch my working form-based code to making use of a local class with relevant methods instead.
So, I recently created a new function group and module, well knowing that I perhaps shouldn’t have – but old habits die hard and my first goal was to get “something” working out there instead of having to simultaneously deal with the logic itself plus not really knowing what I needed to do where with ABAP OO. The gist of what the function module does is to apply several checks on an entered but not yet saved transport title when a transport is newly created. It will eventually be called from Badi CL_IM_CTS_REQUEST_CHECK in method if_ex_cts_request_check~check_before_creation but at the moment I still have it just as a standalone function module for ease of testing via SE37 and at the same time not annoying my colleagues while testing stuff. That’ll come soon enough!
What I already have developed
Here is the gist of what the function module does and uses to do its stuff:
- A Z-table to define the developer type based on the username (we can tell via the username if it’s a regular user or one used for special purposes like e.g. data dictionary activities)
- A Z-table to define which component is expected to be included in the transport title (change-identifier, country-identifier, developername)
- A Z-table to define allowed identifiers, either as plain text (e.g. “Basis”, “ProjextXYZ”) or a REGEX pattern (e.g. “1\d-\d\d\d\d-C for a change-ID looking like “18-0815-C”)
- Incoming parameters are just the username and entered transport title (AS4TEXT) plus the transport-type with “K” for “Workbench” being the default. I’ll most likely start with just calling the FM for workbench-transports and ignore customizing ones which usually aren’t tackled by developers. Not to mention that trying to enforce dev-guidelines for customizing activities is a completely different kettle of fish!
- To begin with, I get the needed information from the Z-tables so that I know what needs to be looked for in the transport title as combinations of IDENTIFIER, COUNTRY and DEVNAME are possible dependent on determined usertype
- In a loop, the expected identifiers are tackled and the title gets queried for each of them in turn in its own form-routine (yeah, I know, the “f-word”!)
- If an identifier is missing, an entry is added to the table eventually used when function module POP_UP_TO_GET_VALUES gets called to ask for any missing identifiers in, well, a pop-up!
- When the user gets the pop-up it’s only asking for the missing values and also shows the incoming transport title
- The pop-up cannot be exited without providing valid input, which means that the earlier checks are done again, just based on different fields
- Error messages with long explanations are provided to make it clear what is expected for each field
- Once all checks are satisfied, the title is accepted and the function module has done its task
Where to go from here
Apart from some fine-tuning, the function module is basically good to go into the Badi and that would obviously be the easy way out for me. It would however most likely also not be the best way out and I’m well aware that some options available with ABAP OO – like unit testing – would have come in handy for at least some of the on the spot testing while putting the code together. And no, I wasn’t even contemplating TDD for this as I actually wanted to get something done fairly quickly and not having to deal with one more complication on top of everything else.
Sooo, long story not so short, what I’d like to do next is to somehow introduce a local class in the function module and to then – step-by-step – switch one form routine after the other to a method. I’m even contemplating leaving the tested and working form-logic in the code for now and to temporarily introduce a flag via which I can make the function module either go the existing form- or the new method-route. That way, I’ll for one be able to do side-by-side testing and for another really see and thereby learn where things stay the same and what needs to or should be changed. This may be way over the top for many of you reading this but it’s the way I learn best.
What I already saw in the function group’s main program is a commented out line for INCLUDE <fg-name>P… with a tentalizing comment next to it indicating that it’s for “Local class implement”. So this to me looks as if what I’d like to do is possible, even more so as the help for function modules also mentions to use calls to methods instead of forms in order to follow the ABAP OO guidelines. My problem is, that I haven’t found an example of what I actually need to do where and in which sequence.
Anybody interested to help me with this? Mind you, I’m not asking for done and dusted code, just some guidance of what I need to add where, how to call things and how to properly copy my code from form-routines to identically working local methods. I don’t necessarily plan to go all-in, meaning that my main goal is to move things somewhat in the right direction but it doesn’t have to be picture-perfect by dotting all the i’s and crossing all the t’s for “pure ABAP OO” unless it’s easy to do and understand for me.
Once this is done and dusted, I’m fairly certain that a follow-up blog post will see the light of day, potentially with some side by side code. That’s at least what I’d like to get from this exercise for future reference.
Any takers?
Best way to learn ABAP-OO is to try doing it! And then do some more theory-crafting and improve. (Or I hope it's the best way 'cause I'm doing it like that).
Anyway, I can explain (or give some pointers) on how I'd do it. Up to you to decide whether it is enough :D. Currently, we're using a lot of local OO, but very often the local classes inherit from globally defined classes so that we don't have to write certain functionalities over and over again..
Why don't you use a completely new class for this? As far as I can see there is nothing that stops you from using a class (e.g. CALL SCREEN).
If you use local classes in the function group, how will you call them? Or do you plan to code something like this:
Hi Enno,
you mean, that instead of working with a function group and module to work with a global class which then gets called from the Badi?
For now my preference is to start from what I'm familiar with as a point of reference, i.e. a function module instead of a class and to also keep all the code in one place instead of "scattering it around" - esp. as this isn't anything which is likely to get used by other processes anytime soon. One of the reasons I started this as a function module with form routines is, that I had the relevant prototyped code lying around from a while back, when I first started to experiment with this in a sandbox system. Now, that I have that code working, I'm weighing my options to perhaps take one step in the direction of ABAP OO by "switching" from calling form routines to methods in a locally defined class in the function group - comparable to what I did for some report programs which now contain local classes.
The other somewhat restricting factor in all of this is, that I currently don't really do a lot of ABAP programming, even though I've been doing it (and PL/1 programming before that) for many years. This is another reason why I'd like to keep things as simple as possible even if it means that it's most likely not the best approach. For me, one step at a time is better than doing it in leaps and bounds. ?
exactly. The advantage is, that you can leave your function group completely unmodified.
Doing the most obvious thing (implementing local classes near to the function module) is not always the simpliest solution... 😉
Well - and the next won't come as a big surprise - I haven't yet set up a global class from scratch so am a bit hesitant to actually do that, not really knowing what I'd have to do where and how. If, on the other hand, there's a fool-proof template around somewhere which lays out a generic framework into which the method definitions and implementations can easily be dropped in, I might give it a try.
That easier than creating local classes! 😉
Start transaction SE24, enter a valid name like ZCL_WHATEVER_POPUP, give it a description and enter the methods (=PERFORMs) you need. Instead of PERFORM something you just write something( ) or CALL METHOD something.
Entering the methods and parameters in the form based SE24 editor is kind of fool-proof.
Sounds easy enough – but I have a hunch that the proverbial devil lies in the details! I’m for example passing structures for internal tables back and forth between the main logic and the form routines and rely on global definitions in the top-include for those. Due to the way we handle dictionary objects I avoid creating DDIC-definitions as much as I can and prefer to have the data- or type-definitions directly in the programs. Would these definitions simply need to be put into the public definitions of the global class to make this comparable or is that just not “allowed” even if it’s technically possible? And if the latter, what are the alternatives?
yes! Exactly the way you described it.
If you implement all in a global class, you can define structures or table definitions inside the class!
E.g.
class zi_cl_request_check definition
public
create public .
public section.
types: tt_contracts type standard table of ever with default key.
I shied away from local classes for years because global classes are so much easier to develop - if you use classic SE24 in SAPGUI.
Now I use local classes widely.
Hmmm, why the caveat "if you use classic SE24 in SAPGUI", Matt? From many other comments, I take it that "we" should actually have been using ABAP in Eclipse for basically ages, so I'm a bit confused (as I'm currently forcing myself to use Eclipse at least for ABAP OO development).
Just that the form based editor of se24 is probably easier at first.
Actually, one of the first things I did when it became available was to switch to "source code based editor" as I prefer to see the complete code, use search via <CTRL>+F and be able to scroll up and down at will, just like with procdedural code or local classes (as long as that doesn't work with includes). ?
When I started with ABAP Objects, there wasn't any source code based editor! If you can use it from the start, that's great!
Hi Bärbel,
I would consider the CRC approach to define local classes. Note I say classes, not a single local class. Just from your specification I would think of a USER and a TRANSPORT class.
I hope you are aware can write unit tests for your function module.
and, there is always a magic step.
JNN
Thanks, Jacques!
Never heard of the CRC approach but from a quick look it might be more to my liking than UML-diagrams. At a guess, it's something also doable e.g. via Mindmanager or a similar tool (yes, I prefer readable electronic versions to scribbled notes on cards/post-its).
Ummm, no not really, I'm afraid. And unit tests are not all that high on my agenda as I'm fairly certain that I have to first have a much better grasp of ABAP OO before tackling that in earnest. One (little) step after the other ....
Cheers
Bärbel
Hi,
just have in mind that the parameter-types are a bit different.
But that makes them a lot more expressive and cleaner.
Regards;
Joachim
PS: for one of my personal projects i made a matching table:
Assuming we use an interface and no static methods.
for parameters
Thanks, Joachim!
And, to show my woeful ignorance regarding (ABAP) OO, a question:
Why is a function module comparable to an "interface instance method"? Unfortunately, I still haven't really grasped what the interface construct actually means and when/why to make use of it. What I think I picked up is that an interface doesn't have any code just the parameters, so how is that comparable to a FM which obviously does have executable code? And, I'm not even sure if my question makes any kind of sense!
It's not 🙂
In all honesty, if it's your first dive into OO, forget about all the advanced concepts, UML diagrams, interfaces, inheritance...
Start simple, start with what you know ... and you'll probably like it.
As Patrick van Nierop said: just do 🙂
At a very basic level:
That's it 🙂
(it's actually how the class concept was first introduced in ABAP back in r/3 4.6c days; all the "advanced" stuff came later)
M.
P.S. Start with a global class, it's a lot easier to grasp than the syntax of local class definitions. You'll quickly get frustrated with the latter (I know I did).
Thanks, Michal, that's helpful to put things into perspective!
Hi,
commenting when having a bit of temperature is not that good. I thought too complex. My matching tables describe a mitigation strategy from function groups to classes.
Nearest to a function group is a static class with static methods.
But this approach has a lot of cons with regards to writing testable and robust code.
Therefore i always recommend not to use them but classes with private instatiation using at least one interface and and for each at least one factory method. But as i see in the comment from Michal, these are already advanced concepts.
First of all, compare function modules to public methods and forms to private methods.
But here we have the first difference. Unlike forms private methods are really private (if we ignore friends) . Forms can be called from everywhere with “perform … of program …”. Not a good coding style but possible.
Global Variables of a function group could be compared to attributes of a class. Here the same: private attributes are really private unlike variables which can be changed from outside via ‘assign’.
Typing: In form routines parameters can be untyped. This is not allowed in methods.
Parameter naming: In function modules it is possible to name an importing parameter exactly the same as an exporting parameter. This is not allowed in methods.
At last: i second the recommendation to start with global class. It really makes starting with ABAP-OO a lot easier.
Hope i could clarify some parts.
Joachim
Postscriptum: I love to recommend the paper of Blumenthal/Keller allthough some code examples are a bit outdated now
http://www.sapwiki.cl/wiki/images/9/90/Abap_top_part_2.pdf
Postscriptum 2: Regarding Interfaces, Singleton etc.
the following code snippets are not tested in SE80 or ABAP in Eclipse, so they may content some syntax errors;-)
FUNCTION zjg_fm1_1.
*”———————————————————————-
*”*”Lokale Schnittstelle:
*” IMPORTING
*” REFERENCE(IV_GUID) TYPE GUID_16 DEFAULT ‘1234567812345678’
*” REFERENCE(IF_FLAG) TYPE CHAR_01
*” TABLES
*” RETURN STRUCTURE BAPIRET2
*”———————————————————————-
could become
interface ZJG_IF_NEW_CLASS
public .
types:
TT_BAPIRET2 type standard table of BAPIRET2 with default key .
methods ZJG_FM1_1
importing
!IF_FLAG type CHAR_01
!IV_GUID type GUID_16 default ‘1234567812345678’
changing
!RETURN type TT_BAPIRET2.
endinterface.
So you see that the interface contains the definition of the method and a type needed for its signature.
The class ZJG_CL_NEW_CLASS would implement the interface
class zjg_cl_new_class definition create private.
public section.
interfaces zjg_if_new_class.
class-methods s_get_instance returning value(rr_instance) type ref to zjg_if_new_class.
private section.
class-data mr_singleton_instance type ref to zjg_cl_new_class.
endclass.
class zjg_cl_new_class implementation.
method s_get_instance.
if mr_singleton_instance is not bound.
mr_singleton_instance = new zjg_cl_new_class( ).
endif.
rr_instance = mr_singleton_instance.
endmethod.
method zjg_if_new_class~zjg_fm1_1.
“import the code of the function module and adapt it to the stricter rules of ABAP-OO.
endmethod.
endclass.
A simple call could be
data lr_instance type ref to zjg_if_new_class.
data lt_return type zjg_if_new_class=>tt_bapiret2.
lr_instance = zjg_cl_new_class=>s_get_instance( ).
lr_instance->zjg_fm1_1( exporting IF_FLAG = ‘A’
IV_GUID = ‘1222567812225678’
changing RETURN = lt_return).
There are shorter ways to do this using method chaining and inline declaration but let us start with this.
Nice help for first OO-Steps.
One Comment.
methods ZJG_FM1_1
importing
!IF_FLAG type CHAR_01
!IV_GUID type GUID_16 default ‘1234567812345678’
changing
!RETURN type TT_BAPIRET2.
Try to design your methods as single action like "get_username". Then you're able to return the values vie "returning ..."
methods ZJG_FM1_1
importing I_FLAG type CHAR_01
I_GUID type GUID_16 default ‘1234567812345678’
returning value(r_RETURN ) type TT_BAPIRET2.
Yes, it's a diffrent behaviour then using changing, but's for OO it's the better style. Oh and try to forget exporting. Single value = returning.
I would have done that, as i prefer functional methods since the very beginning.
For two years i follow the path to RESTful ABAP.
But the tables parameter of a function module / form is unspecified.
As Michal said exporting, importing and changing are very much the same i tried to stress my demo code with the change of a tables parameter.
So to keep the same functionality it had to be a changing parameter.
Joachim
BTW exporting has its advantages over returning in terms of performance.
But as it is written somewhere, performance improvements need measurements and should never be done in advance.
Personally i would check as you said if a returning is possible so one could then write:
data(lt_return) =
zjg_cl_new_class=>s_get_instance( )->zjg_fm1_1( exporting IF_FLAG = ‘A’
IV_GUID = ‘1222567812225678’ ).
Or better having a separate private method in the caller class for the instantiation. As shown in OpenSAP course https://open.sap.com/courses/wtc1/overview.
I use both technics. Depends a bit on the definition of the class and the methodes. There ist no wrong or right.
Hint: If your result of a method is a bigger table use a public (read-only) table in the class. Another way is to use a class-instance as data container. For the returning parameter is it in this way only a pointer.
Thanks, Joachim! It'll take a bit of time to digest this. And - believe it or not - once upon a time (in a galaxy far far away ....) and if I remember correctly, I happened upon that SAPWIKI-article and may even have printed it out and read it. At least, it does look vaguely familiar, so I must have seen it before. Unfortunately, seeing it does not equate understanding it ? - but that would be way too easy anyway, wouldn't?!?
I too found a printout of the predecessor of that article yesterday while packing for an office move. Wow, written in 2005.
Thanks for all the comments thus far!
Let me try to summarise the gist of what I should do next based on your recommendations and my understanding of them:
Once that is done and works, I'll have to take a close look at the logic then contained in the methods as most of them will most definitely be doing a lot more than just one thing! Unfortunately, the form-routines became larger than I had planned due to the different combinations I needed to be able to tackle to check and build the transport title. My hope is that refactoring done via Eclipse will help with that so I'll wait with that until after the global class produces the same results as the function module.
So, how viable a plan is this as far as the next steps are concerned?
Cheers
Baerbel
Yes. This will make life easier (if using SAPGui SE24 ). Furthermore, you're separating completely your class from the function group. This will prevent you taking shortcuts with function group global variables etc.
No. From the off get used to programming with instances. I know people who've started with just statics, and in my opinion it hindered them from learning to think in an OO manner. There are some things that are good OO practice that you an easily move into later (like creating an interface, then implementing) - but modifying a class from static to instance is a pain.
The rest of your proposals look good.
By the way, this is what my implementation of CHECK_BEFORE_RELEASE looks like.
Thanks, Matt!
I thought that I'd be further off the mark with my bullet list so it's somewhat reassuring that this isn't the case.
My rough idea for calling the check logic is to only do it under specific circumstances like when it's a workbench request and when it's invoked in an online and not a batch process. The check logic will always return a transport title containing the required information - until it does, there's no way to back out of the pop-up logic and therefore FM. This is by design as we've tried long enough with asking people nicely to please adhere to the guidelines to not much avail.
Cheers
Baerbel
Sounds like a plan! Sweet and simple, exactly as it should be: KISS and YAGNI are there for a reason.
However, I do agree with Matthew Billingham - static classes are the root of all evil 🙂
Do let us know how it turned out! Another blog maybe?
Here is another update:
I haven't yet embarked on this "journey" as I decided to first work my way through the recently started blog series "Getting comfortable using the Object Oriented design model with ABAP" by James E. McDonough.
Michal Lipnicki - I'm fairly certain that there'll eventually be a blog post (or "travel log") as an outcome of this adventure. I just don't yet know where it'll take me or how long it'll take?
Cheers
Baerbel
Hi Bärbel, i just want to give you some more input for your journey:
https://blogs.sap.com/2018/07/26/migration-from-function-groups-to-abap-oo-artifacts-the-manual-way-13/
Regards;
Joachim
Thanks, Joachim! I already noticed your series and started to read the first part. To not get overwhelmed and in turn confused, I'll however first try to work my way through James E. McDonough's step by step guide mentioned upthread.
Cheers
Bärbel