This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
ABAP Code Refactoring Techniques
Applies to: All SAP Systems supporting object oriented programming. For more information, visit the ABAP homepage.
Summary This tutorial explains the Code Refactoring Techniques based on example programs written in ABAP Objects
Author: Sukru Ilkel Birakoglu
Company: SAP LABS France
Created on: 06 July 2009
Author Bio Sukru Ilkel Birakoglu is working as Senior Support Consultant in SAP Labs France. He is developing in field of ECATT Test Tools at the moment.
Table of Contents 1. Consolidate Duplicate Conditional Fragments ...............................................................................................3 2. Preserve whole object for method calls..........................................................................................................4 3. Decompose Conditional..................................................................................................................................8 4. Extract Method..............................................................................................................................................11 5. Introduce Explaining Variable.......................................................................................................................19 6. Split Temporary Variable ..............................................................................................................................22 7. Self Encapsulate Attributes of a Class .........................................................................................................23 8. Replace Literals and Numbers in Code with Constants ...............................................................................27 9. Replace Type Codes with Subclasses .........................................................................................................29 10. Introduce Null Object ..................................................................................................................................35 11. Separate Query and Modifier Methods.......................................................................................................42 12. Parameterize Method .................................................................................................................................45 13. Pull up Field in the Class Hierarchy............................................................................................................49 14. Pull up Method in the Class Hierarchy .......................................................................................................53 Copyright...........................................................................................................................................................56
1. Consolidate Duplicate Conditional Fragments Explanation: If the same code fragment is used in all branches of a conditional expression then move it outside the expression
Motivation:
• The code fragment which is executed in every branch of a conditional expression causes confusion since it is normally not dependent on any condition
• Repetitive code means extra code to maintain if it is not encapsulated properly
Example Code Fragment:
Code Before Code Refactoring *&---------------------------------------------------------------------* *& Report ZSB_DUPL_COND_FRAGMENTS *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_dupl_cond_fragments. DATA : gv_price TYPE i VALUE 150, gv_discount TYPE p DECIMALS 2. IF gv_price > 100. gv_discount = gv_price * '0.2'. WRITE gv_discount. "=>Duplicate Code ELSE. gv_discount = gv_price * '0.1'. WRITE gv_discount. "=>Duplicate Code ENDIF.
Code After Code Refactoring REPORT zsb_dupl_cond_fragments_rf. DATA : gv_price TYPE i VALUE 150, gv_discount TYPE p DECIMALS 2. IF gv_price > 100. gv_discount = gv_price * '0.2'. ELSE. gv_discount = gv_price * '0.1'. ENDIF. WRITE gv_discount.
In this refactoring, the common code fragment WRITE gv_discount. is moved outside the conditional expression.
2. Preserve whole object for method calls Explanation: If you are using many attributes of an object instance as parameters of a method call you can pass the whole object as a parameter instead of the attributes
Motivation:
• Increases the readability of clients calling the method with many parameters
• Simplifies the signature of the called method
Steps of Refactoring:
• Find out the method(s) to which many attributes of an object instance is being passed as parameter
• Delete these parameters from the signature of the method and add a new reference parameter which references to the class of object instance holding the attributes
• In the body of the method replace the references to old parameters with references to the attributes of the object instance passed as parameter
Example Code Fragment:
Code Before Code Refactoring *&---------------------------------------------------------------------* *& Report ZSB_PRESERVE_WHOLE_OBJECT *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_preserve_whole_object. *----------------------------------------------------------------------* * CLASS lcl_order DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_order DEFINITION. PUBLIC SECTION. METHODS : constructor IMPORTING iv_id TYPE char5 iv_material_id TYPE char10 iv_quantity TYPE i iv_unit TYPE char3 iv_unit_price TYPE i. DATA : mv_id TYPE char5 READ-ONLY, mv_material_id TYPE char10 READ-ONLY, mv_quantity TYPE i READ-ONLY, mv_unit TYPE char3 READ-ONLY, mv_unit_price TYPE i. ENDCLASS. "lcl_order DEFINITION *----------------------------------------------------------------------* * CLASS lcl_order_collector DEFINITION *----------------------------------------------------------------------* *
CodeAfter Code Refactoring *&---------------------------------------------------------------------* *& Report ZSB_PRESERVE_WHOLE_OBJECT_RF *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT ZSB_PRESERVE_WHOLE_OBJECT_RF. *----------------------------------------------------------------------* * CLASS lcl_order DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_order DEFINITION. PUBLIC SECTION. METHODS : constructor IMPORTING iv_id TYPE char5 iv_material_id TYPE char10 iv_quantity TYPE i iv_unit TYPE char3 iv_unit_price TYPE i. DATA : mv_id TYPE char5 READ-ONLY, mv_material_id TYPE char10 READ-ONLY, mv_quantity TYPE i READ-ONLY, mv_unit TYPE char3 READ-ONLY, mv_unit_price TYPE i. ENDCLASS. "lcl_order DEFINITION *----------------------------------------------------------------------* * CLASS lcl_order_collector DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_order_collector DEFINITION. PUBLIC SECTION. CLASS-METHODS : write_total_order_price IMPORTING io_order type ref to lcl_order. ENDCLASS. "lcl_order_collector DEFINITION *----------------------------------------------------------------------* * CLASS lcl_order IMPLEMENTATION
3. Decompose Conditional Explanation: If complicated operations are executed on each branch of a conditional statement, encapsulate these operations in separate methods
Motivation:
• Increases the readability and maintainablity of the code
• Groups complicated operations in separate logical units
• Keeps the code from deep if..else statements which makes the code error prone
Steps of Refactoring:
• Find out the conditional expressions in your code which have many complicated operations in each branch
• Create one or more methods which encapsulate these operations
Example Code Fragment:
Code Before Code Refactoring *&---------------------------------------------------------------------* *& Report ZSB_DECOMPOSE_CONDITIONAL *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_decompose_conditional. CONSTANTS : gc_person TYPE i VALUE '1', gc_company TYPE i VALUE '2'. DATA : gv_customer_1 TYPE i VALUE '1', gv_product_price TYPE i VALUE '10', gv_reduction TYPE p DECIMALS 2. IF gv_customer_1 EQ gc_person. IF gv_product_price > 8. gv_reduction = gv_product_price * '0.1'. ELSE. gv_reduction = gv_product_price * '0.05'. ENDIF. ELSEIF gv_customer_1 EQ gc_company. IF gv_product_price > 6. gv_reduction = gv_product_price * '0.2'. ELSE. gv_reduction = gv_product_price * '0.15'. ENDIF. ENDIF. WRITE / gv_reduction.
Code After Code Refactoring *&---------------------------------------------------------------------* *& Report ZSB_DECOMPOSE_CONDITIONAL_RF *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_decompose_conditional. types : gty_decimal type p decimals 2. CONSTANTS : gc_person TYPE i VALUE '1', gc_company TYPE i VALUE '2'. DATA : gv_customer_1 TYPE i VALUE '1', gv_product_price TYPE i VALUE '10', gv_reduction TYPE p DECIMALS 2. *----------------------------------------------------------------------* * CLASS lcl_reduction_calculator DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_reduction_calculator DEFINITION. PUBLIC SECTION. CLASS-METHODS : get_person_reduction IMPORTING iv_product_price TYPE i RETURNING value(rv_reduction) TYPE gty_decimal, get_company_reduction IMPORTING iv_product_price TYPE i RETURNING value(rv_reduction) TYPE gty_decimal. ENDCLASS. "lcl_reduction_calculator DEFINITION *----------------------------------------------------------------------* * CLASS lcl_reduction_calculator IMPLEMENTATION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_reduction_calculator IMPLEMENTATION. METHOD get_person_reduction. IF iv_product_price > 8. rv_reduction = iv_product_price * '0.1'. ELSE. rv_reduction = iv_product_price * '0.05'. ENDIF. ENDMETHOD. "get_person_reduction
4. Extract Method Explanation : If the code in the body of a method is too long, you use too many local variables in the method and you have long comments for certain code fragments in the code, turn that fragment of code into its own method
Motivation:
• Increases the readability of higher level and client methods using this code.
• The code fragment turned into a method can be reused
• Code fragments which are turned into a method can be overridden in a subclass if overriding is foreseen for this fragment of code
Steps of Refactoring:
• Find out the code fragments which can be encapsulated into new methods
• Create ea new method with an explanatory name and copy the code fragment into the body of the newly created method
• The parameters and local variables of the new method consist of the local variables referenced in the old code fragment. Create the parameters and the local variables of the new method based on this information.
Example Code Fragment:
Code Before Code Refactoring REPORT zsb_extract_method. *----------------------------------------------------------------------* * CLASS lcl_bank DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_bank DEFINITION. PUBLIC SECTION. TYPES : ty_interest TYPE p LENGTH 8 DECIMALS 3. CLASS-METHODS : calculate_interest IMPORTING iv_capital_amount TYPE i iv_interest_rate TYPE i iv_no_of_months TYPE i RETURNING value(rv_interest_amount) TYPE ty_interest. ENDCLASS. "lcl_bank DEFINITION *----------------------------------------------------------------------* * CLASS lcl_bank IMPLEMENTATION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_bank IMPLEMENTATION. METHOD calculate_interest. DATA : lv_last_capital TYPE ty_interest, lv_interest_in_percent TYPE ty_interest, lv_interest_amount TYPE ty_interest.
Code After Code Refactoring REPORT zsb_extract_method. *----------------------------------------------------------------------* * CLASS lcl_bank DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_bank DEFINITION. PUBLIC SECTION. TYPES : ty_interest TYPE p LENGTH 8 DECIMALS 3. CLASS-METHODS : calculate_interest IMPORTING iv_capital_amount TYPE i iv_interest_rate TYPE i iv_no_of_months TYPE i RETURNING value(rv_interest_amount) TYPE
The method CALCULATE_INTEREST of class LCL_BANK consists of three main block which write the header data, calculates the interest amount and write the footer data. These three code fragments were extracted into their own methods for better readablity of code.
Step 3: In the next screen of the refactoring assistant , enter the name of the method which will be added to the class and have the code fragment we selected as its body. You can also select the visibility area of the class to which the method will be added
Step 4: Check the data on the screen and press the Continue button to finish the code refactoring
Step 5: After you create methods for all code fragments you want to extract some local variables of the method CALCULATE_INTEREST becomes obsolete. These obsolete variables can aslo be deleted automatically using the refactoring assistant. Right click the mouse button in the body of the method of which useless local variables and follow the path Refactoring->Delete Unused Data Declarations.
As a result, the local variables lv_last_capital, lv_interest_in_percent and lv_interest_amount are deleted in method CALCULATE_INTEREST.
5. Introduce Explaining Variable Explanation: If you have a complicated expression as a part of a conditonal expression or computation put the results of this expression in a temporary variable which explains the purpose and semantic of the expression
Motivation:
• Increases the readability of complex expressions
• Simplifies the conditional expressions
• Breaks down long computations into understandable and manageable units
Steps of Refactoring:
• Declare a new variable for assigning the value of the result of the complex expression and replace all occurrences of the complex expression with the new variable
• Compile your code and run your ABAP Units
Example Code Fragment:
Code Before Code Refactoring REPORT zsb_introduce_variable. *----------------------------------------------------------------------* * CLASS lcl_price_calculator DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_price_calculator DEFINITION. PUBLIC SECTION. CLASS-METHODS : calculate_total_price IMPORTING iv_product_name TYPE string iv_no_of_products TYPE i iv_customer_type TYPE string EXPORTING ev_total_price TYPE p. CONSTANTS : BEGIN OF c_product_name, computer TYPE string VALUE 'COMPUTER', book TYPE string VALUE 'BOOK', END OF c_product_name, BEGIN OF c_customer_type, frequent TYPE string VALUE 'FREQUENT', rare TYPE string VALUE 'RARE', first_time TYPE string VALUE 'FIRST_TIME', normal TYPE string VALUE 'NORMAL', END OF c_customer_type. ENDCLASS. "lcl_price_calculator DEFINITION *----------------------------------------------------------------------* * CLASS lcl_price_calculator IMPLEMENTATION *----------------------------------------------------------------------* * *----------------------------------------------------------------------*
Code After Code Refactoring REPORT zsb_introduce_variable.
*----------------------------------------------------------------------* * CLASS lcl_price_calculator DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_price_calculator DEFINITION. PUBLIC SECTION. CLASS-METHODS : calculate_total_price IMPORTING iv_product_name TYPE string iv_no_of_products TYPE i iv_customer_type TYPE string EXPORTING ev_total_price TYPE p. CONSTANTS : BEGIN OF c_product_name, computer TYPE string VALUE 'COMPUTER', book TYPE string VALUE 'BOOK', END OF c_product_name, BEGIN OF c_customer_type, frequent TYPE string VALUE 'FREQUENT', rare TYPE string VALUE 'RARE', first_time TYPE string VALUE 'FIRST_TIME', normal TYPE string VALUE 'NORMAL', END OF c_customer_type. ENDCLASS. "lcl_price_calculator DEFINITION
*----------------------------------------------------------------------* * CLASS lcl_price_calculator IMPLEMENTATION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_price_calculator IMPLEMENTATION. METHOD calculate_total_price. DATA : lv_is_risky_sell TYPE abap_bool, lv_is_big_sell TYPE abap_bool, lv_is_ordinary_sell TYPE abap_bool, lv_base_price TYPE i, lv_discount TYPE i, lv_delivery_costs type i. lv_is_risky_sell = boolc( iv_product_name = c_product_name-computer AND iv_customer_type = c_customer_type-first_time AND iv_no_of_products > 1 ). lv_is_big_sell = boolc( iv_product_name = c_product_name-computer AND iv_no_of_products > 10 ). lv_is_ordinary_sell = boolc( iv_product_name = c_product_name-book AND iv_no_of_products < 200 ).
The method CALCULATE_TOTAL_PRICE of class LCL_PRICE_CALCULATOR is used for calculating the total price of an order given by a customer. As it can be easily understood from the code, the total price of the order depends on the order size and customer type. The different combinations of order size and customer type were expressed in complicated conditional statements. These complicated conditional expressions were assigned to temporary variables which explain the semantic meaning of the conditional expression. The price calculation was also separated into to parts, which are base price and discount amount, this makes also the code easier to understand and maintain.
6. Split Temporary Variable Explanation: If you have a temporary local variable to which values are assigned in different contexts and this variable is not a collecting variable or loop variable, make a separate temporary variable for each assignment
Motivation:
• Assigning different responsibilities to the same temporary variable confuses the reader of the code . Using separate temporary variables for different responsibilities remedies this problem
• Initialization problems which occur because of using the same temporary variable in different contexts can result in bugs
Steps of Refactoring:
• Declare a new variable for each use of a temporary variable which occurs in different contexts of code and has another semantic meaning in each context
• Replace the occurrences of the temporary variable with the new variables
• Compile your code and run your ABAP Units
Example Code Fragment:
Code Before Code Refactoring REPORT zsb_split_variable. DATA : lv_temp TYPE i, lv_no_of_units TYPE i VALUE 10, lv_unit_price TYPE i VALUE 2, lv_no_of_workers TYPE i VALUE '40', lv_worker_salary TYPE i VALUE '800'. lv_temp = lv_no_of_units * lv_unit_price. WRITE / lv_temp. lv_temp = lv_no_of_workers * lv_worker_salary. WRITE / lv_temp.
Code After Code Refactoring REPORT zsb_split_variable. DATA : lv_total_price TYPE i, lv_total_salary TYPE i, lv_no_of_units TYPE i VALUE 10, lv_unit_price TYPE i VALUE 2, lv_no_of_workers TYPE i VALUE '40', lv_worker_salary TYPE i VALUE '800'. lv_total_price = lv_no_of_units * lv_unit_price. WRITE / lv_total_price. lv_total_salary = lv_no_of_workers * lv_worker_salary. WRITE / lv_total_salary.
In the first report, the temporary variable lv_temp is used for assigning the total value of an order given and the total salary of workers in the company. Since the name lv_temp does not have any meaning and occurs in two different contexts with two totally different meanings, it can be diffocult for the reader of the code to understand the meaning of this variable. In the second report, this temporary variable is replaced with two variables with names lv_total_price and lv_total_salary and this change makes this fragment of code clearer.
7. Self Encapsulate Attributes of a Class
Explanation: If you are accessing the attributes of a class inside and outside of the class directly and it results in coupling between different code fragments, you can encapsulate the attributes in setter and getter methods
Motivation:
• Hides the details of accessing or initialization of attributes
• Decouples client from the server with respect to the usage of attributes
• Allows subclasses of the server class to override the initialization or reading strategy of attributes
Steps of Refactoring:
• Create Setter and Getter methods for the attributes of the class
• Find all references to the attributes of the class and replace them with setter or getter method calls
• Change the visibility of your attributes to protected / private according to your needs
• Compile your code and run your ABAP Units
Code Before Code Refactoring &---------------------------------------------------------------------* *& Report ZSB_REFACT_ENCAPSULATE_ATTR *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_refact_encapsulate_attr. *----------------------------------------------------------------------* * CLASS lcl_customer DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_customer DEFINITION. PUBLIC SECTION. TYPES : ty_cust_id TYPE n LENGTH 10. CONSTANTS : c_customer_person TYPE i VALUE 1, c_customer_company TYPE i VALUE 1. DATA : mv_id TYPE n LENGTH 10, mv_type TYPE i, mv_adress TYPE string,
*& *&---------------------------------------------------------------------* REPORT zsb_refact_encaps_attr. *----------------------------------------------------------------------* * CLASS lcl_customer DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_customer DEFINITION. PUBLIC SECTION. TYPES : ty_cust_id TYPE n LENGTH 10. CONSTANTS : c_customer_person TYPE i VALUE 1, c_customer_company TYPE i VALUE 1. DATA : mv_id TYPE n LENGTH 10, mv_type TYPE i, mv_adress TYPE string, mv_name TYPE string, mv_surname TYPE string. METHODS : constructor IMPORTING iv_id TYPE ty_cust_id iv_type TYPE i iv_name TYPE string OPTIONAL iv_surname TYPE string OPTIONAL iv_adress TYPE string, set_type IMPORTING iv_type TYPE i, set_name IMPORTING iv_name TYPE string, set_surname IMPORTING iv_surname TYPE string, set_adress IMPORTING iv_adress TYPE string, get_type RETURNING value(rv_type) TYPE i, get_name RETURNING value(rv_name) TYPE string, get_surname RETURNING value(rv_surname) TYPE string, get_adress RETURNING value(rv_adress) TYPE string. ENDCLASS. "lcl_customer DEFINITION *----------------------------------------------------------------------* * CLASS lcl_customer IMPLEMENTATION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_customer IMPLEMENTATION. METHOD constructor. mv_id = iv_id. set_type( iv_type ). set_adress( iv_adress ). set_name( iv_name ). set_surname( iv_surname ).
8. Replace Literals and Numbers in Code with Constants Explanation: If you are using numbers and constants in your code with a specific meaning , you can create constants named after these meanings and replace the literals and numbers in your code with these constants
Motivation:
• Constants help to figure out the meaning of numbers and literals in code
• Makes it easier to make changes if you need to change the value of these numbers or literals in your code. You have to change the value only at the constant definition
Steps of Refactoring:
• Declare a constant and set it the value of the literal / number used in code
• Find all occurrences of the number / literal in code and replace them with the constant
• Compile your code and run your ABAP Units
Code Before Code Refactoring *&---------------------------------------------------------------------* *& Report ZSB_REPLACE_LITERALS *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_replace_literals. DATA : lv_unit_price TYPE i, lv_number_of_units_sold TYPE i, lv_customer_type TYPE c LENGTH 1, lv_total_price TYPE i. lv_customer_type = 'P'. lv_number_of_units_sold = 20. IF lv_customer_type EQ 'P'. lv_unit_price = 10. ELSEIF lv_customer_type EQ 'C'. lv_unit_price = 9. ELSEIF lv_customer_type EQ 'U'. lv_unit_price = 6. ENDIF. lv_total_price = lv_unit_price * lv_number_of_units_sold. IF lv_customer_type EQ 'P'. WRITE :/ 'The total price for a person is ', lv_total_price. ELSEIF lv_customer_type EQ 'C'. WRITE :/ 'The total price for a company is', lv_total_price. ELSEIF lv_customer_type EQ 'U'. WRITE :/ 'The total price for a university is', lv_total_price. ENDIF.
Code After Code Refactoring *&---------------------------------------------------------------------* *& Report ZSB_REPLACE_LITERALS *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_replace_literals. CONSTANTS : c_customer_person TYPE c LENGTH 1 VALUE 'P', c_customer_company TYPE c LENGTH 1 VALUE 'C', c_customer_university TYPE c LENGTH 1 VALUE 'U'. DATA : lv_unit_price TYPE i, lv_number_of_units_sold TYPE i, lv_customer_type TYPE c LENGTH 1, lv_total_price TYPE i. lv_customer_type = c_customer_person. lv_number_of_units_sold = 20. IF lv_customer_type EQ c_customer_person. lv_unit_price = 10. ELSEIF lv_customer_type EQ c_customer_company. lv_unit_price = 9. ELSEIF lv_customer_type EQ c_customer_university. lv_unit_price = 6. ENDIF. lv_total_price = lv_unit_price * lv_number_of_units_sold. IF lv_customer_type EQ c_customer_person. WRITE :/ 'The total price for a person is ', lv_total_price. ELSEIF lv_customer_type EQ c_customer_company. WRITE :/ 'The total price for a company is', lv_total_price. ELSEIF lv_customer_type EQ c_customer_university. WRITE :/ 'The total price for a university is', lv_total_price. ENDIF.
Explanation of Refactoring Done in Code:
The unit price of a product sold depends on the type of customer buying it. In the first report, the type of customers are represented by literals P, C and U for Person, Company and University. In the second report, constants are defined at the beginning of the report to represent customer types. By doing this change in code, we make our program more readable and the constants we have created give us more information about the semantic meaning of the literals used in code. Another advantage is that, we can change the constant value at one place in code and it would take affect on whole program in one go.
9. Replace Type Codes with Subclasses Explanation: If you have an immutable type code which affects the behavior of a class, you can replace the type code with subclasses
Motivation:
• Type codes result in many case-endcase or if-endif statements in code which makes it difficult to handle and maintain
• Encapsulating the different behavior in different classes makes the code easier to extend
Steps of Refactoring:
• For each value of a type code create a separate subclass
• Move the related code for each type code into the corresponding subclass
• Remove the type code field from the superclass
• Example Code Fragment:
In the example application, the types of vehicles will be first handled using a type code and then by using subclasses.
*----------------------------------------------------------------------* CLASS lcl_vehicle DEFINITION. PUBLIC SECTION. CONSTANTS : c_car TYPE i VALUE 1, c_truck TYPE i VALUE 2, c_bus TYPE i VALUE 3. METHODS : constructor IMPORTING iv_vehicle_type TYPE i, get_vehicle_name RETURNING value(rv_vehicle_name) TYPE string, get_maximum_velocity RETURNING value(rv_maximum_velocity) TYPE i. PRIVATE SECTION. DATA : mv_vehicle_type TYPE i. ENDCLASS. "lcl_vehicle DEFINITION *----------------------------------------------------------------------* * CLASS lcl_vehicle IMPLEMENTATION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_vehicle IMPLEMENTATION. METHOD constructor. mv_vehicle_type = iv_vehicle_type. ENDMETHOD. "constructor METHOD get_vehicle_name. CASE mv_vehicle_type. WHEN c_car. rv_vehicle_name = 'Car'. WHEN c_bus. rv_vehicle_name = 'Bus'. WHEN c_truck. rv_vehicle_name = 'Truck'. ENDCASE. ENDMETHOD. "get_vehicle_name METHOD get_maximum_velocity. CASE mv_vehicle_type. WHEN c_car. rv_maximum_velocity = 200. WHEN c_bus. rv_maximum_velocity = 180. WHEN c_truck. rv_maximum_velocity = 120. ENDCASE. ENDMETHOD. "get_maximum_velocity ENDCLASS. "lcl_vehicle DEFINITION DATA : go_vehicle_1 TYPE REF TO lcl_vehicle, go_vehicle_2 TYPE REF TO lcl_vehicle, gv_vehicle_name TYPE string.
Code After Code Refactoring *&---------------------------------------------------------------------* *& Report ZSB_REPLACE_TYPE_CODE_AFTER *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_replace_type_code_after. *----------------------------------------------------------------------* * CLASS lcl_vehicle DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_vehicle DEFINITION ABSTRACT. PUBLIC SECTION. CONSTANTS: c_car TYPE i VALUE 1, c_bus TYPE i VALUE 2, c_truck TYPE i VALUE 3. METHODS : get_vehicle_name ABSTRACT RETURNING value(rv_vehicle_name) TYPE string, get_maximum_velocity ABSTRACT RETURNING value(rv_maximum_velocity) TYPE i, get_type ABSTRACT RETURNING value(rv_vehicle_type) TYPE i. CLASS-METHODS : create_vehicle IMPORTING iv_vehicle_type TYPE i RETURNING value(rv_vehicle) TYPE REF TO lcl_vehicle. PRIVATE SECTION. DATA : mv_vehicle_type TYPE i. ENDCLASS. "lcl_vehicle DEFINITION *----------------------------------------------------------------------* * CLASS lcl_car DEFINITION
DATA : go_vehicle_1 TYPE REF TO lcl_vehicle, go_vehicle_2 TYPE REF TO lcl_vehicle, gv_vehicle_name TYPE string. START-OF-SELECTION. lcl_vehicle=>create_vehicle( EXPORTING iv_vehicle_type = lcl_vehicle=>c_car RECEIVING rv_vehicle = go_vehicle_1 ). lcl_vehicle=>create_vehicle( EXPORTING iv_vehicle_type = lcl_vehicle=>c_truck RECEIVING rv_vehicle = go_vehicle_2 ). gv_vehicle_name = go_vehicle_1->get_vehicle_name( ). WRITE / gv_vehicle_name. gv_vehicle_name = go_vehicle_2->get_vehicle_name( ). WRITE / gv_vehicle_name.
Explanation of Refactoring Done in Code:
In the first report, there is only once class representing all kinds of vehicles and in the code of this class the different behaviour for each kind of vehicle is separated using case-endcase or if –else..if statements. In the second report, a factory method is added to the top level abstract vehicle class which instantiates instances of different vehicle types, in the refactored code each vehicle type is represented by a subclass of the vehicle class.
10. Introduce Null Object Explanation: If you have repeated checks for null values in the code, you can replace the null values with null objects.
Motivation:
• Repeated checks for null values in code result in too many if..else blocks and makes code difficult to understand
• The code belonging to null values is scattered in code and not encapsulated
Steps of Refactoring:
• Create null value classes as subclasses of original class
• Find out all places in code which can give out a null value and replace them so that they give out a null object instance
• Remove the condition checks for null values and delegate the calls to the null object instance
• Example Code Fragment:
In this example , the information about inhabitant of a flat will be fetched. If the flat is not rented, there will also be no information about the inhabitant of the flat.
Code Before Code Refactoring *&---------------------------------------------------------------------* *& Report ZSB_INTRODUCE_NULL_OBJECT *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_introduce_null_object. *----------------------------------------------------------------------* * CLASS lcl_person DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_person DEFINITION DEFERRED. *----------------------------------------------------------------------* * CLASS lcl_flat DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_flat DEFINITION. PUBLIC SECTION. METHODS : constructor IMPORTING io_inhabitant TYPE REF TO lcl_person iv_city TYPE string iv_country TYPE string, get_inhabitant RETURNING value(ro_inhabitant) TYPE REF TO lcl_person. PRIVATE SECTION.
IF lo_inhabitant_test IS NOT INITIAL. lv_age = lo_inhabitant_test->get_age( ). WRITE / lv_age. ELSE. WRITE / 'No age'. ENDIF.
Code After Code Refactoring &-----------------------------------------------------------------------* *& Report ZSB_INTRODUCE_NULL_OBJECT_REFC *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_introduce_null_object_refc. *----------------------------------------------------------------------* * CLASS lcl_person DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_person DEFINITION DEFERRED. *----------------------------------------------------------------------* * CLASS lcl_flat DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_flat DEFINITION. PUBLIC SECTION. METHODS : constructor IMPORTING io_inhabitant TYPE REF TO lcl_person iv_city TYPE string iv_country TYPE string, get_inhabitant RETURNING value(ro_inhabitant) TYPE REF TO lcl_person. PRIVATE SECTION. DATA : mo_inhabitant TYPE REF TO lcl_person, mv_city TYPE string, mv_country TYPE string. ENDCLASS. "lcl_flat DEFINITION *----------------------------------------------------------------------* * CLASS lcl_person DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_person DEFINITION. PUBLIC SECTION. METHODS : get_name RETURNING value(rv_name) TYPE string, get_surname RETURNING value(rv_surname) TYPE string,
In the first report, an instance of a flat instance is created and the person who is living in this flat is passed to this class as an instance of the person class. In the application part of the report, the person instance representing the inhabitant of this flat is received and the details of this person as name, surname and age are written to the screen. If there is noone living in the flat, the person instance is a null instance and we dedect this case with the help of an if..else statement in the application code. If the person instance is null, then we write the texts ‘No name’, ‘No Surname’ etc. on the screen. In the second report , we create a sublass of the person subclass which represents the null reference behaviour of person and encapsulates the values of name, surname and age for null person. In this way, we eliminate the if..else statements in code and receive the values of name, surname and age of the person regardless of it is a null reference or not.
11. Separate Query and Modifier Methods Explanation: If you have a method that returns a value and also changes the values of other variables of the object create two methods one for the accessor and one for the modifier part of the old method
Motivation:
• If a query method has no side effects you can call it as many times as you want without any problems
• The code which returns a value and changes the values of other variables can be very easily source of bugs and it would be difficult to maintain because of different roles the same method can have
Steps of Refactoring:
• Create a new method which has just the query part of the original method as its body
• Create another method which has the data changing part of the original method as its body
• Replace the calls in client application to the old method with the new query method
• Compile your code and run your ABAP Unit tests
Example Code Fragment:
In this example , a method of the customer class returns the total debt of customer object and adds the amount of order given as input to the total debt of customer
Code Before Code Refactoring
*&---------------------------------------------------------------------* *& Report ZSB_SEPARATE_QUERY_FROM_MODIF *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_separate_query_from_modif. *----------------------------------------------------------------------* * CLASS lcl_customer DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_customer DEFINITION. PUBLIC SECTION. METHODS : add_order_and_get_debt IMPORTING iv_order_amount TYPE i RETURNING value(rv_debt) TYPE i. PRIVATE SECTION. DATA : mv_debt TYPE i VALUE '1000', mv_open_order_amount TYPE i VALUE '100'. ENDCLASS. "lcl_flat DEFINITION *----------------------------------------------------------------------* * CLASS lcl_customer IMPLEMENTATION
*----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_customer IMPLEMENTATION. METHOD add_order_and_get_debt. ADD iv_order_amount TO mv_open_order_amount. ADD iv_order_amount TO mv_debt. rv_debt = mv_debt. ENDMETHOD. "add_order_and_get_debt ENDCLASS. "lcl_flat DEFINITION START-OF-SELECTION. DATA : lo_customer TYPE REF TO lcl_customer, lv_debt TYPE i. CREATE OBJECT lo_customer. lo_customer->add_order_and_get_debt( EXPORTING iv_order_amount = 50 RECEIVING rv_debt = lv_debt ). WRITE / lv_debt.
Code After Code Refactoring *&---------------------------------------------------------------------* *& Report ZSB_SEPARATE_QUERY_FROM_MODIF *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_separate_query_modif_ref. *----------------------------------------------------------------------* * CLASS lcl_customer DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_customer DEFINITION. PUBLIC SECTION. METHODS : get_debt RETURNING value(rv_debt) TYPE i, add_order_amount IMPORTING iv_order_amount TYPE i. PRIVATE SECTION. DATA : mv_debt TYPE i VALUE '1000', mv_open_order_amount TYPE i VALUE '100'. ENDCLASS. "lcl_flat DEFINITION
*----------------------------------------------------------------------* * CLASS lcl_customer IMPLEMENTATION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_customer IMPLEMENTATION. METHOD get_debt. rv_debt = mv_debt. ENDMETHOD. "add_order_and_get_debt METHOD add_order_amount. ADD iv_order_amount TO mv_open_order_amount. ADD iv_order_amount TO mv_debt. ENDMETHOD. "add_order_amount ENDCLASS. "lcl_flat DEFINITION START-OF-SELECTION. DATA : lo_customer TYPE REF TO lcl_customer, lv_debt TYPE i. CREATE OBJECT lo_customer. lo_customer->add_order_amount( EXPORTING iv_order_amount = 50 ). lv_debt = lo_customer->get_debt( ). WRITE / lv_debt.
Explanation of Refactoring Done in Code:
In the first report, the customer class has a method with name add_order_and_get_debt which adds the amount of last order given by a customer to his total open order amount and to this total debts and returns the amount of his total debts to the caller. In the second report, the customer class has two methods which separately adds the order amount and returns the total debt of a customer.
12. Parameterize Method Explanation: If you have several methods which do similar operations with different values in the message body you can create one method which uses a method parameter for different values
Motivation:
• You can decrease the number of methods in the class
• You can decrease the usage of duplicate code and reduce maintenance efforts
Steps of Refactoring:
• Create a new parameterized method which can be substituted for each repetitive method
• Replace the old method calls in client application to the old methods with the new parameterized method
• Compile your code and run your ABAP Unit tests
Example Code Fragment:
In this example , the price of a product is being increased by a given percent.
Code Before Code Refactoring *&---------------------------------------------------------------------* *& Report ZSB_PARAMETERIZE_METHOD *& *&---------------------------------------------------------------------* *& *& *&---------------------------------------------------------------------* REPORT zsb_parameterize_method. TYPES : ty_price TYPE p DECIMALS 2. *----------------------------------------------------------------------* * CLASS lcl_product DEFINITION *----------------------------------------------------------------------* * *----------------------------------------------------------------------* CLASS lcl_product DEFINITION. PUBLIC SECTION. METHODS : constructor IMPORTING iv_name TYPE string iv_price TYPE p, get_price RETURNING value(rv_price) TYPE ty_price, increase_price_10_percent, increase_price_20_percent. PRIVATE SECTION. DATA : mv_price TYPE ty_price, mv_name TYPE string. ENDCLASS. "lcl_product DEFINITION
In the first report, the increase rate is part of the method body and in the second report the method which increases the product price receives the increase rate as an input parameter.
13. Pull up Field in the Class Hierarchy Explanation: If you have more than one subclasses of a class which have the same field, move the field to the superclass
Motivation:
• Prevents usage of different fields for the same purpose in subclasses
Example Code Fragment:
Since the refactoring is simple, no example code is given. In the example given below in the ABAP Workbench Support part of this refactoring, we have a class named ZCL_VEHICLE which is the superclass of classess ZCL_BUS and ZCL_TRUCK. Both subclasses ZCL_BUS and ZCL_TRUCK have member attribute MV_SPEED which shows the current speed of a Bus or Truck instance. Since this member attribute has the same type and semantic meaning in both of the subclasses of ZCL_VEHICLE, we can move this member attribute to the superclass.
ABAP Workbench Support for this Refactoring:
Step 1: Open the class ZCL_TRUCK using transaction SE24 and activate ‘ATTRIBUTES’ tab.
Step 2: Start the Refactoring Assistant following the menu item Utilities->Refactoring Assistant
Step 3: Select the attribute MV_SPEED and drag and drop it to the class ZCL_VEHICLE in the Refactoring Assistant. You will have the following screen showing you that the member variable MV_SPEED is moved to the superclass ZCL_VEHICLE. You have to activate the class ZCL_VEHICLE.
Step 4: Check the syntax of the class ZCL_BUS in the transaction SE24 . You will have the syntax error showing that the variable MV_SPEED is already defined since we moved it to the superclass of the class ZCL_BUS.
14. Pull up Method in the Class Hierarchy Explanation: If you have more than one subclasses of a class which have methods with identical results, move these methods to the superclass
Motivation:
• Prevents to have duplicate code and duplicate maintenance in different methods
• Prevents usage of different methods for the same purpose in subclasses
Example Code Fragment:
Since the refactoring is simple, no example code is given. In the example given below in the ABAP Workbench Support part of this refactoring, we have a class named ZCL_VEHICLE which is the superclass of classess ZCL_BUS and ZCL_TRUCK. Both subclasses ZCL_BUS and ZCL_TRUCK have a method named GET_NAME which returns the name of vehicle(Truck, Bus). Member attribute MV_NAME which shows the name of a vehicle is an attribute of the verhicle class. Since the GET_NAME method is same for both of these subclasses, we can move it to the superclass ZCL_VEHICLE.
ABAP Workbench Support for this Refactoring:
Step 1: Open the class ZCL_TRUCK using transaction SE24 and activate ‘METHODS’ tab.
Step 2: Start the Refactoring Assistant following the menu item Utilities->Refactoring Assistant
Step 3:: Select the method GET_NAME and drag and drop it to the class ZCL_VEHICLE in the Refactoring Assistant. You will have the following screen showing you that the instance method GET_NAME is moved to the superclass ZCL_VEHICLE. You have to activate the class ZCL_VEHICLE.
Step 4: Check the syntax of the class ZCL_BUS in the transaction SE24 . You will have the syntax error showing that the method GET_NAME is already defined since we moved it to the superclass of the class ZCL_BUS.
Step 5: Delete the method GET_NAME from the attributes list of class ZCL_BUS and activate the class.
No part of this publication may be reproduced or transmitted in any form or for any purpose without the express permission of SAP AG. The information contained herein may be changed without prior notice.
Some software products marketed by SAP AG and its distributors contain proprietary software components of other software vendors.
Microsoft, Windows, Excel, Outlook, and PowerPoint are registered trademarks of Microsoft Corporation.
IBM, DB2, DB2 Universal Database, System i, System i5, System p, System p5, System x, System z, System z10, System z9, z10, z9, iSeries, pSeries, xSeries, zSeries, eServer, z/VM, z/OS, i5/OS, S/390, OS/390, OS/400, AS/400, S/390 Parallel Enterprise Server, PowerVM, Power Architecture, POWER6+, POWER6, POWER5+, POWER5, POWER, OpenPower, PowerPC, BatchPipes, BladeCenter, System Storage, GPFS, HACMP, RETAIN, DB2 Connect, RACF, Redbooks, OS/2, Parallel Sysplex, MVS/ESA, AIX, Intelligent Miner, WebSphere, Netfinity, Tivoli and Informix are trademarks or registered trademarks of IBM Corporation.
Linux is the registered trademark of Linus Torvalds in the U.S. and other countries.
Adobe, the Adobe logo, Acrobat, PostScript, and Reader are either trademarks or registered trademarks of Adobe Systems Incorporated in the United States and/or other countries.
Oracle is a registered trademark of Oracle Corporation.
UNIX, X/Open, OSF/1, and Motif are registered trademarks of the Open Group.
Citrix, ICA, Program Neighborhood, MetaFrame, WinFrame, VideoFrame, and MultiWin are trademarks or registered trademarks of Citrix Systems, Inc.
HTML, XML, XHTML and W3C are trademarks or registered trademarks of W3C®, World Wide Web Consortium, Massachusetts Institute of Technology.
Java is a registered trademark of Sun Microsystems, Inc.
JavaScript is a registered trademark of Sun Microsystems, Inc., used under license for technology invented and implemented by Netscape.
SAP, R/3, SAP NetWeaver, Duet, PartnerEdge, ByDesign, SAP Business ByDesign, and other SAP products and services mentioned herein as well as their respective logos are trademarks or registered trademarks of SAP AG in Germany and other countries.
Business Objects and the Business Objects logo, BusinessObjects, Crystal Reports, Crystal Decisions, Web Intelligence, Xcelsius, and other Business Objects products and services mentioned herein as well as their respective logos are trademarks or registered trademarks of Business Objects S.A. in the United States and in other countries. Business Objects is an SAP company.
All other product and service names mentioned are the trademarks of their respective companies. Data contained in this document serves informational purposes only. National product specifications may vary.
These materials are subject to change without notice. These materials are provided by SAP AG and its affiliated companies ("SAP Group") for informational purposes only, without representation or warranty of any kind, and SAP Group shall not be liable for errors or omissions with respect to the materials. The only warranties for SAP Group products and services are those that are set forth in the express warranty statements accompanying such products and services, if any. Nothing herein should be construed as constituting an additional warranty.