Migration from Function Groups to ABAP-OO Artifacts; the manual way 2/3
This is part 2 of my series regarding migration function groups.
Starting Blog is Part 1 of 3
In this part I will handle the conversion and the reasonable and necessary code adaptions.
Exception class
Classic exceptions should be converted to class-based exceptions referring to a new exception class.
I use the ‘message’ addition of raise exception, so I do not need additional attributes for the message variables.
The exception classes in my example do no really differ any more, so one could think about using one exception class for a package.
Example
Signature of a function module | Exception class |
FUNCTION zjg_fm1_1. *”———————————————————— *” IMPORTING *” REFERENCE(IV_GUID) *” TYPE GUID_16 *” DEFAULT ‘1234567812345678’ *” REFERENCE(IF_FLAG) TYPE CHAR_01 *” TABLES *” RETURN STRUCTURE BAPIRET2 *” EXCEPTIONS *” NOT_FOUND *” SOME_ERROR *”———————————————————— |
|
Interface
The interface should contain all method definitions for all function modules of the function group together with the related type definitions for tables parameter and constants used as default values.
So apart from DB-Entries every type and constants used in the methods should be defined here. Even if they are simply referring to global constant-/type interfaces. Hereby the caller has a clear contract what is to be expected.
I even recommend to define own types to define exactly the contract between caller and callee.
Naming
As the methods are now not globally unique any more one should follow the clean code rule to name the method describingly.
Example
A function group has one function module ZJG_FM1_1 as defined below.
In the function module the parameter if_flag should contain only ‘X’ or ‘ ‘.
Function module name ZJG_FM1_1 could be replaced by its true meaning: send message to other systems for a contract defined by the GUID.
=>send_message_for_contract.
Function Group | Interface |
in top include
|
|
Class
Instantiation
Class with private instantiation (singleton if wished)
A factory method returning a reference to the interface should be created.
Example with singleton
class zjg_cl_new definition public create private final.
public section.
interfaces zjg_if_new.
class-methods: s_get_instance
returning
value(rr_instance) type ref to zjg_if_new.
private section.
data sr_instance type ref to zjg_cl_new.
endclass.
class zjg_cl_new implementation.
method s_get_instance.
if sr_instance is not bound.
sr_instance = new zjg_cl_new( ).
endif.
rr_instance = sr_instance.
endmethod.
method zjg_if_new~send_message_for_contract.
...do something
endmethod.
endclass.
Example without singleton
Class zjg_cl_new definition public create private final.
public section.
interfaces zjg_if_new.
class-methods: s_get_instance returning value(rr_instance) type ref to zjg_if_new.
endclass.
class zjg_cl_new implementation.
method s_get_instance.
rr_instance = sr_instance.
endmethod.
method zjg_if_new~send_message_for_contract.
...do something
endmethod.
endclass.
Definition
Former Form routines.
They are to be defined as described in the overview.
Exception class used in private methods
If an exception class is generated all private methods should get an exception based on this class. This is needed because in function methods and forms there is no rule when to raise classic exception. Those could be raised everywhere in the call stack. If one wants to replace the raisings all methods in the call stack need this exception class in their method signature.
Former global variables
In first step all variables formerly defined globally should be converted into
- private instance attributes if singleton is used or
- public static attributes if singleton is not used.
But they should be checked if they are really needed. Best would be to have as few as possible.
Even if it is decided to follow the OOP and not the functional programming style one should reduce the amount of attributes by grouping them into structures.
The typing of the attributes may not abide by the rules of ABAP-OO (e.g. table with header line) so here we might have a bit of adaption effort.
Implementation
The methods defined in the interface are to be implemented.
First all implementations should be adapted to abide by the rules of ABAP-OO. This could be easy or a bit more difficult.
An easy example would be to exchange a table defined with header line into one defined as standard table.
By example statics are not allowed in instance methods so one has to decide how to get rid of them. If they are buffers for configuration, by example, on should get rid of them by having a separate configuration access class.
ABAPUnit Testclass
A local unit test class should be created.
Improving the possibility to create automated tests is one of the key reasons for this whole activity.
Facade for Dependent-On-Components(DOC)
For separation of concern, DRY and unit test mocking all calls to external content (like function modules of other function groups) should be called via a facade.
This requirement is eased by creating a local DOC-class. It consists of
- interface definition ‘lif_doc’ in class relevant local defs
- for each external function module called a method with the name of the function module should be created with transformed parameters.
- class ‘lcl_doc’ definition and implementation in local definition
- each interface method should be implemented with the call of this function module without any parameters.
- private attribute of type ‘lif_doc’
- a private attribute of the interface reference should be created.
- instantiation of this attribute in constructor method of generated class.
This approach is fully valid only for the first step of refactoring.
If the called function modules belong to a neighbor function group one should consider refactoring them, too. Then we are the callers of migrated function modules and should adapt our call to the new class/Interface.
Example
In function group ZJG_FUNCTION_GROUP three function modules of other function groups are called.
FUNCTION zjg_fm1_set_buffer.
FUNCTION zjg_fm1_1.
FUNCTION zjg_fm2.
Function Module | Signature |
zjg_fm1_set_buffer | FUNCTION zjg_fm1_set_buffer. |
zjg_fm1_1. | FUNCTION zjg_fm1_1. *” IMPORTING *” REFERENCE(IV_GUID) TYPE GUID_16 DEFAULT ‘1234567812345678’ *” REFERENCE(IV_FLAG) TYPE CHAR_01 *” TABLES *” RETURN STRUCTURE BAPIRET2 *” EXCEPTIONS *” NOT_FOUND *” SOME_ERROR |
zjg_fm2 | FUNCTION zjg_fm2. *” IMPORTING *” REFERENCE(IV_GUID) TYPE GUID_16 *” EXPORTING *” REFERENCE(RV_SUCCESS_FLAG) TYPE CHAR_01 |
Local interface Method definition |
Local class Method implementation |
Adaptions to the newly created class |
LIF_DOC | LCL_DOC | ZJG_CL_NEW |
CLASS lcl_doc DEFINITION. PUBLIC SECTION. INTERFACES lif_doc. ENDCLASS. |
CLASS ZJG_CL_NEW definition. private section. methods constructor. data mr_doc type ref to lif_doc. |
|
TYPES tt_bapiret2 TYPE STANDARD TABLE OF bapiret2 WITH DEFAULT KEY. |
||
class zjg_cl_new implementation. method constructor. mr_doc = new lcl_doc( ). endmethod. |
||
METHODS zjg_fm1_set_buffer. | METHOD lif_doc~zjg_fm1_set_buffer . CALL FUNCTION ‘ZJG_FM1_SET_BUFFER’. ENDMETHOD. |
|
METHODS zjg_fm1_1 IMPORTING iv_guid TYPE guid_16 iv_flag TYPE char_01 CHANGING return TYPE tt_bapiret2 EXCEPTIONS not_found some_error . |
METHOD lif_doc~zjg_fm1_1 . CALL FUNCTION ‘ZJG_FM1_1’ Case sy-subrc. |
|
METHOD lif_doc~zjg_fm2 . CALL FUNCTION ‘ZJG_FM2’ EXPORTING iv_guid = iv_guid IMPORTING rv_success_flag = rv_success. ENDMETHOD. |
Legend
Type TT_BAPIRET2 created in interface to be used in method signature. See green markings.
Code Adaptions
In the next chapters i do not talk about the code adaptions triggered by ABAP-OO itself. As ABAP-OO is a lot stricter than procedural ABAP there is always some effort of adaption this covers change of local tables getting rid of header lines, field-symbols defined globally, other typing rules etc.
The effort occurring here depends a lot on the way programs were written. Some language adaptions are easy others not.
Instead the following chapters describe the code adaptions triggered by the refactoring itself, like the change to exception classes.
Call replacement
The newly created methods for form routines and function modules need to be called. Also the raisings of classical exceptions in the former function modules and forms need to be replaced with the raise of the new exception class.
Replacement of proprietary calls
The calls of the former form routines and function modules being proprietary of the function group should be replaced by the calls of the newly created methods.
Example
The code of method zjg_if_new_class_wrapp6~zjg_fm6_1 should be adapted to call the new methods zjg_if_new_class_wrapp6~zjg_fm6_2 and f01.
The change of call of former form routines is also a change of paradigm. Form routines have a parameter call/definition by position, function modules and methods by keyword. So on change of call it has to be adapted to parameter by name behavior. See also Hardgrave. (1976). Positional versus keyword parameter communication. ACM SIGPLAN Notices, 52-58.
The change of tables parameters to changing parameters makes this worse.
Form routines are called with parameter sequence tables, using, changing, raising.
Methods are called with a parameter sequence exporting, importing, changing, returning, raising.
See blue marking in code example.
Restrictions
Dynamic calls often prohibit an easy replacement. See yellow marking in code example.
Worst situation is if there is a call function with dynamic function module name and parameter table.
Here one have to investigate thoroughly in which cases which function module with which parameters is called.
Example: i once saw a function module having two parameters which were not allowed to be used simultaniously. Get and change-function module had the same signature.
One developer decided to forgo this situation by
- using fully dynamic call and
- filling the parameter table case-driven.
Replacement of external calls
The calls of the external function modules should be replaced by the calls of the DOC-methods.
See green marking in code example.
Example
Old Function Module | New Method |
ZJG_FM6_1 | zjg_if_new_class_wrapp6~zjg_fm6_1 |
FUNCTION ZJG_FM6_1. DATA lt_rettab TYPE bapirettab. CALL FUNCTION ‘ZJG_FM1_1’ ” FG external FM CALL FUNCTION ‘ZJG_FM6_2’ ” FG propriety Fm **************************** lv_progname =
**************************** PERFORM f01 TABLES lt_01_itab lt_02_struc
ENDFUNCTION. |
METHOD zjg_if_new_class_wrapp6~zjg_fm6_1. DATA lt_rettab TYPE bapirettab. mo_doc->zjg_fm1_1( zjg_if_new_class_wrapp6~zjg_fm6_2(
*********************************** lv_progname =
********************************* f01( EXPORTING u_1 = lv_u1 ENDMETHOD. |
Legend dangerous call, to be investigated thoroughly Replaced proprietary call Replaced external call |
Replacement of exception raisings
Raise of classic exception | Raise of new exception class | |
Pattern | Raise old_exception | Raise exception type new_exception_class exporting former_exception = old_exception. |
Example | RAISE not_found. | RAISE EXCEPTION TYPE zcx_abc EXPORTING former exception = ‘NOT_FOUND’. |
Replacements of message raisings
Static message raisings should be replaced with the raise of the newly created exception class. Dynamic message raisings need to be handled manually.
Message of classic exception | Raise of new exception class | Comment | |
Pattern | Message t100 with v1 v2 v3 v4 raising old_exception. | Raise exception type new_exception_class message id t100-msgid type t100-msgty number t100-msgno with v1 v2 v3 v4 exporting former_exception = old_exception. |
The T100 key can be implemented in 4 different types,
|
Example | Message e001(D0) with ‘ANTON’ RAISING ERROR. | RAISE EXCEPTION TYPE zcx_abc Message ID ‘D0’ Type ‘E’ NUMBER ‘001’ With ‘ANTON’ EXPORTING former_exception = ‘ERROR’. |
Short form of T100-Key used in message |
Example |
Message TYPE ‘E’ NUMBER 001 with ‘ANTON’ RAISING NOT_FOUND. |
RAISE EXCEPTION TYPE zcx_abc Message ID ‘D01’ Type ‘E’ NUMBER ‘001’ With ‘ANTON’ EXPORTING former_exception = ‘NOT_FOUND’. |
The function group has a message ID D01 assigned. |
Pitfalls
Function modules
Parameters names
In function modules it is allowed to have an importing parameter and an exporting parameter with the very same name. This is not allowed in ABAP-OO.
Parameter typing
Parameters in function modules refer to generic types or DDIC-Types. These can be defined with ‘Like’. Parameters in method definition referring to generic or DDIC-types must be typed with ‘TYPE’. Therefore all ‘Like’s in definition of parameter of method are to be changed to ‘TYPE’s.