Nearly two years ago I had a discussion on how one could introduce Test Driven Development and unit testing into old fashioned procedural code esp. into function groups. The outcome was that a bigger refactoring of the function group into OO-artifacts would very often make sense, but there was no clarity, no hints nor best practices on how to do that.
A major issue was the poor knowledge of ABAP-OO in that group. Once again I heard the sentence: ‘We cannot use ABAP-OO as our developers do not know it.’.
Boy, how many times in my 25 years of abaping have I heard such things 😐
Irritated I started a comparison which led to a longer presentation including some demos.
As I see some blogs and discussions here, I decided to put my absolute personal and totally biased ideas 😉 from then into a small blog.
- At first I will talk about the reasons for and against a migration and an overall picture.
- Then I will handle the conversion and the reasonable and necessary code adaption.
Part 2 of 3
- Finally, the necessary adaptions in the callers to use the new code artifacts are described.
Part 3 of 3
It is absolutely clear that the process and recommendations described in this blog series will never cover 100 percent.
There is no silver bullet.
Motivation of Migration
Arguments related to testing
- Function modules and their function groups are testable, but not on the same support level as ABAP-Classes.
- Not only the testability of the function group and modules themselves are obstructed, also the tests of the callers are impeded. A mocking using interfaces is way easier than using function modules.
And by example the ABAP Test Double Framework needs global interfaces.
This differentiates the creation of classes from the usage of test-seams.
Arguments related to architecture
- “Programming against interfaces” is a lot easier with ABAP-OO.
- Function modules names are globally unique. Seeing the length of 30 characters good naming is very restricted as namespaces use lots of the name.
- Function module calling is a dynamic call, so syntax check is done not on design time but on runtime.
- The clarity of parameter types is better in methods then in function modules. No undirected parameter types like tables any more.
- Forms can be called externally , so no call restriction here, leading to high cross-linking.
- The very helpful ADT-based refactoring tools (like the extraction of code to an instantly created method to reduce code duplicates) are usable only with class based code.
- Modern and recommended SAP Frameworks like BOPF are using OO-Artifacts.
Switching from classes to procedural code, often a lot of times within the code structure, increases the amount of code complexity thus increasing the error probability.
Finally, I want to recommend an article series published by Horst Keller and Andreas Blumenthal in 2006: “An insider’s guide to writing robust, understandable, maintainable, state-of-the-art ABAP programs”; at least this part: http://www.sapwiki.cl/wiki/images/9/90/Abap_top_part_2.pdf
Discouraging of refactoring
After telling why to migrate I need to mention the arguments against the migration and of course any disruptive refactoring.
Arguments against disruptive refactoring
Enhancements and Modifications
Any refactoring has the problem that ABAP allows implicit enhancements and modifications and free usage of programming objects. By example there is no technical mean to forbid the call of any function module (apart from authorization checks, but that is checked only on run time).
In blog S4HANA Extensibility Concept this issue is also dicussed.
Unfortunately there is no cross-system general collection of enhanced programming objects.
Modifications are collected and within a system there are tools investigating the usages but if one has many customers/receivers this is not enough.
So there are two extremes:
- do not refactor anything disruptively or
- rely on the well known SPAU mechanism and a simplification/deprecation mechanism (and pray).
If you want to replace old programming objects with new programming objects starting at a new release/SP/FP you somehow put the maintenance at risk. You have to make the connection old to new transparent and you may never loose this transparency.
The same deprecation management is not only needed for maintenance but also for information management. The users calling the code to be refactored need to be activated. Once again the SPAU mechanism is relevant.
See also chapter ‘Post Generation Effort’.
Arguments against migration of Function Groups
- Not all function groups can be migrated.
- Some could be migrated after a split.
- Some may be migrated but a code adaption would connect this change to a vast amount of other programs.
- Some migration would become too costly.
So there is no one size fits all.
Generated function groups cannot be migrated. This is especially true for function groups used in Table View Maintenance. They cannot be migrated as this framework needs function groups and generates its own code.
Some content of a function group may impede a migration. The developer should think about a split of these function groups to separate the migratable parts from the prohibiting ones.
Another aproach would be to extract the content of function groups to classes and use the function modules as wrapper. This approach is thoroughly described in chapter ‘Post Generation Effort – Approach 1’.
- Function groups may have dynpros defined. ABAP-OO lacks the ability to process dynpros. This split would also follow the Model-View-Controller concept.
- Function modules having technical settings prohibiting a migration to ABAP-OO.
This would separate the Import-code from other code easing the migration from RFC-technique to ODATA.
- Update Task
Update Task is related to Database affine code areas so this should be separated to ensure DB-layering.
- Function modules used in frameworks prohibiting a migration to ABAP-OO.
- BDT and other frameworks
Reasons for not migrating migratable function groups
Some function groups may be technically migratable, but trigger a lot of effort. This may be ok if one wants to get a package free of procedural code or to get an often used callstack clean or other reasons like that. The following paragraphs are true for every disruptive refactoring, not only for the migration function group to ABAP-OO.
Massively connected function groups
If function modules are heavily used in a lot of function groups/classes/reports a migration with caller adaption would lead to a big list of changed programs.
In this case the priority of migration is to be questioned. I recommend to work on other function groups first.
Heavy usage of dynamic calls
If a function group heavily uses dynamic calls like “call function lv_function_name” or “perform (‘LV_FORMNAME’) in program (‘LV_PROGNAME’)” the investigation effort might become very high.
Better use the effort on other stakes.
OOP versus class-based code
One word about OOP. This blog is not about converting a function group into OOP.
With microservices and RESTful programming in my opinion pure OOP may not be the right direction. See https://blogs.sap.com/2017/12/07/be-prepared-for-the-new-abap-programming-model-in-sap-s4hana/
Quite in contrary; following the renaissance of functional programming style one should try to get rid of mutable static and instance attributes, thus the close coupling of data and methods.
Implementation / design patterns
Some of the following patterns refer to „Design Patterns – Elements of Reusable Software“ of the Gang of Four [GOF1995].
Some patterns need to be discussed as I follow them henceforth.
I will talk about them only briefly, and why I use them.
Static versus Instantiable Classes
Nearest to a function group would be a static class with static methods.
Some say static is the root of evil. Although I nearly absolutely second this statement I want to stress some statements on another level.
- Static methods are not redefinable which is quite useful in mocking techniques.
- Static variables 1: They are the oposite of side effect free programming as they are valid cross-instance. So even in instantiable classes they should be thoroughly checked to get rid of them.
- Static variables 2: There is one exception: If you have a factory class managing instances. Then the managing table of instances has to be static.
I decided for myself not to use static classes at all.
Beside a static class next nearest thing to a function group is a class with instance components using the singleton pattern.
But there are some disadvantages of singleton, as it is problematic for side-effect-free programming. Instance variables are becoming variables with a static behaviour.
Personally I do not recommend nor discourage it in the first step of migration.
But one should question the usage to ease side-effect-free programming.
Program to an interface, not implementation.
In my point of view this is all about stability, robustness and reliability. I want to rely on a stable API, not on an implementation. Apart from a far better testability, this allows also better code evolution, multi-team-development and parallel development.
This is not only true for my program, but also for the callers of my program. They can start developing right after definition of the interface. They can implement their stubs and mocks to be used for their program.
Best way to build a clear facade/contract to my callers is to have one or more interfaces. I strongly recommend to have separate interface for each group of callers. By example one could think of creating an interface for all the read-only callers and one for callers using write methods.
But for this task ( migration ) a 1:1 interface is sufficient.
This is directly connected to the interface. If I want to rely only on an interface the caller should not be capable to instantiate the class but use a factory method to get a reference to an interface he needs.
Factory methods (versus factory classes)
In ‘private instantiation’ the use of a factory method is requested. But there are reasons to use a factory class instead.
In this document i restrict myself to a factory method to ease understanding.
Side-effect free programming
This is often claimed as one of the main advantages of functional programming (languages), but I interpret it also as prerequisite for FIRST class tests. If a test should be repeatable side-effects like database contents, configuration, stateful variables et al. are to be avoided.
Especially all about buffering of configuration et al. should be extracted into a separate (local) class.
Migration patterns at a glance
The components of a function group could be matched to components of Interfaces and classes. Henceforth I describe shortly, which components would be migrated or could be compared to the other.
|Function Group Component||OO Component||Comment|
|Function Modules (Signature)||Interface Instance Methods|
|Form Routines||Private Instance Methods|
|Events (Load-of-program)||(Class Constructor)||I don’t recommend that, as most LOPs i see contain code to read configuration, which should be extracted into a separate configuration access class.|
|Global Variables||Private Instance Attributes or
Private Static Attributes
Check if they are really needed, not for buffering configuration by example
Depends on usage of Singleton.
If singleton is used no static attributes are needed at all.
Reduce the amount of attributes as much as possible.
|Statics||Private Static Attributes||Check if they are really needed. Often they are used for configuration buffering, too
If this is true=>config access class.
|Constants / Types||
|Macros||Macros||Try to create a separate class|
|Local test classes in *T99-include||Local test class|
|Local classes||Local classes|
The parameters should get the same names in the first refactoring step ignoring any naming conventions.
|Function module||Form routine||Method||Comment|
|Using||Importing||This may lead into problems, as though the distinction between using and changing is recommended for decades(sic!) it is not forced. It may occur that using parameter values are changed which is not allowed for importing parameters in methods.|
If the tables parameter is defined via a structure a new table type (standard table type with default key) needs to be created either in interface or in the class and used instead of the old parameter type.
Tables-parameter have no direction. Therefore i put them to changing at first sight, but one should investigate, if importing, exporting or returning wouldn’t be suitable.
|Exceptions||They should be converted to a class-based exception having all the exceptions of the function group as constants. This exception class is then used in the exception raising of the method|
|Changing||Exporting||Arguable, may be better converted to changing or returning|