DO 1 TIMES is a valid statement in ABAP to introduce a loop that is executed exactly one time.
Basically, this construct can be used in ABAP programs to have a part within a method that can be easily exited at multiples places by using CHECK statements—a concept that was handy when no classes and methods were available. For new coding, no new pseudo loops with DO 1 TIMES should be written since better options are available. The pseudo loop always indicates that more than one thing is done, but a method should only do one thing. However, DO 1 TIMES is still seen today, as we’ll see in the following sections.
Pseudo Loops for Control Flow
Exiting a loop when a specific condition is met with a single statement is handy, and the next statement that is executed should be well-defined and easy to understand. Some methods require statements to be executed regardless of what happened previously in the method. The code in between has a few reasons to be exited early or completely skipped. Implementing this scenario with IF blocks would become cluttered. The easy solution to exit code early and conditionally and have code run nonetheless is to work with a loop that is executed exactly one time. However, the purpose of a loop is to execute the same coding during several iterations. A loop that is consciously used so that it’s only executed exactly one time to use special statements for easier exiting is referred to as a pseudo loop. Using CHECK within a loop will only continue with the iteration if the given condition is true. Otherwise, the iteration is skipped and continues with the next one. Since there is no next iteration in a one-time loop, the execution continues after the ENDO.
Below shows a basic example of the approach that is followed. However, this example is only preventing the two IF blocks that should have been used instead of the pseudo loop. Normally, this construct is used when four or more IFs are chained together, but we’ve provided a basic example to illustrate the point.
DO 1 TIMES.
DATA(messages) = precheck_input( input ).
CHECK NOT line_exists( messages[ type = error ] ).
prepare_processing(
EXPORTING input = input
IMPORTING prepared_input = data(prepared_input)
messages = messages ).
CHECK NOT line_exists( messages[ type = error ] ).
messages = process( prepared_input ).
CHECK NOT line_exists( messages[ type = error ] ).
ENDO.
result = COND #( WHEN line_exists( messages[ type = error ] )
THEN failed
ELSE success ).
Three different steps are necessary to process the input. Each individual step can return a table including messages that were raised during the method execution. Whenever an error message exists within the table, the processing should be stopped and a result flag should be returned. In the current implementation, several chained IF statements are avoided by exiting the loop earlier.
Instead of the CHECK statement, you could have also used a CONTINUE statement, but the block would be even more confusing. If the implementation requires a separate part that can be exited early and that another part must still be executed, the method might become convoluted. Moreover, the method clearly does more than one thing, which contradicts the single-responsibility principle.
This example is simple, and often, the pseudo loop is longer than a typical method based on clean code would be. Following the logic becomes more tedious because the block is basically a method within another method. However, creating a new method would bring additional context as a benefit. Its own method defines a clear signature of values that are required from the outside and what can be expected as a result, including possible exceptions. None of these advantages from the clear signature are gained, which variables of the method are needed within the loop is unclear, and error situations are hard to identify. Therefore, avoid Do 1 Times constructs completely for new coding.
Refactoring
Following the Boy Scout Rule (leaving every location in a better state than when you arrived) requires refactoring every pseudo loop you come across.
Before object-oriented programming patterns were applied in ABAP, the DO 1 TIMES statement was a valid option for reducing the necessary code and to indicate that a part of code must be run anyway.
When refactoring code, we recommend following object-oriented patterns. Consider the code shown earlier. Other opportunities to refactor the code overall may exist, but you should focus on removing the loop. The first thing to check is whether the action needs to be performed in the first place. In this case, the action is just a flag that is returned indicating whether processing was successful or not. The method is marked as failed if at any point a message of type error is added to the table of messages. The content of the table is, however, never analyzed further. As shown below, instead of returning a table of messages, an exception is thrown by each method when an error occurs.
TRY.
precheck_input( input ).
data(prepared_input) = prepare_input( input ).
process( prepared_input ).
result = success.
CATCH cx_error_raised.
result = failed.
ENDTRY.
After the three methods are processed without any exceptions, the result is marked as successful. Any exception of the type cx_error_raised would exit the TRY block and the execution would be continued in the CATCH block in which the result is set to failed. The assumption in this refactoring is, on one hand, that the returned messages are not further processed or necessary (e.g., to show to a user). On the other hand, another assumption is that all three methods were changed to raise an exception, which may not be possible if a method that is not easy to adapt is reused (e.g., in foreign code).
However, another approach is to extract the three methods into a new method that returns a table of the message. If the table contains at least one error message, the result is set to failed. This refactoring result, shown below , is less invasive and preserves access to the raised messages.
DATA(messages) = input_complete_processing( input ).
result = COND #( WHEN line_exists( messages[ type = error ] )
THEN failed
ELSE success ).
Since the pseudo loop was introduced to serve as a detached part to facilitate easy control flow, this part can also be extracted into its own method. One disadvantage of the loop is its undefined signature. During refactoring, you’ll need to first analyze which values flow into the loop and which values are used as results. These values define the signature of the new method. Then, the code might be relocated into the new method in which the CHECK statement is working as intended. However, the results from the new method must be compatible with the results generated by the old pseudo loop so that the calling method still works. As with any refactoring, the best way is to first cover the method with suitable unit tests as a safety net for the refactoring.
With these two approaches on refactoring, you should be able to get rid of the pseudo loop, which can be quite hard to maintain or extend over time. Overall, not all situations might be as straightforward as the example used in this section. The biggest difference in such a case is the effort that is necessary to extract the relevant details to craft new methods. Sometimes, it might even be necessary to create multiple new methods to create a decent result in the end.
Editor’s note: This post has been adapted from a section of the book Clean ABAP: A Style Guide for Developers by Klaus Haeuptle, Florian Hoffmann, Rodrigo Jordão, Michel Martin, Anagha Ravinarayan, and Kai Westerholz
Comments