On the topic of APIs and Poka-Yoke:

Likewise, design APIs so that they make illegal states unrepresentable. If a state is invalid, it’s best to design the API so that it’s impossible to express it in code. Capture the absence of a capability in the API’s design, so that something that should be impossible doesn’t even compile. A compiler error gives you faster feedback than a runtime exception.

The above quote is from a book by Mark Seemann: Code That Fits Your Head (2021, 159–160). Seemann is also one of the authors of the excellent 2019 book Dependency Injection: principles, practices, and patterns.

At first read, it seems like a sensible though not terribly practical suggestion. After all, plenty of code compiles and still contains bugs. Then I hit upon a good example of how ABAP APIs can organically grow to allow incorrect use.

OPTIONAL parameters

Suppose you have decided to subclass the basic ALV Grid class and have also created a static factory method for creating instances:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CLASS zcl_my_enhanced_alv_grid DEFINITION
  INHERITING FROM cl_gui_alv_grid.
  
  PUBLIC SECTION.
    CLASS-METHODS create_grid
      IMPORTING io_container    TYPE REF TO cl_gui_custom_container
      RETURNING VALUE(r_result) TYPE REF TO zcl_my_enhanced_alv_grid.
  "...
ENDCLASS.

CLASS zcl_my_enhanced_alv_grid IMPLEMENTATION.

  METHOD create_grid.
  
    "...
  
    r_result = NEW zcl_my_enhanced_alv_grid( i_parent = io_container ).
    
    "...
  
  ENDMETHOD.

ENDCLASS.

After a while you realize that the container instance (parameter io_container) is always created the same way, only the container name varies:

1
DATA(lo_container) = NEW cl_gui_custom_container( container_name = 'CUSTOM_CONTAINER_ELEMENT' ).

It seems sensible to Move Statements into Function and to Parameterize Function (Fowler 2019), especially since we can still support the case in which the container instance is passed as a parameter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
CLASS zcl_my_enhanced_alv_grid DEFINITION
  INHERITING FROM cl_gui_alv_grid.
  
  PUBLIC SECTION.
    CLASS-METHODS create_grid
      IMPORTING io_container      TYPE REF TO cl_gui_custom_container OPTIONAL
                im_container_name TYPE csequence                      OPTIONAL
      RETURNING VALUE(r_result) TYPE REF TO zcl_my_enhanced_alv_grid.
  "...
ENDCLASS.

CLASS zcl_my_enhanced_alv_grid IMPLEMENTATION.

  METHOD create_grid.
  
    "...
    IF io_container IS BOUND.
      DATA(lo_container) = io_container.
    ELSE.
      lo_container = NEW cl_gui_custom_container( container_name = im_container_name ).
    ENDIF.
  
    r_result = NEW zcl_my_enhanced_alv_grid( i_parent = lo_container ).
    
    "...
  
  ENDMETHOD.

ENDCLASS.

Now editor auto-complete suggests the following:

1
2
3
4
5
DATA(lo_grid) = zcl_my_enhanced_alv_grid=>create_grid(
*  EXPORTING
*    io_container      = 
*    im_container_name = 
).

As far as ABAP can tell, it is syntactically correct to call this method without supplying any parameters, since all parameters have been marked as OPTIONAL. Yet invoking the function without supplying at least one parameter will crash the program at some point. We could add code to check that at least one parameter has been supplied, but that check too takes place at runtime and ABAP programs that will fail the check are still syntactically correct and will compile. Some poor soul could even supply both parameters and would have no way of knowing (apart from looking at the method implementation) which parameter would take precedence.

This is an example of how the design of ABAP and developer practices organically produce cases where invalid cases are still syntactically correct. The following two ABAP features push APIs towards such design:

  1. ABAP does not support method overloading and method names are limited to 30 characters. This means that there is a strong incentive to “overload” well named methods with OPTIONAL parameters in order to reuse the name in different contexts. Special methods (ie. constructor) are a special case of this, since it would not even be possible to create an alternative method.
  2. ABAP uses named parameters. Unlike languages in which parameters are supplied as an ordered list (like C and Erlang), there’s little force limiting the number of parameters, especially given that parameters can be optional. Thus it is not uncommon to see ABAP methods with more than five parameters. Adding an optional parameter is also a favored trick for extending and changing the behavior of a method.

In order to make invalid cases syntactically incorrect, the create_grid factory method would need to be altered somehow:

  • Drop support for one of the mutually exclusive cases. Grids can be created using container instances or container names but not both.
  • Create separate factory methods for each case. create_container_and_grid (container name) and create_grid_using_container (container instance) could work.
  • Extract conditional code from constructor and extend instance creation with a factory method for required cases. Ideally all a constructor should do is accept dependencies/data, thus a factory method for extending would be even more welcome.

As we can see, syntactically correct yet still invalid code might lurk closer to you than you might realize.

Sources

  • Deursen, S van. – Seemann, M. (2019) Dependency Injection: principles, practices, and patterns. Manning Publications. 978-1-61729-473-0.
  • Fowler, M. (2019) Refactoring: improving the design of existing code. Addison-Wesley. 978-0-13-475759-9.
  • Seemann, M. (2021) Code That Fits in Your Head: Heuristics for Software Engineering. Addison-Wesley. 978-0-13-746440-1.