ABAP OO For HCM
ABAP OO For HCM
ABAP OO For HCM
Page |1
1 Contents
2 3
Version history ........................................................................................................................................... 3 Introduction ................................................................................................................................................ 4 Motivation .......................................................................................................................................... 6 Misusing abap-objects....................................................................................................................... 7 Further reading on object orientation ............................................................................................. 8 Notes .......................................................................................................................................... 8
3.1 3.2 3.3 4 5 6 7 8 9 10 10.1 10.2 11 12 12.1 12.2 12.3 13 14 14.1 15 15.1 16 16.1 16.2
3.3.1
Procedural vs. OO in HCM ......................................................................................................................... 9 Use UML Class diagrams for designing ................................................................................................... 10 Test the business logic implementing Unit tests ................................................................................ 10 Separation of concerns ............................................................................................................................ 11 GLOBAL objects VS local objects ............................................................................................................. 12 Local classes in reports ............................................................................................................................ 12 Class pools in global classes ................................................................................................................ 13 Design classes with a reasonable size ............................................................................................ 14 Use Local Classes to Modularize Class Pools ................................................................................. 14 Designing classes with correct data encapsulation ........................................................................... 15 Memory usage ..................................................................................................................................... 15 Whenever possible, use Lazy Instantiation.................................................................................... 16 Do not modify global data in methods........................................................................................... 17 Access Attributes in Methods via Selectors ................................................................................... 17 Inheritance and polymorphism........................................................................................................... 18 Static methods vs. Singleton ............................................................................................................... 20 Related guidelines............................................................................................................................ 20 Interface composition.......................................................................................................................... 22 Additional guidelines on interfaces ................................................................................................ 23 Use Inheritance Moderately ................................................................................................... 24 Data Types within classes and method parameters.......................................................................... 24 Do not use obsolete Parameter Kinds ............................................................................................ 26 Do not use obsolete Parameter Typings ........................................................................................ 27 15.1.1
21.6.1 21.6.2 21.6.3 22 22.1 22.2 22.3 22.4 22.5 22.6 22.7 22.8 22.9 22.10 22.11 22.12 22.13 22.14 23 23.1
Additional guidelines ........................................................................................................................... 48 Coding style ...................................................................................................................................... 48 ABAP Objects, Modularization, and Program Flow ....................................................................... 50 Some General Hints on Modularization ......................................................................................... 50 Declaration of the Instance Constructor ........................................................................................ 52 Control Structures............................................................................................................................ 54 Handle Exporting Parameters Passed by Reference Appropriately ............................................. 55 Jump Out of Methods Only via RETURN ........................................................................................ 56 Do not use CALL METHOD for static invoke................................................................................... 57 Type and Data Declarations ............................................................................................................ 57 Reuse Only Data Types that Exactly Match Your Needs ........................................................... 58 Use the data type ABAP_BOOL as Boolean data type .............................................................. 59 Declare Data Objects Only in Classes ......................................................................................... 59 Use the TABLES Statement Only for Dynpro Structures ........................................................... 60 Refer to Data Objects when Appropriate, refer to Data Types Otherwise ............................. 60
2 Version history
Version 1, July 2011: Released Version 2, August 2011: a. Review of chapter Global class with multiple implementations b. Review of chapter singleton vs static methods with code example c. New chapter local types / implementation includes of global classes
3 Introduction
This is a guide to assist on designing new HCM applications or enhancing existing functionalities based on object oriented programming. This guide was built based on recommendations from SAP development guidelines document and on the article State-of-the-Art ABAP1 in order to support best practices on software development between HCM teams. This guide is also helpful on solving common issues and even bad practices on application development. Currently the great majority of HCM applications are implemented based on procedural programming style mainly by reading and filling internal tables in order to achieve a specific result. There are some reasons why the procedural programming is so common among HCM applications, for example: The great majority of HCM applications are processes which must be executed in batch for a large number of employees resulting on minimum user interaction; the reports are schedule to run in background mode, results are stored in database or files, logs are printed to system spools. In many cases, the online execution serves just for testing purposes. The main user interaction application (practically the only one in HCM) is the employee masterdata transaction (PA30, PA40, etc), and is built on a module pool framework. In any case, the maintenance and new developments on this area are not so frequent or not so problematic. Most of the HCM applications and foundations were developed when the only abap programming model available was the procedural one. In most cases, the query between employee masterdata, results and customizing tables is the necessary procedure to achieve a reporting result. Current issues of procedural style: Solution design o When developing a new business rule or report the developer is guided by step-by-step internal table operations (looping at this table, what has to be done for each entry?). This leads to the basic recipe: concatenate some internal table loops until a certain level, make some IFs or CASEs, and finally handle each special case with a procedure. o At the end, the number of similar or redundant FORMS (procedures) is big. Maintenance o When a report is fresh new, the code looks clean and simple. One year later it is filled with IFs, ELSEIFs and CASES o The procedural style usually leads to the creation of long reports full of global data; when performing a correction, it is necessary to study the practically all implementation in order to ensure that this correction will not influence - or be influenced by - other procedures managing the same global data. o Since there are several FORM routines with similar code within a report, you cannot forget to check if your correction needs to be replicated in similar Forms Readability
State-of-the-Art ABAP: Guidelines for Writing Robust, Understandable, and Maintainable ABAP Programs. Andreas Blumenthal, Horst Keller - SAP NetWeaver Application Server ABAP Preprint from a series of articles published in three subsequent issues of the SAP Professional Journal, starting January/February 2006
What are the advantages of the OOP in respect to the issues of procedural style? Here are a few of them: Solution design o The development of a business rule or report is guided by modeling objects; these objects can be reused between different applications. o Class diagrams are a visual part of the design document, it facilitates the understand of the functionality Maintenance o The general solution model will remain stable. Specific case changes are minor compared to the solution model o Correcting a general functionality on the super class will automatically correct all child classes. Readability o Functionality modeling is simpler o the purpose of the functionality is evident due to its public interface(methods) o Combination between object abstraction and object polymorphism makes the purpose understanding much more easy and simple, especially for developers used with other contemporary development languages (java, C++, C#, etc...)
3.1 Motivation
This is chapter has been extracted from the article state-of-the-art-ABAP and serves as a motivation for the usage of object-oriented programming: ABAP supports a hybrid programming model. You can use an object-oriented (OO) programming model based on classes and interfaces, and you can use the more classic procedural and event-driven programming model based on function modules, subroutines, dialog modules, and event blocks. Both models can be used in parallel. You can use classes inside classic processing blocks or you can call classic procedures from methods. In ABAP Objects, SAP has implemented a cleanup of the ABAP language. Within the scope of this language cleanup, stricter syntax checks are performed in classes that restrict the usage of obsolete language elements. The stricter syntax checks usually result in a syntax which should also be used outside of ABAP Objects but where the old versions could not be forbidden for compatibility reasons. The stricter syntax check comprise Prohibiting many obsolete statements and additions Requiring many implicit syntax completions to be explicit Detecting and preventing potentially incorrect data handling You can find a complete list of all obsolete language elements that are forbidden in ABAP Objects in the ABAP keyword documentation.
Recommendation
The object-oriented programming model provided by ABAP Objects is superior to the procedural programming model, since it offers better means for 1. Data encapsulation 2. Explicit object instantiation 3. Improved code reuse via inheritance 4. Standalone interfaces 5. Explicit event raising and handling In addition to these general reasons for using ABAP Objects, three more specific reasons apply:2 1. ABAP Objects is simpler to learn and use than procedural and event-driven ABAP 2. ABAP Objects has a stricter syntax check that prevents the use of obsolete language elements
You can find a detailed description of these eight reasons and a comparison of ABAP Objects to procedural ABAP in Not Yet Using ABAP Objects? Eight Reasons Why Every ABAP Programmer Should Give it a Second Look (SAP Professional Journal, September/October 2004). As stated in that article, we believe that programming with ABAP Objects means programming in a modern style that exploits the benefits of a paradigm that was invented to solve the problems of complex software projects object orientation.
Its very bad class design. In fact, its a procedural program wrapped into a local class, which otherwise would have looked like this:
DATA: some TYPE TABLE OF ztable, variables TYPE ztable, used TYPE kunnr, as TYPE adrnr, globals TYPE adrnr.
With operational statements we denote all coding that is not declarative. Operational statements comprise all statements that can be executed. In ABAP, an operational statement is always part of the implementation of a processing block. 4 A class pool is an ABAP program that contains the definition of a single global class and can contain an arbitrary number of local classes. The class pool of a global class is generated automatically from the Class Builder tool of the ABAP Workbench when the class is created. The counterpart of class pools for global interfaces is interface pools.
But thats beside the point (I even found a worse piece of code where everything was defined as statics, which actually seems to be a quite common practice among ABAP programmers who are told to use OO but dont understand one bit of it). Anyway, the point is, I was working on another piece of code which required the functionality written inside the check method. But because it is written inside a local class, I could not access this functionality. It was not reusable. If this program would have been written with a decent OO design and global classes, I would have been able to use a domain or application service class to perform the check.
7 Separation of concerns
The business logic should be separated from presentation or logging functionality. The aim is to increase the reuse of business logic on the backend side, allowing the business logic to be reused by other technologies (or even future technologies) Currently the abap reports do not offer any reuse. The usage of the business logic is limited to the R3 reports; the communication between the business logic within reports to external services is not possible. Therefore, when developing new functionality, try to separate the object models from the business logic models and from the user interface, so that they can be reused when appropriate. For further information on separation of concerns, you can study the concepts of MVC pattern.
Guideline from SAP standard guidelines document regarding separation of concerns: Priority Strongly Recommended Rule You should always follow the concept of separation of concerns: Model your applications strictly service-oriented: always disentangle the logics for presentation, application, and database access. A software layer should never produce data that it consumes. Especially no database accesses are allowed in the layers for presentation or application and vice versa, the persistency and the application layer are
One thing to take care is that local classes of a global class cannot be statically accessed outside even in inheriting classes or on global friends classes. As a conclusion, you can use local classes in a global class to apply internal modularization thus avoiding two common bad practices: Global classes with many methods (public and private) Methods with large number of code lines (a method should not exceed 50 lines of code, as it becomes unreadable and avoids internal modularization / code reuse; see SAP development guidelines) Two additional guidelines linked to this subject extracted from the state-of-the-art abap programming:
You should design classes (and function groups where needed) with reasonable size (that is number of methods or functions). Rationale Classes (or function groups) should be dedicated to a well defined purpose, so that no large class (function group) has to be loaded while only one single component (function module) of it is used.
For purposes of internal modularization you may use local classes in class pools. Rationale This is preferable to private methods of the global class because it keeps your main classes slim and their interfaces readable.
12 Memory usage
Currently it is not technically possible to define a class destructor in abap. The memory occupied by object instances is released by the garbage collector when there are no more references pointing at the memory. The abap command CLEAR can be used to un-assign the instances reference from the reference variable. In case you work with several instances containing heavy data, you may implement a destructor method that clears all attributes data (variables and internal tables). This is useful when you know exactly that you will not need an instance anymore (call the destructor method and then clear the reference variable).
This way, only the data that has been used at least once is stored in the attribute, and further access to the Database isnt required for the same entry. Of course this solution is only feasible in the cases that at least the Bufferized Key is specified completely. Two guidelines extracted from SAP standard guidelines document:
Priority Recommendation Rule You should restrict the modification of data that are global for a method (attributes of the own class, public attributes of other classes, global data of the ABAP program) to a minimum inside the method. If you modify data that are global for a method inside a method, avoid the modification of such global data in too many methods and. use only dedicated methods that can be recognized, e.g., via their naming (set_ methods). Rationale A method should be free of side effects. Known Problems Exceptions None Priority Hint Rule You might use the appropriate selectors for accessing attributes of the same class inside the method. Rationale Distinguish attributes from the methods local data, if naming conventions are not followed or not sufficient. Known Problems Readability of code Exceptions Naming conventions are sufficient for the distinction. Bad Example METHOD meth. DATA lv_var TYPE ... mv_attr1 = gv_attr2 + lv_var. ENDMETHOD. Good Example METHOD meth. DATA lv_var TYPE ... me->mv_attr1 = class=>gv_attr2 + lv_var. ENDMETHOD.
WHEN 'B'. " similar to 'a' above, just a copy and past with a few differences PERFORM compensation_b CHANGING ls_credits ls_debits. PERFORM balance_debit_credit CHANGING ls_credits ls_debits.
WHEN 'C' OR 'D' OR 'E'. "similar to the ones above, just a copy and past with a few differences PERFORM compensation_c CHANGING ls_credits ls_debits. PERFORM balance_debit_credit CHANGING ls_credits ls_debits.
WHEN OTHERS. "... ENDCASE. PERFORM append_result USING ls_credits ls_debits. ENDLOOP. ENDLOOP. ENDDO. ENDLOOP.
Notice below, the special case for section = E. this case is only necessary for one section, but is passed by for each other section cases. This is a common situation on ERP applications implementing procedural style.
The same logic could be written in a cleaner and more reusable style by applying the OO polymorphism:
LOOP AT mt_rules ASSIGNING <ls_rules>. DO. lr_credit = mr_credits->read_entry( iv_sectn = <ls_rules>-sectn1 iv_tax_code = <ls_rules>-tax_code1 ). IF lr_credit IS INITIAL. EXIT. ELSE. lr_debit = mr_debits->read_entry( iv_sectn = <ls_rules>-sectn2 iv_tax_code = <ls_rules>-tax_code2 ).
In this case, each object (debit or credit of type A, B, C, D or E) is a child of object of a more general tax code, inheriting the general properties and overwriting each specific need. A correction or change in the implementation of one of these child objects will not cause a side effect on the others; at the same time, it is easy to create a side effect on different tax codes by modifying the
FORM balance_debit_credit
The object oriented programming paradigm is a vast theory and developers may require time to become a real user of all its techniques and advantages. For example, the following link lists a variety of design patterns for objects: http://en.wikipedia.org/wiki/Design_pattern_(computer_science) http://en.wikipedia.org/wiki/Object-oriented_programming
Another very common practice nowadays is the creation of service methods as static methods in global classes. For example, it is common to see static methods for reading data from database tables. This appears useful since the method can be called from anywhere and also allows internal buffering (defining static data within methods) avoiding redundant access to database or recalculations. A much better approach than standalone static methods is to use singleton objects. Singletons are common also in other programming languages. In abap, a singleton object is characterized for having a private constructor and a static public method that returns an instance of the object; the first time this method is called, it stores the instance in a static attribute. This static method is commonly known as factory method. Example of singleton factory method (this case is actually considered a multiton since more than one instance can be created according to the taxcode type):
METHOD get_instance. FIELD-SYMBOLS <ls_instance> TYPE lty_s_instance. DATA ls_instance TYPE lty_s_instance. READ TABLE mt_instances ASSIGNING <ls_instance> WITH KEY taxcode = iv_taxcode. IF sy-subrc <> 0. CASE iv_taxcode(1). WHEN 'A'. CREATE OBJECT lr_tax_code_impl TYPE lcl_taxcode_a. WHEN 'B'. CREATE OBJECT lr_tax_code_impl TYPE lcl_taxcode_b. WHEN OTHERS. * RAISE exception cx_invalid_taxcode. ENDCASE. ls_instance-taxcode = iv_taxcode. CREATE OBJECT ls_instance-instance EXPORTING p1, p2, p3... APPEND ls_instance TO mt_instances ASSIGNING <ls_instance>. ENDIF. rr_instance = <ls_instance>-instance. ENDMETHOD.
There are many benefits on using the singleton approach; the main one is the control of instantiation: it is possible to instantiate different instances based on the key properties of the object and keep track of all instances by using a static internal table declared locally in the static method (factory method). In this way, when the factory method is called from a program or object which already instantiated the singleton, it just returns the reference of the existing instance instead of creating a new instance. Another benefit of singleton comparing to standalone static methods is that the service method has access to all of the other (instance) methods in the class (since it is no more static). This is another point that helps the object to be better modeled within abap workbench. A class with many static methods will probably share static attributes between its methods, leading to a similar global variables approach, which will lead to procedural programming style within the methods. (Extracted from the state-of-the-art ABAP programming article):
o Release clients from activating and deactivating event handlers Other recommendations for the practical use of ABAP Objects that give your programs an adequate structure: For purposes of internal modularization, exploit local classes in class pools. This is preferable to private methods of the global class. With that, you can keep your main classes slim and their interfaces readable. For internal modularization of a class pool, prefer instance methods in local singletons6 versus static methods of local classes. Two important reasons to prefer instantiation over the direct use of classes via static methods are o o That you can use inheritance and method redefinitions7, if needed, and That you can control the moment of the instantiation and the lifetime of the object.
A factory method is a method normally static of a class that creates an object of that class and returns a reference that points to that object. 6 A singleton is an object-oriented pattern. A class defined according to the singleton pattern is implemented in such a way that exactly one object can be instantiated from that class. A possible way to do so is to define a class with private instantiation and to use the CREATE OBJECT statement in the static constructor only. 7 Remember, that you cannot redefine static methods in ABAP Objects.
15 Interface composition
Interfaces are a very useful tool in order to achieve polymorphism. Within ABAP, the interfaces work very similar to classes: they are globally defined in transaction SE24 or locally within global classes or reports. It is not possible to create an interface pool inside the local types declaration of a global interface.
Remember you can inherit more than one interface in the same object and work with these interfaces independently. For example, the object lcl_example inherits the interfaces if1 and if2, the same instance is handled by two different interfaces within the program:
DATA lr_example TYPE REF TO lcl_example. DATA lr_if1 TYPE REF TO lif1. DATA lr_if2 TYPE REF TO lif2. CREATE OBJECT lr_example. lr_if1 = lr_example. lr_if1->test1( ). lr_if2 = lr_example. lr_if2->test2( ).
15.1.1 Use Inheritance Moderately Priority Recommended Rule You should use inheritance moderately. Especially you should avoid deep inheritance trees. In case of doubt, prefer interfaces to achieve polymorphism. For the reuse of interfaces, exploit the concept of interface composition in favor to inheritance. Rationale It is almost impossible to change (maintain) other than private components of superclass with many subclasses. This is especially true for classes in frameworks having subclass in different systems. Known Problems None Exceptions Application is thoroughly modeled following accepted and robust OO-design rules.
For public attributes and method parameter types (valid as of release ECC 500) o Define the types in the public section as seen in the picture below:
For public attributes and method parameter types (valid for releases 46c and 470) o Define the types in a type-pool (even if they are declared as obsolete, it is ok to do it in old/obsolete releases):
Take care that on release 46c, the changes on type pools are not supported by correction instructions!
Sometimes you will need to refer to classes definitions before they are declared (inside the local definition of global class for example); Read the abap keyword documentation for the both commands below Definition deferred Definition load
Priority Mandatory Rule If you create new function modules or add parameters to existing function modules you must not use the parameter kind TABLES. Rationale TABLES is an obsolete parameter kind for internal tables with header lines. Known Problems None Exceptions You add parameters to existing function modules that already contain TABLES parameters. Bad Example FUNCTION func. *"-------------------------------------------------------------*" TABLES
Good Example FUNCTION func. *"-------------------------------------------------------------*" CHANGING *" para TYPE table_type
Priority Mandatory Rule If you create new function modules or add parameters to existing function modules you must not type parameters with the additions LIKE or STRUCTURE. You must use TYPE instead. Rationale LIKE and STRUCTURE are obsolete additions for typing parameters, that support implicit casting of actual parameters Known Problems None Exceptions None Bad Example FUNCTION func. *"-------------------------------------------------------------*" IMPORTING *" para1 LIKE struc-comp para2 STRUCTURE struc. Good Example FUNCTION func. *"-------------------------------------------------------------*" IMPORTING *" para1 TYPE struc-comp para2 TYPE struc.
Rule You should always type formal parameters as specific as possible. Avoid to use the most generic types as any or REF TO object. If you do not want to offer generic services, you should always type completely. For GET methods, for performance reasons array interfaces can be appropriate to read more than just one attribute. Rationale
Release 7.1: the former button implementations is now renamed in order to comply with the new recommendation (see for example system EH5).
Release 7.1: The include local types can be found at the Goto menu and has also been renamed. The following information is displayed when these includes are accessed for the first time in transaction SE24 in new releases:
New comments inside include Local Definitions/Implementations: *"* use this source file for the definition and implementation of *"* local helper classes, interface definitions and type *"* declarations
New comment on include Class-Relevant Local Definitions: *"* use this source file for any type of declarations (class *"* definitions, interfaces or type declarations) you need for *"* components in the private section There are a few technical reasons for why is it better to define local classes in the former implementation include: Classes defined in this include can inherit and have access to the global class attributes and methods; this allows to explore additional design patterns (see next chapter) Changes on this include do not trigger a full recompilation of the global class The include of local types can be used, from now on, mainly for private data types declaration and definition/implementation of classes relevant only for the private section of the global class. Nothing has been changed from the compiling point-of-view between the releases (since 470), it regards only a better recommendation on how to use these includes (the code you developed in a specific include of the global class will always be supported in higher releases).
DATA lr_taxcode TYPE REF TO zcl_taxcode. START-OF-SELECTION. lr_taxcode = zcl_taxcode =>get_instance( iv_taxcode = 'A1' ). lr_taxcode->add_amount( iv_amount = 6000 ). lr_taxcode = zcl_taxcode =>get_instance( iv_taxcode = 'B1' ). lr_taxcode->add_amount( iv_amount = 6000 ).
See below the technical properties and source code for this example: The global class must be defined as Abstract (abstract constructor) The global class has a (public) constructor for initializing private attributes; There is no problem to define the constructor as public since the class cannot be instantiated from outside (abstract) The global class has a public factory method defined as static that controls the instantiation of the redefined local classes and returns the relevant instance. This method can also serve for singleton/multiton implementation in the case you want to create only one instance of a tax code and return that instance if already created (the instance(s) must be stored in a private static attribute/internal table) The global class has a public method add_amount which adds an amount parameter to the taxcode amount. The real implementations of tax codes inherit from the abstract tax code. Class (and local classes) source code: class ZCL_TAXCODE definition public abstract create public . public section. methods CONSTRUCTOR importing !IV_LGART type LGART . methods ADD_AMOUNT importing !IV_AMOUNT type F . methods GET_AMOUNT returning value(RV_AMOUNT) type MAXBT . class-methods GET_INSTANCE importing !IV_LGART type LGART returning
19 Using events
Events are primarily essential for user interface programming, for example, to execute some action when a button is clicked or when a screen is to be showed. Events are very important in UI programming because, for example, you cannot predict when the user will click on a certain button; therefore events are very useful to trigger actions that dont follow a specific sequence.
Example of event schema In HCM or ERP applications, the situation within reports is different. The actions and calculations must be executed in a very specific sequence. Therefore at first sight, events may not look useful for business logic programming. There are however a few useful situations where events can benefit also on BL programming, such as: Logging Collecting information from objects, like amounts, properties, even if they are in an internal table without having to loop at them Centrally handle actions performed by objects Controlling objects without having to loop at each one. The Case study chapter exemplifies the usage of events
20 Friend classes
Friend classes are allowed to access and modify the private attribute or call private methods of the other class (defined as Friend). This practice is not recommended in general since it may easily lead to a procedural programming model, where the attributes of classes are global data and methods represent the classic FORMS. The text below was extracted from the BC401 handbook: In some cases, classes have to work together so closely that one or both classes need access to the others protected and private components. Similarly, they need to be able to create instances of these other classes regardless of the visibility of the constructors. To avoid making these options available to all users, you can use the concept of class friendship. A class can provide friendship to other classes and interfaces (and hence all classes that implement the interface). This is the purpose of the FRIENDS addition of the CLASS statement and the Friends tab in the Class Builder. There, all classes and interfaces that grant friendship are listed. In principle, granting friendship is one-sided: A class granting friendship is not automatically a friend of its friends. If a class granting friendship wants to access the non-public components of a friend, this friend must also explicitly grant friendship to it.
Figure 160: Definition of Friendship Relationships The friend property is inherited: Classes that inherit from friends and interfaces containing a friend (as a component interface) also become friends. Therefore, extreme caution is advised when granting
Figure 161: Areas of Use for Friendship Relationships A typical application of the friend relationship between classes is when methods that access the same data are distributed over several classes. This common data is, however, to be protected from access by foreign classes. In such friendships, you can make the class containing the data a singleton - that is, make sure it can only be instantiated once in each program instance.
21 Case study
The case study demonstrates the usage of object oriented programming for a payroll function. The business logic in this example does not represent any real logic, but only illustrative calculations that could be confronted with real cases. The objectives in this case study are: Model business logic as object oriented Reuse of functionality Separation of concerns regarding application log (user interface) Technical targets of this case study: Modeling objects and processes Good usage of interfaces Inheritance Polymorphism Exploiting Events Data encapsulation
21.2 Solution
The implementation of this case study is composed by the following objects: Wagetype: each wagetype represent an employee specific amount, like income, tax, deductions, etc..; the wagetype is basically composed by its name and an amount.
8 9
Employees related salaries, incomes, deductions, etc... During payroll calculation, table RT contains final amounts, which are no more modified during payroll execution.
Tax wagetype: inherits from the general wagetype, but has an specific business logic to determine its tax amount based on 20% of income State-tax wagetype: inherits from the tax wagetype, having an additional logic to limit its tax amount to 100,00 and lock its modification after reaching the 100,00 limit. Table RT: an object that represents a collection of wagetypes Log handler: an object to handle the application log Interface loggable: any objects that inherit this interface become loggable by the log handler. In this case, all wagetypes and the RT table will log on the application. Payroll function: an object to control the calculation flow; For keeping the example simple, it will be a local class within a report.
21.5 Workflow:
The following tax wagetypes are instantiated: Tax /600 with amount = 0 State tax with amount /650 = 0. The wagetypes income wagetypes are instantiated: /101: with amount 1000,00 /102: with amount 2000,00 /103: with amount 3000,00 Later, the amount 1000,00 is added to /103 , and then these income wagetypes are added to RT. Finally the tax wagetypes are added to RT and the processing is finished.
This is the log written by the function for the workflow above: /600 /650 /101 /600 /650 /650 /101 /102 /600 /650 /102 /103 /103 /600 /650 /103 /650 /600 /650 /650 I I I I I I I I I I I I I I I I I I I I wt created - 0.00 wt created - 0.00 wt created - 1000.00 amount added - 200.00 amount added - 200.00 add to tax - amount limited to 100 and locked wt added to RT - /101 1000.00 wt created - 2000.00 amount added - 400.00 add to tax - wagetype is locked and cannot be modified wt added to RT - /102 2000.00 wt created - 3000.00 amount added - 1000.00 amount added - 800.00 add to tax - wagetype is locked and cannot be modified wt added to RT - /103 4000.00 add to tax - wagetype is locked and cannot be modified wt added to RT - /600 1400.00 add to tax - wagetype is locked and cannot be modified wt added to RT - /650 100.00
21.6.2 Source code of classes All source code can be accessed in development system L7D, program zgh_teste2. The source code below was printed from the SE24 transaction; there are some uppercase/lowercase formatting differences. 21.6.2.1 Interface ZIF_LOGGABLE
interface ZIF_LOGGABLE public . events LOGPOINT_ADDED exporting value(IV_MESSAGE_TEXT) type CLIKE optional value(IV_MSG_TYPE) type MSGTY optional value(IV_EVENT_NAME) type CLIKE optional value(IV_OBJECT_NAME) type CLIKE optional . endinterface.
<-the attribute is public but set as read-only, this is an ABAP feature to avoid getter in case the attribute is widely accessed outside. This should be used only for key attributes
21.6.2.3 RT class
class-pool . TYPES: BEGIN OF lty_s_rt, lgart TYPE lgart, o_wt TYPE REF TO zcl_wt, END OF lty_s_rt. TYPES lty_t_rt TYPE TABLE OF lty_s_rt. class ZCL_RT definition public
21.6.3 Payroll function source code This report source code represents the payroll function. The classes for taxes are defined locally within the report (to keep the example simple).
*&---------------------------------------------------------------------* *& Report Z_TESTE2 *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT z_test. TYPE-POOLS: abap. *----------------------------------------------------------------------* * CLASS lcl_tax_wt DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_tax_wt DEFINITION INHERITING FROM zcl_wt. PUBLIC SECTION. METHODS add_to_tax FOR EVENT wt_inserted OF zcl_rt IMPORTING ir_wt. ENDCLASS. "lcl_tax_wt DEFINITION
*----------------------------------------------------------------------* * CLASS lcl_state_tax_wt DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_state_tax_wt DEFINITION INHERITING FROM lcl_tax_wt. PUBLIC SECTION. METHODS add_to_tax REDEFINITION. PRIVATE SECTION. DATA mv_locked TYPE abap_bool. ENDCLASS. "lcl_state_tax_wt DEFINITION
*----------------------------------------------------------------------* * CLASS lcl_tax_wt IMPLEMENTATION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_tax_wt IMPLEMENTATION. METHOD add_to_tax. DATA lv_tax TYPE maxbt. IF ir_wt->mv_lgart(2) = '/1'.
*----------------------------------------------------------------------* * CLASS lcl_state_tax_wt IMPLEMENTATION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_state_tax_wt IMPLEMENTATION. METHOD add_to_tax . IF mv_locked = abap_true. RAISE EVENT logpoint_added EXPORTING iv_message_text = 'wagetype is locked and canot be modified' iv_msg_type = 'I' iv_event_name = 'add to tax' iv_object_name = mv_lgart. RETURN. ENDIF. super->add_to_tax( ir_wt ). IF me->mv_amount > 100. me->mv_amount = 100. RAISE EVENT logpoint_added EXPORTING iv_message_text = 'amount limited to 100 and locked' iv_msg_type = 'I' iv_event_name = 'add to tax' iv_object_name = mv_lgart. mv_locked = abap_true. ENDIF. ENDMETHOD. ENDCLASS. DATA DATA DATA DATA DATA "add_to_tax "lcl_state_tax_wt IMPLEMENTATION
lr_rt TYPE REF TO zcl_rt. lr_wt TYPE REF TO zcl_wt. lr_log TYPE REF TO zcl_log_handler. lr_tax_wt TYPE REF TO lcl_tax_wt. lr_state_tax_wt TYPE REF TO lcl_state_tax_wt.
START-OF-SELECTION.
CREATE OBJECT lr_rt. CREATE OBJECT lr_log. SET HANDLER lr_log->handle_message FOR ALL INSTANCES. CREATE OBJECT lr_tax_wt EXPORTING
CREATE OBJECT lr_state_tax_wt EXPORTING iv_lgart = '/650' iv_amount = 0. SET HANDLER lr_state_tax_wt->add_to_tax FOR lr_rt. "----------------------------CREATE OBJECT lr_wt EXPORTING iv_lgart = '/101' iv_amount = 1000. lr_rt->insert_wt( lr_wt ). "----------------------------CREATE OBJECT lr_wt EXPORTING iv_lgart = '/102' iv_amount = 2000. lr_rt->insert_wt( lr_wt ). "----------------------------CREATE OBJECT lr_wt EXPORTING iv_lgart = '/103' iv_amount = 3000. lr_wt->add_amount( 1000 ). lr_rt->insert_wt( lr_wt ). "adding taxes to RT: lr_rt->insert_wt( lr_tax_wt ). lr_rt->insert_wt( lr_state_tax_wt ).
22 Additional guidelines
22.1 Coding style
Program code must be readable and understandable by everyone who knows ABAP. If programs or part of programs are not understandable by reading the source code itself and its documentation, you must use comments. Comments should be in English and describe what a program, procedure, or part of a program is doing. If necessary, you should also comment on how the code produces it results, e.g., by including citations to literature if you implement general algorithms.
Use only the relational operators (=, <>, <, >, <=, >=), which are more readable than their respective old-fashioned character forms (EQ, NE, LT, GT, LE, GE). If available, use the addition NOT inside predicates of locigal expressions10 (e.g., dobj IS NOT INITIAL) instead of the Boolean operator NOT in front of the logical expression (e.g. NOT dobj IS INITIAL). The reason is that you naturally do the same for comparisons (dobj1 <> dobj2 instead of NOT dobj1 = dobj2), dont you? Use only the short form meth( ) for a method call. Use CALL METHOD meth for dynamic invocation only. With the short form you avoid the pollution of your source code with syntactical noise. This is especially important in object-oriented programming, where method invocations are more frequently used than, for example, function calls in procedural programming. Furthermore, you then use the same syntax for normal method calls as for functional method calls in operand positions. Use the keyword LENGTH len in type and data declarations with TYPES and DATA etc. instead of (len). The reason is to you use the same syntax in DATA and CREATE DATA. Use the equals sign (=) instead of TO and FROM when defining the parameter list in EXPORT and IMPORT. The reason is to use the same syntax for parameter lists as in all CALL statements.
In ABAP a logical expression, i.e., an expression whose result is true or false, can be written either as comparisons involving relational operators (=, <>, ) or with predicates involving special language elements (BETWEEN, IS, ). Other than the language element NOT inside the predicates of a logical expression, the Boolean Operators NOT works with the result of the expression.
10
This section gives you some recommendations on the usage of ABAP Objects, how to modularize your program and how to control the program flow. Keep an eye on the memory consumption of objects. Release unneeded objects to the garbage collector the garbage collector can only delete an object if the object is no longer referenced. Be aware that references to objects can exist in various contexts, e.g. in local fields of methods, in instance attributes of an object, in static attributes of classes, in global data objects of the program, and last but not least in event handler tables or frameworks. In the latter case be aware that eventually you must use special methods of the respective framework to free objects.11
Recommendation
Every method must be a coherent unit of work. Some very general guidelines for implementing methods are: Short is better than long Modularize dont atomize. Flat is better than deep Avoid potential side-effects From these, more practical recommendations can be derived: One-line methods should be an exception. As a rule of thumb, the number of lines should normally not be less than five, the maximum number of lines should not exceed 50, and the number of declarative statements should not exceed ten. As a broader rule of thumb, a method should fit on a single printed page. The cyclomatic number of a method (number of branches due to tests) should not be greater than ten12
In this context its worthwhile to call attention to system class CL_ABAP_WEAK_REFERENCE. Objects of that class represent weak references to an object. Unlike normal object references, a weak reference does not prevent the referenced object from being deleted when the garbage collector is executed. You might use this class if you need to create applications that simply monitor certain kinds of objects without keeping the monitored objects alive. An example for the need of such applications is the internal administration and monitoring of ABAPs shared objects in the shared objects memory. 12 When computing this number we use a very simplistic approach we dont count the WHEN conditions of a CASE structure or the ELSEIF conditions of an IF structure as individual tests, but each CASE or IF structure is counted as a single test. We are aware that the number of WHEN or ELSEIF conditions also increases method complexity and that a cyclomatic number of 10 without consideration of these conditions is a very rough criterion only.
11
All global data of the ABAP program. The declaration of global data should be avoided under any circumstances. Nevertheless, there are exceptions from this rule as the interface work areas for classic dynpros. Since operational coding is allowed in methods only, it is clear that you must modify data that are global to methods within methods. In order to keep your methods free of side effects, avoid the careless modification of such global data in too many methods. Use only dedicated methods that can be recognized, e.g., via their naming (set_... methods). This is especially important for data that do not belong to the own class. For example, the interface work area for a dynpro should be set in one dedicated method of one dedicated class and only before calling the dynpro. When you access data that are global for a method inside the method it is a good idea to distinguish them from the methods local data via the appropriate selectors: o For attributes of the own class including the protected attributes of super classes you can use the self reference me and the object component selector (->) for instance attributes or the class name and the class component selector (=>) for static attributes. Public attributes of other classes must be addressed via reference variables and object component selector (->) or class name and class component selector (=>) anyway.
o o
Global data of the program cannot be addressed with special selectors. Therefore they can be easily confused with attributes of the own class or with local data of the method. This is an additional reason not to access them in too many methods (see example below). Although we have not suggested strict naming rules, some naming conventions can also be helpful here. From the foregoing, a general rule emerges: you should restrict the scope of a method to a specific task. Refrain from working with too many attributes of the class or even global data inside a single method, otherwise the methods implementation can quickly become confusing (see example below). Note Never call a procedure without making provision for appropriate exception handling (see section 0).
Example
The following example shows the addressing of data objects within a method in a chained WRITE statement14. The identifiers a1 and a2 address the respective local data objects of method main.
The level of encapsulation in ABAP Objects is the class, not the object. Note that we use WRITE for list output here, in seeming contradiction of our recommendations in section 2.1.2. However, for simple tests and non-productive coding, WRITE can still play the same role in ABAP as, for example, System.out.println(...) does in Java.
14
13
CLASS demo DEFINITION. PUBLIC SECTION. METHODS main. CLASS-DATA a1 TYPE string VALUE `a1 class`. DATA a2 TYPE string VALUE `a2 class`. DATA a3 TYPE string VALUE `a3 class`. ENDCLASS. CLASS demo IMPLEMENTATION. METHOD main. DATA a1 TYPE string VALUE `a1 local`. DATA a2 TYPE string VALUE `a2 local`. WRITE: / a1, / a2, / demo=>a1, / me->a2, / a3, / a4. ENDMETHOD. ENDCLASS.
Context
Declare the instance constructor always in the visibility section that matches its true visibility. For a class declared with CREATE PRIVATE, the statement METHODS constructor should be placed in the PRIVATE SECTION, for CREATE PROTECTED in the PROTECTED SECTION and FOR CREATE PUBLC in the PUBLIC SECTION. Then: The visibility section of the instance constructor protocols its technical visibility. You can refer to declarations of the same section in the interface of the constructor
Recommendation
Example
The following example shows the recommended declaration of the instance constructor in a class that is declared with CREATE PRIVATE as of SAP NetWeaver 2004s. Note that a declaration in the PROTECTED or PUBLIC section is also allowed (up until SAP NetWeaver 04, only the declaration in the PUBLIC section was allowed), but that you cannot not use a private type for typing the formal parameter then.
CLASS demo DEFINITION CREATE PRIVATE. PUBLIC SECTION. ... PROTECTED SECTION. ... PRIVATE SECTION. TYPES t_... TYPE ... METHODS constructor IMPORTING p TYPE t_... ENDCLASS. CLASS demo IMPLEMENTATION. METHOD constructor. ... ENDMETHOD. ... ENDCLASS.
Parameter interfaces When defining the parameter interface of a method (or a function module), keep the following points in mind: Regarding the number of formal parameters, as a rule the parameter interface of a procedure should be slim. Ideally, a method is functional, i.e., it has no or only few importing parameters and one returning parameter. Regarding the kind of formal parameters, always use the appropriate kind. The kind of parameter defines the semantics of a parameter, while the way of passing is a more technical detail that should not be mixed up with the parameters kind. For example, do not exploit the fact that an EXPORTING parameter passed by reference behaves like a CHANGING parameter. If you need an Input/Output parameter, define it as a CHANGING parameter and use EXPORTING parameters for output only (do not read it before it has first been written).
Lets assume a structure text with at least four subsequent text-like components word1 to word4 that have the same length. The first DO loop accesses these components using the obsolete VARYING addition. Here, a data object word is used to access the contents of the components. If the structure text contains less than four of these components, the behavior can be undefined or even a runtime error might occur, if the range of text is exceeded. DO 4 TIMES VARYING word FROM text-word1 NEXT text-word2. WRITE / word. ENDDO. The second DO loop shows how the functions of the first loop can be programmed more explicitly and more generically using the statement ASSIGN INCREMENT. Here, a field symbol <word> is used to point to the components instead of copying their contents to a variable. The success of the assignment can be checked in sy-subrc and the loop is exited when the memory range of text is exceeded. DO 4 TIMES. idx = sy-index - 1. ASSIGN text-word1 INCREMENT idx TO <word> RANGE text. IF sy-subrc <> 0. EXIT. ENDIF. WRITE / <word>. ENDDO. For the second DO loop we assumed that the name but not the position of the first component in the series of four is known. If you know the position pos of the first component in the structure, you might also use the following DO loop using the statement ASSIGN COMPONENT. DO 4 TIMES. idx = pos + sy-index - 1. ASSIGN COMPONENT idx OF STRUCTURE text TO <word>. IF sy-subrc <> 0. EXIT. ENDIF. WRITE / <word>. ENDDO.
Examples
Priority Recommended Rule You should not access an EXPORTING parameter passed by reference for reading before a write access. If the first write access does not replace the contents completely you should initialize the parameter before writing to it. Rationale Before the first write access, the contents of an EXPORTING parameter passed by reference is undefined (it is the contents of the actual parameter bound to the formal parameter). Known Problems None Exceptions
Priority Recommended Rule You should use only the short form meth( ) for a method call. Use CALL METHOD meth for dynamic invocation only. Rationale This avoids the pollution of your source code with syntactical noise. Usage of the same syntax for normal method calls as for functional method calls in operand positions. Known Problems None Exceptions None Bad Example CALL METHOD meth EXPORTING i1 = lv_e1 i2 = lv_e2 IMPORTING o1 = lv_i1 o2 = lv_i2 CHANGING c1 = lv_c1 c2 = lv_c2. Good Example meth( EXPORTING i1 = lv_e1 i2 = lv_e2 IMPORTING o1 = lv_i1 o2 = lv_i2 CHANGING c1 = lv_c1 c2 = lv_c2 ).
22.10 Reuse Only Data Types that Exactly Match Your Needs
Priority Strongly Recommended Rule
Priority Recommended Rule You should refer to ABAP_BOOL from type group ABAP for working with Boolean fields instead of declaring your own data type. As values you should use the predefined constants ABAP_TRUE and ABAP_FALSE from the same type group. Rationale ABAP_BOOL is the central data type that is foreseen for Boolean values. Known Problems None Exceptions None Bad Example DATA flag TYPE c LENGTH 1. IF flag = 'X'. ... ENDIF. Good Example TYPE-POOLS abap. DATA flag TYPE abap_bool. IF flag = abap_true. ... ENDIF.
Priority Mandatory Rule You must declare data objects only as attributes of classes or as local helper variables in methods. Rationale Only data objects in classes of ABAP Objects are encapsulated properly.
22.14 Refer to Data Objects when Appropriate, refer to Data Types Otherwise
23 Error Handling
Software development and operation and ABAP is of course no exception here are intrinsically prone to errors in a number of ways: Internal errors may arise from incorrect implementations and improper usage of underlying services. When interacting with external resources (such as the user, system memory, or disk storage), errors can occur due to invalid input or unexpected resource limitations. The ABAP language and infrastructure offers different ways for handling these kinds of errors, both those that are recoverable (exceptions and dialog messages) and those that are non-recoverable (assertions and exit messages). Exceptions
Context
ABAP offers different ways to handle recoverable errors: Class-based (new) exceptions Class-based exceptions are available as of release 6.10. They replace classic exceptions and catchable runtime errors. A class-based exception is a treatable (i.e., capable of being caught) exception, which is represented by an exception object of an exception class.16 If an exception occurs, an object of an exception class is generated17 and can be propagated across call levels. The propagation behavior depends on the kind of exception class. (See sidebar: Kinds of Exception Classes). There are predefined exception classes for exceptions of the runtime environment, and selfdefined exception classes for custom applications. A class-based exception that is handled in the program does not lead to a runtime error. A class-based exception can be handled with a CATCH statement if it occurs in the TRY block of the respective TRY ENDTRY control structure.18
An exception class is a class that inherits from the predefined abstract class CX_ROOT. For performance reasons, the object is in fact only generated when its actually addressed during exception handling in the program. 18 For further information on this subject, see A Programmer's Guide to the New Exception-Handling Concept in ABAP (SAP Professional Journal, September/October, 2002)
17 16
Recommendation
As a rule, use mainly class-based exceptions. The other exceptions (classic exceptions, catchable runtime errors) are only there for backwards compatibility and are mostly superseded by class-based exceptions. Do not define classic exceptions in methods or function modules any longer. Classic exceptions still play a role in existing procedures, so you will have to deal with them when calling or modifying such procedures. It is good practice to catch classic exceptions and map them to class-based exceptions. Consider the use of MESSAGE RAISING instead of RAISE in existing procedures that are governed by classic exceptions. With MESSAGE RAISING you can pass textual information about the situation to the handler of the exception. Therefore, such messages can be seen as a precursor to exception texts in exception classes. Catchable runtime errors are now completely obsolete, since they have been replaced by corresponding exception classes. Instead of CATCH SYSTEM-EXCEPTIONS use TRY ... CATCH ... ENDTRY only. In the following, we will give you some hints on working with class-based exceptions. Creating exception classes If you discover a situation where your program might create an error adhere to the following procedure: 1. Check if an exception is appropriate In a situation where an exception is too strong, alternatives to exceptions are selfdefined return codes or flags, for example showing if a read operation was successful or not. In a situation where a treatable exception is too weak, e.g., when no recovery is possible, alternatives to exceptions are assertions (see 23.1) or exit messages (see 23.2). 2. Describe the situation
There are three kinds of exception classes: static check, dynamic check or no check, which are defined by the class super class. Static check exceptions The exception classes inherit from CX_STATIC_CHECK. Such exceptions can be propagated from a procedure, if they are explicitly declared in the interface of the procedure. This is checked during compile time. Use this kind of exception if you want the caller of a procedure to explicitly take care of the error situation. If the exception can safely be avoided by additional checks beforehand it might be better to define a dynamic check exception. Dynamic check exceptions The exception classes inherit from CX_DYNAMIC_CHECK. For propagation from procedures, the same holds as for static check exceptions with the difference, that the declaration is not checked at compile time but only after an exception has actually occurred at runtime. Use this kind for exceptions a caller usually can avoid either because they know that it cant possibly occur or that an appropriate check has been made before calling a certain routine. It is a good idea to document such preconditions for procedures. In a properly designed application, most exceptions will probably be of type dynamic check. No check exceptions The exception classes inherit from CX_NO_CHECK. Such exceptions can be propagated from a procedure without explicit declaration in the interface of the procedure. Use this kind of exception for errors where you cannot expect a caller to handle it directly. Typical examples are
19
Before inheriting from an exception class, in some cases it might even be useful to first create a new super class for related existing exception classes and to inherit the existing classes and your new one from that super class.
End of Sidebar
23.1 Assertions
Context
Assertions are closely related to error handling; with assertions you express conditions that must hold true. They can be checked at runtime and if they fail an error has been found. Assertions are available in ABAP as of Release 6.20, Support Package 29 via the ASSERT statement. An assertion is either always active or can be activated via the maintenance transaction SAAB. When a program reaches an active assertion, it evaluates the corresponding condition. If the condition is violated the program terminates with a runtime error, accesses the ABAP Debugger, or creates a log entry. The behavior is controlled by the activation settings.
Recommendation
Since assertions can be activated from outside a program and inactive assertions produce no additional load on an application, use them frequently. But do not use an assertion when actually an exception is required. Impose assertions only on conditions for program states that are fully under your control. Never formulate an assertion which is based on input parameters or the availability of some external resource. The violation of such conditions must result in an exception so that an external caller can catch it and react to it. Assertions must be always implementation-specific. Only preconditions for internally used functionality might be checked by assertions (e.g., some private method of a class, which only you can use in your implementation). Assertions are typically used for invariants and post-conditions where you are simply stating that your implementation does what you specified.
23.2 Messages
Context
Messages are basically texts that can be displayed via the MESSAGE statement as so-called dialog messages during screen (dynpro) processing. Messages can be classified with a message type that defines the display type and the program behavior (message handling). Possible message types are termination message (A), error message (E), information message (I), status message (S), or warning (W). On the application logic side, messages can be used as a surrogate for exceptions that are combined with a text. This is made available by the predefined classic exception ERROR_MESSAGE in function modules or by using the RAISING addition with the MESSAGE statement. Furthermore, there is a special message type X. When classified with X, a message is an exit message that terminates a program via a runtime error.
Messages are mainly intended for use during classic dialog processing involving dynpros, and especially during the event PAI. During PAI processing, messages of type E and W permit an error dialog when used in connection with the dynpro statement FIELD. Therefore, never use messages and in particular messages of type E or W in contexts other than PAI processing. On the application logic side, do not use messages for exceptions use class-based exceptions instead (see section 0). If existing procedures send messages, it is good practice to catch and map them to classbased exceptions. If an exception should be connected to an end-user specific dialog text, you can implement the predefined interface IF_T100_MESSAGE in the exception class (as of SAP NetWeaver 04). After catching such an exception using TRY ... CATCH ... ENDTRY you use the exception object directly in the MESSAGE statement to get a proper error message during dialog processing. Since they terminate an application without any possibility of recovery, use exit messages of type X only with extreme care. In most cases throwing an exception or using an assertion is the better approach (see 0 for when to use which of these). Cases for exit messages could be the prevention of database inconsistencies or fatal errors in the user interface. In such circumstances, an exit message might be favored over assertions because it allows you to display a meaningful message text in the short dump resulting from the runtime error. For basic application functionality that is widely reused, however, never use exit messages, since they can hinder the reuse of parts of your application to a great extent.
Recommendation