Wednesday 1 November 2017

The Global Variable Dilemma

When doing complex coding, there’s always the dilemma of using or non using global variables. We all know that global variables are dangerous because you never know where they are set and what value they carry in a specific method.

On the other hand, if I keep everything local, I am ending with huge lists of parameters in every method just to pass the values needed to the deepest nested method where I need it.

An example out of my daily work: I am refactoring a huge print program where a lot of global variables are set in various locations of the code. Let’s take one simple variable, which is the plant code. It is used in various methods because many utility classes I use take it as parameter. The plant is determined when the order that is to be printed is being read from db. If I make it local, I have to pass it to dozens of methods that need to know about the plant. On the other hand, if I make an instance attribute of it, I never can be sure that it is set when using it somewhere around in the code.

What I have startet to use for this case is a class I call data provider that keeps all data to be re-used throughout the code. It is instantiated at the very start of processing and keeps only data that is set once and remains set to its first value during the whole process. All attributes of that class are populated with instantiation and can not be changed any more (there are only getter methods for the values). In my main class, I instantiate the data provider as soon as possible and store the reference in an instance attribute so it is available to all methods.

Example

In this example report, a pp order is given and all occurring work centers in the operations are being determined and sent to output. In this simple program the usage of a data provider may be too much overhead, but if the program grows and you need to access the order data in many nested methods, it is as handy as global data but without the danger of overwriting it accidentally. The data provider gives also transparency about where the data comes from. Just use the where-used list and you understand that the data is provided in the constructor.

I would appreciate to know about your approaches!

*&---------------------------------------------------------------------*
*& Report  ZP_TMP_DATA_PROVIDER
*&
*&---------------------------------------------------------------------*
*& Demo for SCN blog post "The global variable dilemma"
*& This report creates a list of all work center header data
*& used in a production order
*&---------------------------------------------------------------------*
report zp_tmp_data_provider.

parameters p_ordid type aufnr.


class lcl_data_provider definition.
  public section.
    types gty_t_operations type standard table of afvc with default key.
    methods constructor importing iv_orderid type aufnr.
    methods get_header returning value(rs_res) type aufk.
    methods get_pp returning value(rs_res) type afko.
    methods get_operations returning value(rt_res) type gty_t_operations.
  private section.
    data: ms_order_header type aufk,
          ms_order_pp     type afko,
          mt_operations   type gty_t_operations.

endclass.

class lcl_data_provider implementation.
  method constructor.
    " general order data
    select single * from aufk
      where aufnr = @iv_orderid
      into @ms_order_header.
    " production-related order data
    select single * from afko
      where aufnr = @iv_orderid
      into @ms_order_pp.
    " order operations
    select * from afvc
      where aufpl = @ms_order_pp-aufpl
      into table @mt_operations.
  endmethod.

  " getter methods for read-only access to the global data
  method get_header.
    rs_res = ms_order_header.
  endmethod.

  method get_pp.
    rs_res = ms_order_pp.
  endmethod.

  method get_operations.
    rt_res = mt_operations.
  endmethod.
endclass.

class lcl_app definition.
  public section.

    types: gty_t_crhd type standard table of crhd with default key.

    methods constructor importing iv_orderid type aufnr.
    methods get_workcenter_list returning value(rt_res) type gty_t_crhd.
  private section.
    data: mo_data_provider type ref to lcl_data_provider.
    methods get_workcenter_oper
      importing is_oper type afvc
      returning value(rs_res) type crhd.
endclass.

class lcl_app implementation.
  method constructor.
    " instantiate the data provider
    mo_data_provider = new #( iv_orderid ).
  endmethod.

  method get_workcenter_list.
    " use data provider for accessing global data without being able
    " to modify it
    loop at mo_data_provider->get_operations( ) into data(ls_oper).
      data(ls_workcenter) = get_workcenter_oper( ls_oper ).
      collect ls_workcenter into rt_res.
    endloop.
  endmethod.

  method get_workcenter_oper.
    call function 'CR_WORKSTATION_READ'
      exporting
        id    = is_oper-arbid    " Work center ID
        msgty = 'E'    " Message type
      importing
        ecrhd = rs_res.   " Header data
  endmethod.
endclass.

start-of-selection.
  cl_demo_output=>display(
    new lcl_app( p_ordid )->get_workcenter_list( ) ).

No comments:

Post a Comment