0% found this document useful (0 votes)
25 views355 pages

Clean Abap

Uploaded by

Ioana Acsente
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
25 views355 pages

Clean Abap

Uploaded by

Ioana Acsente
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 355

SAP PRESS is a joint initiative of SAP and Rheinwerk Publishing.

The know-how offered


by SAP specialists combined with the expertise of Rheinwerk Publishing offers the
reader expert books in the field. SAP PRESS features first-hand information and expert
advice, and provides useful skills for professional decision-making.

SAP PRESS offers a variety of books on technical and business-related topics for the SAP
user. For further information, please visit our website: http://www.sap-press.com.

Brian O’Neill, Jelena Perfiljeva


ABAP: An Introduction (2nd Edition)
2020, 684 pages, hardcover and e-book
www.sap-press.com/4955

Kiran Bandari
Complete ABAP (2nd Edition)
2020, 1027 pages, hardcover and e-book
www.sap-press.com/4947

Paul Hardy
ABAP to the Future (3rd Edition)
2019, 864 pages, hardcover and e-book
www.sap-press.com/4751

Winfried Schwarzmann
Test-Driven Development with ABAP Objects
2019, 594 pages, hardcover and e-book
www.sap-press.com/4882

Stefan Haas, Bince Mathew


ABAP RESTful Programming Model: ABAP Development for SAP S/4HANA
2020, 563 pages, hardcover and e-book
www.sap-press.com/4998
Klaus Haeuptle, Florian Hoffmann, Rodrigo Jordão,
Michel Martin, Anagha Ravinarayan, Kai Westerholz

Clean ABAP®
A Style Guide for Developers
Dear Reader,

Editors and programmers have a lot in common.

In fact, editing text and writing clean code share the same goal. Readability is the driv-
ing force behind the clean ABAP best practices that you’ll find in this book, and also
the focus of my day-to-day. When I edit technical guides like this one, it’s important to
hone in on each sentence and ensure that concepts are conveyed clearly and effectively.
It’s not only grammatical accuracy; it’s also the strategic use of tools like lists, tables,
text styles, paragraph breaks, headings, transitions, and more. How do you structure
ideas logically, and what is the proper level of context to provide so that the reader can
follow along?

Programmers writing clean code follow a similar path to a similar destination,


although with an entirely different set of tools. Clean ABAP is all about making the
most of the elements you use when writing code, from classes, interfaces, methods,
and tables to variables, names, comments, and formatting. You’ll find all these com-
ponents and much more in this book, and the expert guidance you need to use them
effectively to maximize the readability of your ABAP code.

What did you think about Clean ABAP: A Style Guide for Developers? Your comments
and suggestions are the most useful tools to help us make our books the best they can
be. Please feel free to contact me and share any praise or criticism you may have.

Thank you for purchasing a book from SAP PRESS!

Megan Fuerst
Editor, SAP PRESS

[email protected]
www.sap-press.com
Rheinwerk Publishing • Boston, MA
Notes on Usage

This e-book is protected by copyright. By purchasing this e-book, you have agreed to
accept and adhere to the copyrights. You are entitled to use this e-book for personal pur-
poses. You may print and copy it, too, but also only for personal use. Sharing an electronic
or printed copy with others, however, is not permitted, neither as a whole nor in parts. Of
course, making them available on the Internet or in a company network is illegal as well.

For detailed and legally binding usage conditions, please refer to the section
Legal Notes.

This e-book copy contains a digital watermark, a signature that indicates which person
may use this copy:

Copy No. 7aji-f3v6-ehyx-z89g


for personal use of
Andrei Arabolea
[email protected]
Imprint

This e-book is a publication many contributed to, specifically:

Editor Megan Fuerst


Acquisitions Editor Hareem Shafi
Copyeditor Yvette Chin
Cover Design Graham Geary
Photo Credit iStockphoto.com: 1169400886/© GoranStimac
Production E-Book Hannah Lane
Typesetting E-Book III-satz, Husby (Germany)

We hope that you liked this e-book. Please share your feedback with us and read the
Service Pages to find out how to contact us.

The Library of Congress has cataloged the printed edition as follows:


Names: Haeuptle, Klaus, author.
Title: Clean ABAP : a style guide for developers / Klaus Haeuptle, Florian
Hoffmann, Rodrigo Jordão, Michel Martin, Anagha Ravinarayan, Kai
Westerholz.
Description: 1st edition. | Bonn ; Boston : Rheinwerk Publishing, 2020. |
Includes index.
Identifiers: LCCN 2020036135 (print) | LCCN 2020036136 (ebook) | ISBN
9781493220267 (hardcover) | ISBN 9781493220274 (ebook)
Subjects: LCSH: ABAP/4 (Computer program language) | Software patterns.
Classification: LCC QA76.73.A12 H35 2020 (print) | LCC QA76.73.A12
(ebook) | DDC 005.13/267--dc23
LC record available at https://lccn.loc.gov/2020036135
LC ebook record available at https://lccn.loc.gov/2020036136

ISBN 978-1-4932-2026-7 (print)


ISBN 978-1-4932-2027-4 (e-book)
ISBN 978-1-4932-2028-1 (print and e-book)

© 2021 by Rheinwerk Publishing, Inc., Boston (MA)


1st edition 2021
Contents

Preface ....................................................................................................................................................... 17

1 Introduction 23

1.1 What Is Clean ABAP? ............................................................................................................ 23


1.1.1 What Is Readability? .............................................................................................. 23
1.1.2 What’s the Story behind Clean ABAP? ............................................................ 25
1.2 How to Get Started with Clean ABAP ........................................................................... 26

1.3 How to Handle Legacy Code ............................................................................................. 26

1.4 How to Check Code Automatically ................................................................................ 28

1.5 How Does Clean ABAP Relate to Other Guides? ...................................................... 29

1.6 How to Engage with the Clean ABAP Community ................................................. 30

1.7 Summary ................................................................................................................................... 31

2 The ABAP Language 33

2.1 Mind Legacy Code ................................................................................................................. 33

2.2 Mind Performance ................................................................................................................ 35


2.2.1 Mostly Harmless ..................................................................................................... 35
2.2.2 Start Clean, Degrade Only Where Needed ..................................................... 36
2.2.3 Measure, Don’t Assume ....................................................................................... 37
2.3 Prefer Object Orientation to Procedural Programming ....................................... 37
2.3.1 ABAP’s Programming Paradigm ........................................................................ 37
2.3.2 The Difference between Function Groups and Classes ............................. 38
2.3.3 Differences in ABAP’s Object Orientation ...................................................... 41
2.3.4 If You Have No Choice ........................................................................................... 43
2.4 Favor Functional Language Constructs over Procedural Language
Constructs ................................................................................................................................. 43

2.5 Avoid Obsolete Language Elements ............................................................................. 46

2.6 Use Design Patterns Wisely .............................................................................................. 48


2.6.1 Too Many Singletons ............................................................................................. 48
2.6.2 Too Many Patterns Mixed ................................................................................... 49
2.7 Summary ................................................................................................................................... 50

7
Contents

3 Classes and Interfaces 51

3.1 Object Orientation ................................................................................................................ 51


3.1.1 Interfaces .................................................................................................................. 52
3.1.2 Classes and Objects ............................................................................................... 57
3.1.3 State ............................................................................................................................ 75
3.2 Scope and Visibility .............................................................................................................. 76
3.2.1 Global and Local Scope ......................................................................................... 76
3.2.2 Visibility ..................................................................................................................... 81
3.3 Constructors ............................................................................................................................. 83
3.3.1 Scope and Visibility ................................................................................................ 83
3.3.2 Dependency Injection ........................................................................................... 85
3.3.3 Static Creation Methods ...................................................................................... 86
3.3.4 Creational Patterns ................................................................................................ 89
3.3.5 Instantiation ............................................................................................................ 91
3.4 Summary ................................................................................................................................... 93

4 Methods 95

4.1 Object-Oriented Programming ....................................................................................... 95


4.1.1 Static and Instance Methods .............................................................................. 95
4.1.2 Public Instance Methods ...................................................................................... 98
4.1.3 Method Redefinition ............................................................................................. 100
4.2 Parameters ............................................................................................................................... 102
4.2.1 How Many Input Parameters Are Too Many? ............................................... 103
4.2.2 Optional Input Parameters ................................................................................. 103
4.2.3 Preferred Input Parameters ................................................................................ 105
4.2.4 Boolean Input Parameters ................................................................................... 106
4.2.5 EXPORTING Parameters ....................................................................................... 108
4.2.6 RETURNING Parameters ....................................................................................... 109
4.2.7 CHANGING Parameters ........................................................................................ 111
4.2.8 Pass-by-Value and Pass-by-Reference ............................................................. 112
4.3 Method Body ........................................................................................................................... 115
4.3.1 Do One Thing ........................................................................................................... 115
4.3.2 Descend One Level of Abstraction .................................................................... 117
4.3.3 Keep Methods Small .............................................................................................. 120
4.3.4 Fail Fast ...................................................................................................................... 122
4.3.5 CHECK versus RETURN .......................................................................................... 124

8
Contents

4.4 Calling Methods ..................................................................................................................... 125


4.4.1 Passing Input Parameters .................................................................................... 125
4.4.2 Capturing Output Parameters ........................................................................... 126
4.4.3 The CALL METHOD Construct ............................................................................. 127
4.4.4 Optional Parameter Name .................................................................................. 128
4.4.5 Self-Reference .......................................................................................................... 129
4.5 Summary ................................................................................................................................... 130

5 Names 133

5.1 Good Naming .......................................................................................................................... 133


5.1.1 Descriptive Names ................................................................................................. 133
5.1.2 Domain Terms ......................................................................................................... 134
5.1.3 Plural or Singular? .................................................................................................. 135
5.1.4 Abbreviations ........................................................................................................... 136
5.1.5 Naming Classes and Methods ........................................................................... 136
5.1.6 Noise Words ............................................................................................................. 137
5.1.7 Term Consistency ................................................................................................... 137
5.1.8 Design Patterns in Code ....................................................................................... 138
5.1.9 Hungarian Notation and Prefixes ..................................................................... 138
5.2 ABAP Peculiarities ................................................................................................................. 138
5.2.1 Group of Objects with Possible Name Collisions ........................................ 139
5.2.2 snake_case versus camelCase ........................................................................... 139
5.3 Affixes: Prefixes, Suffixes, and Infixes ........................................................................ 140
5.3.1 The Musts .................................................................................................................. 140
5.3.2 Helpful Affixes ......................................................................................................... 141
5.3.3 Affixes That Don’t Bring Value .......................................................................... 142
5.4 Dealing with Legacy Code ................................................................................................. 142

5.5 Summary ................................................................................................................................... 143

6 Variables and Literals 145

6.1 Variables .................................................................................................................................... 145


6.1.1 Declaring Variables ................................................................................................ 146
6.1.2 Branches and Scope ............................................................................................... 148
6.1.3 Declaration Chaining ............................................................................................ 149
6.1.4 Looping Variables ................................................................................................... 151

9
Contents

6.2 Constants .................................................................................................................................. 152


6.2.1 Proper Use of Constants ...................................................................................... 152
6.2.2 Enumeration Classes ............................................................................................. 154
6.2.3 Constant Grouping ................................................................................................ 159
6.3 Strings ......................................................................................................................................... 160
6.3.1 String Literals ........................................................................................................... 160
6.3.2 String Building ......................................................................................................... 161
6.4 Booleans .................................................................................................................................... 161
6.4.1 When to Use Booleans .......................................................................................... 162
6.4.2 XSDBOOL for Inline Decisions ............................................................................ 163
6.5 Regular Expressions .............................................................................................................. 164
6.5.1 Simple Regular Expressions ................................................................................ 164
6.5.2 Basic Checks ............................................................................................................. 165
6.5.3 Complex Regular Expressions ............................................................................ 166
6.6 REDUCE ....................................................................................................................................... 166

6.7 Summary ................................................................................................................................... 168

7 Internal Tables 171

7.1 Using the Right Table Category ...................................................................................... 171


7.1.1 Standard Tables ...................................................................................................... 172
7.1.2 Sorted Tables ........................................................................................................... 172
7.1.3 Hashed Tables ......................................................................................................... 173
7.2 Avoiding DEFAULT KEY ........................................................................................................ 173

7.3 INSERT INTO TABLE and APPEND TO ............................................................................. 174


7.3.1 APPEND Statement ................................................................................................ 174
7.3.2 INSERT Statement .................................................................................................. 175
7.4 Verifying the Existence of a Row .................................................................................... 176
7.4.1 READ TABLE .............................................................................................................. 176
7.4.2 LOOP AT ..................................................................................................................... 176
7.4.3 LINE_EXISTS .............................................................................................................. 177
7.5 Retrieving Table Contents ................................................................................................. 177
7.5.1 LOOP AT ..................................................................................................................... 178
7.5.2 READ TABLE .............................................................................................................. 178
7.6 LOOP AT WHERE and Nested IF ....................................................................................... 179
7.6.1 Nested IF .................................................................................................................... 179
7.6.2 LOOP AT … WHERE .................................................................................................. 180

10
Contents

7.7 Identifying Unnecessary Table Reads .......................................................................... 180

7.8 Block Processing of Table Rows and Single Row Operations ............................. 181

7.9 DESCRIBE TABLE and Table Function LINES ................................................................ 182


7.9.1 DESCRIBE TABLE ...................................................................................................... 182
7.9.2 LINES ........................................................................................................................... 183
7.10 Summary ................................................................................................................................... 183

8 Control Flow 185

8.1 IFs .................................................................................................................................................. 185


8.1.1 IF Branches ................................................................................................................ 186
8.1.2 Keep It Understandable ....................................................................................... 188
8.2 Nesting Depth ......................................................................................................................... 190

8.3 Conditions ................................................................................................................................. 190


8.3.1 Try to Make Conditions Positive ........................................................................ 190
8.3.2 IS NOT or NOT IS ..................................................................................................... 193
8.3.3 Complex Conditions .............................................................................................. 195
8.4 CASE ............................................................................................................................................. 196
8.4.1 CASE or IF ................................................................................................................... 197
8.4.2 CASE or SWITCH ...................................................................................................... 198
8.4.3 Multiple Uses of the Same CASE ....................................................................... 199
8.5 Do 1 Times ................................................................................................................................ 200
8.5.1 Pseudo Loops for Control Flow .......................................................................... 200
8.5.2 Refactoring ............................................................................................................... 201
8.6 Summary ................................................................................................................................... 203

9 Comments 205

9.1 Express Yourself in Code .................................................................................................... 205

9.2 Comment Placement and Usage .................................................................................... 207

9.3 Comments to Avoid .............................................................................................................. 208

9.4 FIXME, TODO, and XXX Comments ............................................................................... 211

9.5 Special Comments: ABAP Doc, Pragmas, and Pseudo Comments ................... 212
9.6 Summary ................................................................................................................................... 214

11
Contents

10 Formatting 215

10.1 Consistency in Coding Style .............................................................................................. 216

10.2 Optimizing for Reading ...................................................................................................... 216

10.3 The Pretty Printer .................................................................................................................. 217

10.4 Number of Statements Per Line ...................................................................................... 218

10.5 Line Length ............................................................................................................................... 219

10.6 Condensing the Code ........................................................................................................... 220

10.7 Blank Lines ................................................................................................................................ 220

10.8 Alignment of Assignment Statements ........................................................................ 221

10.9 Alignment of Variable Declarations .............................................................................. 222


10.10 Placement of Closing Brackets ........................................................................................ 223

10.11 Formatting Method Parameters ..................................................................................... 223


10.11.1 Single Parameter Calls .......................................................................................... 224
10.11.2 Line Break Multiple Parameters ........................................................................ 224
10.11.3 Placement of Parameters .................................................................................... 224
10.11.4 Indenting Parameters ........................................................................................... 225
10.11.5 Vertical Alignment of Parameter Values ........................................................ 226
10.11.6 Breaking Calls to a New Line .............................................................................. 226
10.11.7 Indents and Using the Tab Key .......................................................................... 227
10.11.8 Indentation of Inline Declarations .................................................................... 227
10.12 Summary ................................................................................................................................... 227

11 Error Handling 229

11.1 Messages ................................................................................................................................... 229

11.2 Return Codes ........................................................................................................................... 232


11.2.1 Exceptions versus Return Codes ........................................................................ 233
11.2.2 Handling Failures ................................................................................................... 234
11.3 Exceptions ................................................................................................................................. 236
11.3.1 Exceptions Are for Errors ...................................................................................... 236
11.3.2 Class-Based Exceptions ........................................................................................ 237
11.3.3 CX_STATIC_CHECK ................................................................................................ 242
11.3.4 CX_NO_CHECK ........................................................................................................ 243
11.3.5 CX_DYNAMIC_CHECK ........................................................................................... 244

12
Contents

11.4 Raising and Catching ........................................................................................................... 245


11.4.1 Exception Superclass ............................................................................................. 245
11.4.2 Raising Exceptions ................................................................................................. 247
11.4.3 Catching Exceptions .............................................................................................. 248
11.4.4 When to Dump ........................................................................................................ 251
11.5 Summary ................................................................................................................................... 252

12 Unit Testing 253

12.1 Test Classes .............................................................................................................................. 254


12.1.1 Test Class Properties ............................................................................................. 254
12.1.2 Test Class Scope ...................................................................................................... 257
12.1.3 Test Helper Classes ................................................................................................ 259
12.1.4 Executing Tests ....................................................................................................... 260
12.2 Test Methods ........................................................................................................................... 262
12.2.1 Test Fixture Methods ............................................................................................ 262
12.2.2 Given-When-Then Style ....................................................................................... 265
12.3 Class under Test ..................................................................................................................... 266

12.4 Naming ....................................................................................................................................... 267

12.5 Assertions .................................................................................................................................. 269


12.5.1 Writing Effective Assertions ............................................................................... 270
12.5.2 Checking Expected Exceptions .......................................................................... 272
12.5.3 Unexpected Static Exceptions ........................................................................... 274
12.5.4 Custom Assertions ................................................................................................. 275
12.5.5 Constraints ............................................................................................................... 277
12.6 Test Doubles ............................................................................................................................ 279
12.6.1 Dependency Inversion and Test Doubles ....................................................... 279
12.6.2 ABAP Object-Oriented Test Double Framework .......................................... 284
12.6.3 More ABAP Test Tools ........................................................................................... 288
12.7 Test Seams ................................................................................................................................ 289

12.8 Principles ................................................................................................................................... 291


12.8.1 Test-Driven Development ................................................................................... 291
12.8.2 Clean Test Properties ............................................................................................ 292
12.8.3 Test Coverage .......................................................................................................... 294
12.9 Summary ................................................................................................................................... 294

13
Contents

13 Packages 297

13.1 General Package Concepts ................................................................................................ 297


13.1.1 Use Cases .................................................................................................................. 298
13.1.2 Reuse Levels .............................................................................................................. 298
13.1.3 Cohesion .................................................................................................................... 299
13.2 Package Concepts in ABAP ................................................................................................ 299
13.2.1 Package Types .......................................................................................................... 299
13.2.2 Encapsulated Packages ........................................................................................ 300
13.2.3 Package Interfaces ................................................................................................. 301
13.2.4 Best Practices ........................................................................................................... 303
13.3 Package Design Options ..................................................................................................... 304
13.3.1 Side-by-Side Application Hierarchies ............................................................... 305
13.3.2 Layer-Based Hierarchies ....................................................................................... 306
13.3.3 Translation Relevance Split Hierarchies ......................................................... 306
13.4 Package Checks ...................................................................................................................... 308
13.4.1 What Are Package Checks? ................................................................................. 310
13.4.2 Manual Execution of Package Checks ............................................................. 310
13.4.3 Automated Execution of Package Checks ...................................................... 312
13.4.4 How to Fix Package Check Errors ...................................................................... 313
13.4.5 Best Practices ........................................................................................................... 315
13.5 Consequences of Poor or No Package Strategy ....................................................... 316

13.6 Summary ................................................................................................................................... 316

14 How to Implement Clean ABAP 319

14.1 Common Understanding among Team Members .................................................. 319

14.2 Collective Code Ownership ............................................................................................... 320

14.3 Clean Code Developer Initiative ..................................................................................... 322

14.4 Tackling the Broken Window Effect .............................................................................. 323


14.4.1 Static Code Check ................................................................................................... 325
14.4.2 Metrics ....................................................................................................................... 325
14.4.3 Code Coverage ......................................................................................................... 326
14.5 Code Review and Learning ................................................................................................ 326
14.5.1 Code Review Prefix ................................................................................................. 326
14.5.2 Style Guide ................................................................................................................ 327

14
Contents

14.5.3 Make It Visible ......................................................................................................... 327


14.5.4 Feedback Culture .................................................................................................... 327
14.6 Clean Code Advisor ............................................................................................................... 330

14.7 Learning Techniques ............................................................................................................ 330


14.7.1 Kata ............................................................................................................................. 330
14.7.2 Dojo ............................................................................................................................. 331
14.7.3 Code Retreat ............................................................................................................. 331
14.7.4 Fellowship ................................................................................................................. 332
14.7.5 Pair Programming .................................................................................................. 332
14.7.6 Mob Programming ................................................................................................. 333
14.7.7 Habits ......................................................................................................................... 333
14.8 Continuous Learning in Cross-Functional Teams .................................................... 334
14.8.1 Profile of a Team Member ................................................................................... 334
14.8.2 Cross-Functional Teams ....................................................................................... 335
14.8.3 Multipliers: Need for Topic Experts in the Team ......................................... 336
14.8.4 Need for Community of Practice ....................................................................... 336
14.9 Summary ................................................................................................................................... 337

The Authors ............................................................................................................................................. 339


Index .......................................................................................................................................................... 341
Service Pages ..................................................................................................................................... I
Legal Notes ......................................................................................................................................... I

15
Preface

Clean code has been a staple of agile software development for many years. Code is
more often read than it’s written, and so it’s critical that your code is easy to read and
that it clearly conveys what it’s trying to achieve. With a relentless focus on readability
and understandability, clean code ultimately has a huge impact on the maintainability
and testability of a code base.
This book is a compendium of rules, guidelines, and recommendations for how to
make your ABAP code clean: readable, understandable, maintainable, and testable. It
discusses detailed code patterns and idioms, as well as more high-level design consid-
erations. It also explores some processes and organizational concerns that have an
impact on the implementation of clean code.

Who This Book Is For


This book is mainly written for ABAP developers and testers with a penchant for devel-
opment. You’ll find many code samples, as the book is very technical in nature.
Clean ABAP is for novice as well as senior programmers. Depending on your skill level,
you may benefit from different topics, and you may feel drawn towards different cen-
ters of focus. However, the general ideas address all developers, independent of experi-
ence.
We also encourage architects to read this book and understand clean ABAP. Some top-
ics in this book transgress in the architect’s realm of large-scale objects such as pack-
ages and object-oriented design. Even if you don’t code at all, you should be able to
connect to your team and understand at least the main ideas of clean ABAP.
In this book, we included topics that should help you to get started with clean ABAP at
any time, in any kind of project, with any kind of team.

How This Book Is Organized


This book begins with an introduction to clean ABAP and a general discussion of the
ABAP language in Chapter 1 and Chapter 2. Then, in Chapter 3 and Chapter 4, it dives
into the overarching topics of classes, interfaces and methods, discussing clean ABAP
practices for these higher-level constructs. The key naming topic is discussed in Chap-
ter 5. Then, more detailed clean ABAP topics are presented in Chapter 6 through Chap-
ter 11, from variables and literals to error handling. Unit testing in ABAP is discussed in
Chapter 12, followed by ABAP packages in Chapter 13. Finally, Chapter 14 talks about
how to learn and implement clean ABAP, whether that’s at the individual, team, or
organization level.

Personal Copy for Andrei Arabolea, [email protected] 17


Preface

Chapter 1 and Chapter 2 are important, as they set up the context for the whole book.
Chapter 3 and Chapter 4 are best read together, since the topic of classes and interfaces
naturally segues into methods. The other chapters are more independent and can be
read in any order or used as references. Still, be sure to read Chapter 1, Section 1.2: How
to Get Started with Clean ABAP, to get more specific guidance on which topics to pur-
sue at which time. The last chapter is important for anyone willing to learn and imple-
ment clean ABAP at any level in an organization.
Let’s look briefly at what is covered in each chapter:
쐍 Chapter 1: Introduction
This chapter introduces the clean ABAP concept, describes why it’s important to
write clean code, and how to get started. You’ll also learn about other style guides
and learning resources that can be used to expand your understanding beyond this
book.
쐍 Chapter 2: The ABAP Language
This chapter offers general guidance about using ABAP and sets up the context in
which to apply clean ABAP practices. You’ll learn about special considerations for
legacy code and performance, and understand overarching methodologies for pro-
ducing readable, functional code.
쐍 Chapter 3: Classes and Interfaces
You’ll learn to apply clean ABAP concepts to classes and interfaces in this chapter.
The chapter explores classes and interfaces from the perspective of object-oriented
programming and teaches you how to use classes and interfaces appropriately to
create clean code.
쐍 Chapter 4: Methods
This chapter teaches you about using methods. You’ll understand how to handle
method design, declaration and use, and control flow.
쐍 Chapter 5: Names
This chapter delves into the naming of elements in clean ABAP code. You’ll learn to
create good names and understand affixes and abbreviations.
쐍 Chapter 6: Variables and Literals
This chapter discusses how to declare and use variables, constants, and literals in
clean ABAP code.
쐍 Chapter 7: Internal Tables
This chapter shows you how to declare and use internal tables in clean ABAP. It cov-
ers the different table categories that are available and key statements and code
structures that are relevant to internal tables.
쐍 Chapter 8: Control Flow
You’ll learn how to structure control flow for clean code in this chapter. Clean ABAP
recommendations for using ifs, conditions, case statements, and refactoring are
included.

18 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Acknowledgments

쐍 Chapter 9: Comments
This chapter explores why and how to use (or not to use) comments when coding
with ABAP, including where and when to place comments, comments to avoid, and
special comments like ABAP Docs.
쐍 Chapter 10: Formatting
This chapter discusses code formatting. It covers recommendations for consistency,
reading optimization, line lengths, blank spaces, indentation, and much more.
쐍 Chapter 11: Error Handling
This chapter teaches you about error handling. You’ll learn about using messages,
return codes, and exceptions with clean ABAP recommendations. The chapter also
covers throwing exceptions and the different options available for handling excep-
tions and errors.
쐍 Chapter 12: Unit Testing
In this chapter, you’ll learn about unit testing principles and understand how to
design and run unit tests for ABAP code. The chapter covers test classes, code under
test, injections, test methods, assertions, and more.
쐍 Chapter 13: Packages
This chapter teaches you about structuring ABAP code into packages. You’ll learn
about package design, package interfaces, and package hierarchies for clean code.
쐍 Chapter 14: How to Implement Clean ABAP
This chapter discusses the challenges and approaches to establishing clean ABAP
practices.

Acknowledgments
Writing readable, testable, and maintainable code is something a professional developer
deeply cares about. Thanks to all developers who care about this topic, share their
knowledge with others, and help to grow future professional developers. The book
would not have been possible without the support of a huge developer community that
supported the open-source project Clean ABAP. I want to especially thank all contribu-
tors to the Clean ABAP project. A special thanks is also due to Thomas Hammer, Jakub
Filak, and all the other passionate contributors, to Knut Stargardt for spreading the
word, to the Basis teams for their valuable feedback, to the legal team for the quick li-
cense decision, and to Brian Bernard for his support in assembling the public repository.
Without the dedication and support of my former teams in governance risk and com-
pliance at SAP, this book would not have been possible. I would like to express special
gratitude to my former colleague Florian Hoffmann for his great dedication to the
development of the Clean ABAP project. Furthermore, I greatly appreciate the time
given by our former manager Eckhardt Mentz, who always supported the idea and
our work.

Personal Copy for Andrei Arabolea, [email protected] 19


Preface

A big thank you to my coauthors Florian Hoffmann, Rodrigo Jordão, Michel Martin,
Anagha Ravinarayan, and Kai Westerholz, who invested so much time and dedication
into the book. I have always appreciated this trustful and inspiring collaboration.
Thanks to Megan Fuerst who supported us with our first book project and helped us
with all of our questions.
—Klaus Haeuptle
Clean ABAP is a community thing. There are many individuals who helped get it on the
way and let it grow. Thanks to everybody who assists in bringing this to a wider audi-
ence, contributes to its content, and takes a stand for good code, day by day.
—Florian Hoffmann
I'd like to thank Florian Hoffmann and Klaus Haeuptle for kickstarting and developing
the topic of clean ABAP at SAP at large, and for stewarding the vibrant open-source
community that has rallied around Florian's initial writings. Clean ABAP and clean
code in general have always been topics that are very dear to me, and their example
and leadership has inspired me to contribute. This book also wouldn't be possible with-
out the support of SAP, with a culture that encourages constant learning and chal-
lenges us to improve and be better each day. Last but by no means least, I wouldn't be
where I am today without the monumental support of the people I most love in my life:
my parents, my sons, and my lovely wife Nara. You inspire me every day to be a better
version of myself!
—Rodrigo Jordão
I am grateful for the amazing career opportunities I had over the last 17 years at SAP.
SAP invests in its employees, encourages continuous learning, and allows employees
to try different roles, which in turn helps everyone grow. As SAP employees, we have
the freedom to create different communities of practice, share knowledge, and encour-
age change. Without SAP's corporate culture, it would have been difficult to gather
everyone to coauthor this book.
—Michel Martin
I am grateful to my family (the ones related by blood and the ones that aren’t) for con-
stantly supporting me in all my endeavors and encouraging me to go beyond barriers.
Also, a big thank you to my colleagues at SAP, from whom I learn new things every day.
Their collaborative and learning mindset is highly appreciated.
Many thanks to Florian Hoffmann and Klaus Haeuptle for giving me the opportunity
to contribute to this book. And thank you to all my co-authors, for your efforts and for
being so approachable. I’ve learned a lot from you all and thoroughly enjoyed the expe-
rience.
—Anagha Ravinarayan

20 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Conclusion

Reading this book alone is not enough to adopt clean ABAP in your team; however, it is
a good starting point for you and your team. It will be necessary to discuss the aspects
shared in this book, and it will need some time and iterations to improve. At this point
I want to thank my team for being on this journey with me, improving day by day, shar-
ing knowledge, and being open to new ideas.
—Kai Westerholz

Conclusion
Reading this book will arm you with important concepts, patterns, and idioms that will
make your ABAP code clean. You will learn what the “clean” concept means, why you
should care about it, and how you can apply it to your work.
Clean ABAP evolves as the programming language and our understanding of it
changes. We’ll therefore also show you how questioning old habits can show ways to
better code, and that sometimes it may be better to ignore the rules.
Clean ABAP is also meant for teams, and we thus want to show you how you can intro-
duce it to your team and project with respect and tangible proof.

Personal Copy for Andrei Arabolea, [email protected] 21


© 2025 by Rheinwerk Publishing Inc., Boston (MA)
Chapter 1
Introduction
Writing cleaner code is an easy way to boost the quality and maintain-
ability of your code. You can start doing it anywhere and anytime, with
any kind of code and project. The price? Practice, discipline, and patience.

In this chapter, we’ll start our discussion of clean ABAP by defining and describing the
importance of code readability in Section 1.1. Then, in Section 1.2, we’ll show you how to
get started with clean ABAP. Since you’ll likely inherit and interact with legacy code, in
Section 1.3, we’ll describe some basic clean code principles, like the Boy Scout Rule and
clean islands. In Section 1.4, you’ll learn how to check code automatically. To round out
this introduction, we’ll compare clean ABAP with other coding guides in Section 1.5,
and finally, we’ll describe how you can interact with the clean ABAP community in Sec-
tion 1.6.

1.1 What Is Clean ABAP?


Clean ABAP adopts the principles described in Robert C. Martin’s book, Clean Code, spe-
cifically for the ABAP programming language. It expands on selected topics to advance
the notion further.
While Martin’s original publication applies to all programming languages, its examples
mostly deal with Java. Thus, adopting these principles and code in languages with other
properties, such as ABAP, can be quite challenging.
The goal of clean ABAP is to close this gap by providing examples that can be readily
consumed by ABAP coders, offering guidelines that point out where ABAPers might
need to deviate, and relating clean code topics to other guides for the ABAP language.
In the following sections, we’ll explore a fundamental value of clean ABAP: readability.
We’ll also take a look at the story behind clean ABAP.

1.1.1 What Is Readability?


Thankfully, programmers who write good code have always been around, but it was
Martin who, in 2008, gave wide attention to the notion that code should not only be
correct and efficient, but also readable.
The definition is simple: Code is readable if it can be understood easily and quickly.

Personal Copy for Andrei Arabolea, [email protected] 23


1 Introduction

For example, a “dirty” statement like t = 42. is hard and time-consuming for the reader
to understand. The reader must find out that “t” stands for “timeout,” that the number
is a duration in seconds, and that 42 is the default timeout for all operations in this con-
text.
The equivalent clean statement timeout_in_seconds = DEFAULT_TIMEOUT., in comparison,
gives all these details readily away at first glance—the code is much more readable.
So, why should we care about readability? Past literature on software mostly dealt with
the correctness and resource efficiency of programming languages.
For example, Donald E. Knuth’s The Art of Computer Programming gave us thousands
of pages that compare the processing times and memory consumption of algorithms
to help you pick the optimal algorithms for your purposes.
Martin made the point that, if resource efficiency was the only aspect that counted, we
should all be writing code in first-generation programming languages, like assembler,
which are by their very nature languages that enable maximum control over all
resources and thus have the greatest potential for optimization.
The reason we don’t use a language like assembler is that those languages are
extremely hard to understand. They deal with the lowest level of detail you can possi-
bly have on a computer, such as registers, adders, and interrupts. Seeing the bigger pic-
ture in this sea of details is particularly challenging.
As human beings, we tend to think in higher levels of abstractions, such as functions,
objects, and interfaces—the reason we invented second-, third-, and fourth-generation
programming languages, like ABAP, in the first place.
If understandability is what these languages add, then following Martin’s argument,
why is code that we write in these abstract languages so hard to understand?
The point we want to emphasize is that programming languages may enable you to
write readable code, but that readability doesn’t emerge on its own in some magical
way. You must actively learn how to write clean code, just like traditional authors must
learn how to write good English prose.
This leads us to what we gain with readability. We read code a lot more than we write
code. We may reread our own code many times over when debugging it or extending it
with new features. We also read other developers’ code all the time, in reviews, in sup-
port tickets, and when connecting to new components. As a result, every minute we
invest in simplifying our code usually pays off several times over on the reading side.
The advantages of readability go way beyond time savings. Readable code is easier to
verify and therefore harder to get wrong. You’ll not only be able to debug readable code
more quickly, but you’ll have fewer bugs right from the beginning.
Also, the software’s design becomes clearer with smaller individual components. As a
consequence, attaching new features is easier because more places exist where new

24 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


1.1 What Is Clean ABAP?

features can be plugged in easily, and many more components can be swapped out
with little extra effort.

1.1.2 What’s the Story behind Clean ABAP?


In 2010, three development Scrum teams from the SAP governance, risk, and compli-
ance department, including Florian Hoffmann, started coding the new solution SAP
Fraud Management in ABAP. To meet the quarterly release cycle in Scrum mode, we
started by attending a training—given by Klaus Haeuptle—that was mostly influenced
by clean code.
Although we reached many of our targets in the following years, we found that teams
still sported quite distinct ideas about what good ABAP code was supposed to look like.
Code reviews sometimes degraded into subjective quarrels over harmless details like
whitespace formatting.
In February 2018, we started collecting our best practices for clean ABAP code to learn
from each other, create an objective grounding for our code reviews, and onboard new
developers more easily. We started with a Wiki page, then moved to an internal GitHub
repository as the content grew.
Over the course of 2018, Klaus Haeuptle started an engineering ecosystem, a loose com-
munity of hundreds of software developers who wanted to share agile expertise.
In November 2018, we gave a presentation on our Clean ABAP repository in the ecosys-
tem’s regular sessions. The participation was overwhelming, with several hundred par-
ticipants from across SAP. We repeated the session in January 2019, as part of SAP’s
annual internal developer conference DKOM. This time, our session was the biggest
non-main stage session at the time.
Previously, SAP propagated code style guides either top-down from management or
high-level architects to developers, or inside-out from language developers to applica-
tion developers. We made a point that our guide was from coder to coder at eye level,
not motivated by management key performance indicators (KPIs) but purely opt-in,
and that we wanted to advance clean ABAP with an interactive community.
The positive feedback encouraged Klaus and Florian to take the next step in May 2019
and make clean ABAP open-source. There was no point in keeping clean ABAP internal;
we wanted to share our knowledge with partners and customers. The Clean ABAP
repository turned into SAP’s first-ever non-code project to be published as open-
source.
Writing a book about clean ABAP was an option that had always been on the table. But
first, we wanted to lay the groundwork for an interactive community that not only con-
sumed, but actively discussed and developed, best practices further. Now that this
community has been achieved, we think it is a good idea to follow up with a book that
goes more into detail than the intentionally brief open-source repository.

Personal Copy for Andrei Arabolea, [email protected] 25


1 Introduction

1.2 How to Get Started with Clean ABAP


If you’re starting new with clean ABAP in your team, we recommend you start with top-
ics that are easy to consume, especially Booleans (Chapter 6, Section 6.4), conditions
(Chapter 8, Section 8.2), and ifs (Chapter 8, Section 8.1). Typically, developers immedi-
ately understand the benefits of these recommendations, find them easy to use, and
will find little to quarrel about. Insert these principles into your daily coding routines
and code reviews and savor how these little changes can improve your codebase with
barely any extra effort.
Most benefits come from clean ABAP practices for methods (Chapter 4), especially the
advice to do one thing and keep methods small. Those topics are easy to digest but hard
to apply in practice. You’ll see discussions on questions like “How short is short
enough?” and “What is one thing?” These debates are necessary, and we hope our guid-
ance will help you reach a common understanding without too much hassle.
You will achieve a foundation in clean ABAP if you follow the rules use descriptive
names, do one thing, keep methods small, try to make conditions positive, and express
yourself in code, all of which you’ll encounter throughout this book. These five rules
alone will boost your code’s readability exponentially, meaning these rules will enable
you and other coders to understand the code much more quickly.
From this point, you can grow into the more controversial areas of clean ABAP, such as
the details of the section’s comments (Chapter 9), names (Chapter 5), and formatting
(Chapter 10). These topics are perfectly “healthy” but are hard to communicate to new
clean coders and may spark more than one heated discussion about personal styles.
Our recommendation thus is to proceed with these topics only once you’ve seen proof
of the positive effects of clean ABAP on your code.
Our open-source guide was so brief in some aspects that we strongly recommended
coders read Robert C. Martin’s Clean Code first to learn the broader ideas behind our
rather condensed statements. With this book, we think we’ve elaborated sufficiently on
all the relevant topics that you can use this book as a one-stop shop. Reading up on the
fundamentals is never wrong, however, so much wisdom can still be gained from Mar-
tin’s original book.
If you’re looking for different approaches on getting engaged with clean code, have a
look at the Clean Code Developer Initiative at https://clean-code-developer.com. This
initiative features more gaming-oriented didactics (gamification), with karate belts of
different colors, so you can practice and improve your proficiency in clean code.

1.3 How to Handle Legacy Code


When Java developers talk about “legacy code,” they usually mean code without unit tests
that therefore cannot be refactored safely. The code also often looks a little outdated and
mixes up too many different intentions in a poor style that is hard to understand.

26 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


1.3 How to Handle Legacy Code

Introducing clean code into an ABAP project is a little harder than introducing it into a
Java project. ABAP is older, so we tend to have more code that has grown wildly in all
directions. The language also started out with a different and less pronounced focus,
made a paradigm shift from procedural to object-oriented, runs a wider variety of
releases due to SAP’s long-term support promises, has an architecture that favors cen-
tralized remote components like the ABAP Data Dictionary over local file structures,
and includes enhancement concepts that make changing existing code difficult.
Fortunately, introducing clean ABAP in a legacy project is both possible and worth it.
We observed good results with the following four-step plan (Chapter 2, Section 2.1, fol-
lows up with details on what exactly to introduce):
1. Getting the team on board
First, you’ll need to get your team on board. Explain the new style, encourage discus-
sion, and guide the team towards agreeing on a common set of rules that they con-
sider both realistic and applicable.
This set of rules doesn’t have to be big and, in the beginning, may consist of only a
handful of rules. For example, you could start with a subset of Booleans, conditions,
ifs, and methods because they are quite rewarding and can be applied selectively
only to new or changed code. You can grow this set anytime during the project.
2. Following the Boy Scout Rule
Second, follow the Boy Scout Rule in your daily routine: Always leave the code a little
cleaner than you found it. When you’re changing something, take an extra minute
and improve something small on the side.
Do not extract refactoring iteration items for your code into your own backlog
items. These items are quite hard to justify to the team, especially the product
owner, and will only be ranked down again and again until nobody remembers them
anymore and they’ll never be done. Just like unit tests, refactoring should be done
right away, as part of the coding, and may even form part of the criteria for com-
pleteness. You can make these activities visible as subtasks of backlog items. Refac-
toring backlog items should be reserved for large-scale changes, such as an
architecture workover, and must be transparent to track your technical debt.
In reverse, restrain yourself to one or two extra mini changes. Don’t get trapped
picking up a trail of breadcrumbs; that trail of crumbs has no end, and you’ll only
find yourself adding more and more to already oversized change sets.
Avoid producing weird style mixtures in the same element. For example, if your
very large method uses upfront data declarations, don’t start mixing in inline decla-
rations somewhere in the middle. Either refactor all declarations to inline in one
step or let them be. Some refactoring steps are better left for later, after other steps
have been applied, such as cutting large methods into smaller methods that are then
less effort to rework.

Personal Copy for Andrei Arabolea, [email protected] 27


1 Introduction

Make sure that your team agrees on a common style to avoid “refactoring loops,”
where two developers revert each other’s changes again and again because of con-
flicting notions about what the code should look like.
3. Building clean islands
Third, build clean islands. From time to time, pick a small object and try to make it
clean in all aspects that you’ve agreed upon as a team.
These islands demonstrate where the activity will lead you. You may need some-
thing to point to and say, “Look, we’ve done it here, and the bug count for that com-
ponent is 70% less than in similar, uncleaned components.” Or, you might say, “Of
course it’s worth it, last time we did it, we eliminated 8 bugs in the process.” Other
coders may need good real-life examples that they can safely refer to when discuss-
ing rules.
Once you have a clean island, grow it iteratively by making connected components
cleaner with the Boy Scout Rule.
4. Talking about code
Fourth, talk about code—a lot. It doesn’t matter whether you set up old-school Fagan
code reviews, present things in info-sessions, form discussion boards in a chat tool,
or organize expert workstreams to reflect on things. You’ll need to talk about your
experiences and the knowledge you’ll gain to enable your team to choose clean
ABAP and determine which aspects to emphasize or deemphasize.

1.4 How to Check Code Automatically


Unfortunately, at the time of this writing, no comprehensive suite of static code checks
is available to automatically detect all the anti-patterns we describe in this book (an
anti-pattern is an ineffective response to a recurring problem). However, many such
checks have been produced while we were writing this book, so the future will look
brighter in this regard. Until then, some code checks are still available for your use.
The primary code checks for ABAP are the following:
쐍 ABAP Test Cockpit
Use the ABAP Test Cockpit to run your unit tests and static code checks in a single
pass. It includes other checks like the Code Inspector and the extended program
check, and also their clean code-related metrics.
쐍 Code Inspector
The Code Inspector provides checks that can help you identify code that disregards
clean code rules. For example, you can use it to limit the maximum size of your
methods, prevent overly complex conditional branching with the “cyclomatic com-
plexity” metric, and identify components with too many dependencies via the
“fanout” calculation.

28 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


1.5 How Does Clean ABAP Relate to Other Guides?

쐍 Extended program check


Some of the extended program check’s content allows finding the anti-patterns we
describe in this book. For example, you can use it to find code that uses obsolete
statements, or common unfavorable uses of statements like CHECK and EXIT. As an
older transaction, its checks are particularly well suited to “modernize” old code that
still revolves around functions and forms.
쐍 Transaction CHECKMAN
Transaction CHECKMAN is an alternative to the Code Inspector and ABAP Test Cock-
pit that is typically only active in SAP’s own systems. It doesn’t provide new or differ-
ent checks; it only recombines them in a standard manner for standard system
landscape setups, ensuring that different teams at SAP rely on comparable quality
metrics.
쐍 abapOpenChecks
An open-source collection of Code Inspector checks that also covers some anti-
patterns. You can import these checks into your SAP systems and run them as part
of your regular Code Inspector profiles.
쐍 abaplint
An open-source reimplementation of the ABAP parser. This standalone component
is meant to be used on code that has been extracted from SAP systems with abapGit.
abaplint allows you to check for some of the anti-patterns described in this book and
can be used to check formatting and code conventions. You can integrate this com-
ponent into your workflows through GitHub Actions, Jenkins, and text editors.

Each one of these checks may help you find certain anti-patterns. As more and more of
SAP’s teams adopt clean ABAP, chances are that more Code Inspector checks will be
available in the future, so watch out for release notes and new documentation.
Some clean ABAP guidelines are inherently difficult to automate because they require
a true understanding of what’s happening in the code. Artificial intelligence (AI) hasn’t
reached this level of understanding yet. For example, naming variables and classes will
likely remain a task for human beings for quite some time. Thus, while automatic
checks could be helpful, you must still be prepared to add other things, like code
reviews, to cover the rest.

1.5 How Does Clean ABAP Relate to Other Guides?


Our guide follows the spirit of clean code, although we’ve adjusted some things to the
ABAP programming language.
For example, clean code suggests that we should throw only unchecked exceptions. In
Java, this rule means those exceptions don’t have to be declared in the methods that
throw them. In clean ABAP, we determined that unchecked exceptions in ABAP behave

Personal Copy for Andrei Arabolea, [email protected] 29


1 Introduction

a little differently and further investigated whether and how this behavior affects our
choice of exception type.
Some facts in this book mirror the ABAP programming guidelines, and repeating
them in this book will make clear how they contribute to readability. Clean ABAP is
mostly compatible with the ABAP programming guidelines. Differences will be indi-
cated and are always in the spirit of cleaner code.
This guide also respects the “Recommendations for ABAP Development” (http://s-
prs.co/v519001), which was published by the German SAP User Group (DSAG) long
before clean ABAP. We highly appreciate the effort taken there and worked out many of
the topics in more detail to pin them down to concrete code examples.
Many more guides are available for ABAP, especially for specific topics such as object-
oriented programming or security. Although we’re trying to be comprehensive, inevitably,
we may come to points where the guides start contradicting each other. For example,
clean ABAP’s recommendation to make methods small may contradict some performance
guide recommendations to make methods not too small.
This apparent contradiction doesn’t mean that one guide must be wrong and you
should ignore it. This inconsistency only indicates that different guides look at differ-
ent aspects and that most things in life are trade-offs. We’ll point out the most import-
ant contradictions in this book and try to guide you through situations when you
would want to stick to clean ABAP and when you would not.

1.6 How to Engage with the Clean ABAP Community


The clean ABAP community lives online and is open to everyone. You can engage with
us in a variety of ways:
쐍 Chat
Many of us love chatting. Join our Slack workspace https://sapcodestyleguides.
slack.com if you want to discuss guidelines or want to gather feedback on changes
you’d like to propose.
If you have an @sap.com email address, you can join right away. Otherwise, send an
email to [email protected], and we’ll invite you to the Slack workspace.
We’ll always ask you for permission before using anything you write on Slack in the
Clean ABAP repository or in future editions of this book. If you don’t want your con-
tent included, we’ll produce variations that reflect the spirit of the intended change.
쐍 Questions and answers
If you have trouble understanding a guideline or would like to clarify your own spe-
cial use cases, we recommend sending a question to one of the question-and-answer
platforms that deal with ABAP.

30 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


1.7 Summary

SAP also has its own forum, available at https://answers.sap.com/index.html, where


you can reach ABAPers of all trades.
A little less frequented by ABAPers, but always worth a try, is the widely known
https://stackoverflow.com, where tagging your question with “abap” will ensure that
your question reaches the right people.
쐍 Repository
The Clean ABAP repository forms the interactive entry point for most of clean
ABAPers. This place will help you to adopt clean ABAP or produce your own variation
of it: http://s-prs.co/v519002.
This repository’s guide describes easy ways you can use pull requests, issues, and
forks to contribute to the Clean ABAP repository, from simple things like fixing
typos to in-depth discussions of vital changes: http://s-prs.co/v519003.

1.7 Summary
We hope this introduction provided you with an overview of what clean ABAP is, where
it comes from, and how it fits into the ABAP world. You should have an idea that it’s
easy to get started with clean ABAP in both greenfield projects and in legacy brownfield
projects. Hopefully, we also encouraged you to engage with our community.
The following chapters in this book describe individual recommendations in detail,
starting with recommendations for the ABAP language in Chapter 2. Refer to the final
chapter, Chapter 14, for more ideas on how to work with clean ABAP in a team.

Personal Copy for Andrei Arabolea, [email protected] 31


© 2025 by Rheinwerk Publishing Inc., Boston (MA)
Chapter 2
The ABAP Language
Every programming language has specific strengths and operates in a
unique ecosystem of coders, projects, and practices. Use clean code to
boost these elements, not fight against them.

This chapter provides some general recommendations rooted in the programming lan-
guage itself and the contexts the language is typically used in. This chapter applies to
ABAP as a whole and should be considered on all levels of detail, from individual vari-
ables to whole packages. We’ll cover many topics in this chapter, starting with recom-
mendations for dealing with legacy code in Section 2.1 and for improving performance
in Section 2.2. Then, we’ll explain why we prefer object-oriented programming over
procedural programming in Section 2.3 and prefer functional language to procedural
language in Section 2.4. The last two sections of this chapter describe more generally
why obsolete language elements should be avoided (Section 2.5) and why you should
use design patterns wisely (Section 2.6).

2.1 Mind Legacy Code


In Chapter 1, Section 1.3, we described how you can start clean ABAP in legacy projects
from a team and working mode perspective. This section describes how you can make
new content and recommendations blend in with legacy code.
SAP landscapes typically sport a large variety of ABAP releases, from very old to very
new. Although SAP’s contracts usually provision regular upgrades for long periods of
time, most customers are reluctant to actually implement frequent updates.
One reason for this reluctance is that upgrading an ABAP system is only semi-automated
and often involves expensive additional manual work. Another reason is that SAP sys-
tems are often mission-critical, and planned downtimes required by upgrades are hard
to deal with. As a result, you may find yourself confronted with the task of cleaning
code in several different ABAP versions.
The following are some tips and recommendations for clean ABAP when it comes to
legacy code:
쐍 Mostly independent
Most of clean ABAP’s recommendations work in any kind of ABAP release.

Personal Copy for Andrei Arabolea, [email protected] 33


2 The ABAP Language

For example, the side conditions for formatting and commenting have not changed
for decades, and you’ll be able to use clean ABAP in the exact recommended way
throughout your entire landscape.
쐍 Ignoring irrelevant constructs
With some topics, you’ll find examples with statements that may be unavailable in
your oldest systems. However, you’ll also find that you can simply ignore those
statements and apply the concepts anyway.
For example, the listings in Chapter 3 demonstrate the workings of the object orien-
tation use of inline data declarations, for instance, in the following statement:

DATA(total) = money_utils=>calculate_total( items ).

The inline declaration itself, however, is not required at all to get the point or to
implement the pattern, and you’ll still be able to use recommendations in older sys-
tems with perfect accuracy.
쐍 Cutting back latest constructs
Some sections in this book do recommend using the latest language constructs. In
these cases, you might want to identify the oldest release you need to support and
then agree on a form that is available in that version.
Most of the latest language constructs don’t increase ABAP’s abilities; they simply
reduce the amount of code you need to write. Falling back on an older form thus
may be a little less readable, but you won’t lose any features.
For example, we recommend using shorthand calculation assignments like sum +=
42. to make your code shorter and more concise, but the old construction sum = sum +
42. works just as well and is still clean enough.
쐍 Compensating unavailable constructs with methods
In many cases, you can compensate unavailable language constructs by adding
more methods. For example, perhaps, you cannot use the VALUE operator, as in the
following:

DATA(employee) = VALUE #( first_name = 'John'


last_name = 'Doe' ).

Adding a method will still give you clean code, as in Listing 2.1.

DATA employee TYPE employee_structure.


employee = create_employee( first_name = 'John'
last_name = 'Doe' ).

Listing 2.1 Adding Methods can Make Code Cleaner Where Short Forms are Unavailable

쐍 Keeping the codebase harmonized


Our experience with legacy code suggests that keeping the codebase in sync across a
selection of systems is a good idea. This recommendation is especially true if your
development system has a newer kernel than the downstream quality, consolida-
tion, and production systems, as is often the case in SAP’s internal landscapes.

34 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


2.2 Mind Performance

Although you could use the latest language constructs in the development system,
transporting the code would lead to compilation errors in your downstream sys-
tems. Deciding to not use the latest constructs in that whole set of systems ensures
trouble-free code transports between any two systems.
Another good idea is keeping the code style in sync even if the systems are not con-
nected. Not only may developers need to switch between these systems on a regular
basis, but they may even have different versions open at the same time, in multiple
windows on their desktops. Agreeing on one code style for all spares people the
extra seconds required to ascertain what version they are working in.
쐍 Splitting the codebase
In some cases, splitting the codebase into one part that uses newer constructs and
another that uses the older ones might be preferable. This division will usually be
the case when you really need the newer constructs because they provide new abili-
ties.
For example, the relatively new ABAP-Managed Database Procedures (AMDPs) sim-
plify interactions with database objects so much that you may want to use them
even though some older systems in your landscape don’t support them.
In this case, you should make the split obvious and clean by keeping the code in dif-
ferent packages or transport layers. Your developers can then see at a glance what
“release branch” the code belongs to, and you can prevent accidental slip ups.
쐍 Revise when updating
If you agree on an older style of code, remember to revise that decision when you
upgrade your oldest systems, or else the original reason for your decision may be
lost.

2.2 Mind Performance


A main concern when starting with clean ABAP is that it might degrade the perfor-
mance of existing programs. After all, some suggestions we make seem to contradict
the best practices found in the literature on performance. Let’s look at the facts.

2.2.1 Mostly Harmless


A large portion of clean ABAP deals with topics that have no influence on performance
whatsoever. Take naming, formatting, and comments, for example: The compiled code
won’t differ, no matter how you format it or how many comments you put in. Good
names are not necessarily longer than bad names, and so good names don’t consume
more memory. The CPU doesn’t care about variable names anyway, so speed won’t dif-
fer either. A large portion of clean ABAP, therefore, can be applied without worrying
about performance.

Personal Copy for Andrei Arabolea, [email protected] 35


2 The ABAP Language

Another portion of clean ABAP with little impact on performance deals with areas
where performance is not of primary interest anyway. This portion applies to activities
like exception handling and unit testing. Of course, no one wants to wait too long for
unit tests, especially in test-driven development (TDD), but most people will agree that
understanding the tests is much more important than squeezing out the last microsec-
ond. Exception handling deals with error situations where the software has already
failed, and most people don’t care whether 0.8 or 0.9 seconds pass before an error
reaches the user interface.
In summary, the majority of clean ABAP principles do not impact performance and can
be safely applied to any project. Even if you restrict yourself exclusively to these por-
tions of clean ABAP, you’ll still benefit considerably.

2.2.2 Start Clean, Degrade Only Where Needed


The main topic that worries people is the suggestion to make classes and methods
small. Following this advice can make things a little slower and bigger. Smaller classes
often mean more objects, which in turn usually consume more memory and need
more time for their instantiation. Smaller methods mean more method calls and thus
deeper call stacks. While many languages employ low-level compiler optimizations like
inlining that literally make method borders disappear at the CPU level, ABAP takes its
time to check data types upon each call. These differences are small on an individual
scale, but in large programs, they can add up to a point where it may be necessary to
break with certain clean ABAP rules.
Before you conclude that your project must be the one exception that requires such
strict performance that you must throw clean ABAP overboard, consider that clean
ABAP was first conceived in a high-performance application. SAP assurance and com-
pliance software was one of the first ABAP solutions based on SAP HANA, and SAP
wanted to showcase the database’s performance by sticking to ambitious performance
goals, such as subsecond response times for nearly all user interactions. If we could
reach this objective with clean ABAP in our codebase, why wouldn’t you?
In fact, we’ve only seen one place where clean ABAP was in the way. We wanted to
enable the downloading of a list of 1,000,000 documents as an Excel sheet, but the
object-oriented piece of code that assembled the XML file instantiated so many objects
that the process ran out of memory. Cutting back on the object orientation in selected
places kept memory consumption in check. This scenario was the only concession to
disregard clean ABAP we’ve made in several years of development.
Consider the theoretic side: Imagine software A has good response times but messed
up code, and software B is the exact opposite, with clean code but messed up response
times. Both extremes are undesirable, of course, but not equally so. Software A makes
it nearly impossible to escape a dead end without a complete rebuild, an activity that is
so expensive it is unlikely to ever happen. In contrast, software B’s bad performance

36 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


2.3 Prefer Object Orientation to Procedural Programming

can likely be improved by refactoring or swapping out critical parts, steps that have
rather more agreeable effort estimations thanks to its clean design and high automatic
test coverage.
We can condense these ideas into the following recommendation: Start with a clean
design and clean code. If performance requirements force you to move away from the
clean ABAP suggestions, do so little by little, in as few selected places as possible. This
approach will guide you towards an overall result that is both clean and fast.

2.2.3 Measure, Don’t Assume


A long time ago, somebody measured that RETURNING large tables from methods was
much slower than EXPORTING them. This idea went into the documentation, literature,
and training courses. The Code Inspector was even created to check for this situation.
When people quoted that rule, they would nod wisely and leave it undisputed.
When we came up with clean ABAP, we found that RETURNING tables would allow us to
write shorter and more concise code. We therefore challenged the RETURNING versus
EXPORTING rule and conducted a series of measurements that suggested that both ways
were equally fast in all recent ABAP releases. We talked about and ticketed some, and
finally, the ABAP language group confirmed that, a while back, they added an optimiza-
tion that made both variants use the exact same memory mechanism; no performance
difference exists either in theory or in practice.
If you deal with performance, don’t be easily misguided by obscure fears and hearsay.
Base your decisions and coding patterns on real measurements that you perform for
the individual occurrences that you want to question.

2.3 Prefer Object Orientation to Procedural Programming


Object orientation is a key ingredient of clean ABAP. We’re no object orientation evan-
gelists, however, and we actively advise against converting all your code to Gang of
Four (GoF) design patterns. However, object orientation adds that little extra spice that
makes decoupling easy and is thus a must-have in ABAP. In this section, we’ll compare
procedural and object-oriented programming and describe how best to balance the
two in your code.

2.3.1 ABAP’s Programming Paradigm


For better orientation, let’s take a second to reiterate some predominant programming
paradigms and position ABAP among them:
쐍 “Unstructured” programs
Jump around freely and wildly with GOTO <line number> statements. ABAP never sup-
ported this kind of behavior, which encourages unclean code too much.

Personal Copy for Andrei Arabolea, [email protected] 37


2 The ABAP Language

쐍 Procedural programs
Replace go-tos with functions and local variable scopes that make it harder to acci-
dentally call or access the wrong thing. ABAP started as a procedural language.
쐍 Object-oriented programs
Allow instantiating the same function multiple times for different contexts, adding
interfaces and inheritance on top. ABAP adopted the object-oriented paradigm in
the early 2000s.
쐍 Functional programs
Remove states and make functions free of side effects. You can realize some func-
tional patterns in ABAP, but missing parts like lambda functions and streams will
prevent you from complete adoption.
쐍 Declarative programs
Do not list what step to take one after another; only describe the target picture that
should be reached. While ABAP itself is not declarative, the closely associated SQL
traditionally counts as declarative.

2.3.2 The Difference between Function Groups and Classes


When comparing ABAP’s procedural and object-oriented parts, we can generally say
that classes resemble function groups.
In simple terms, think of a function group as a CLASS that is GLOBAL, ABSTRACT, and FINAL.
Functions, form routines, and global variables are its members, and they are all STATIC
and PUBLIC.
While experienced object-oriented developers immediately see the problems in proj-
ects that limit themselves to these modifiers, new clean coders routinely ask why
exactly they should favor one modifier over another. Let’s look at the differences next.

No Instantiation
At any given time, only one instance of a function group ever exists. This fact may be of
little importance for its functions, which are always the same anyway, but it matters a
lot for its variables.
Imagine you wanted to produce a function group PRODUCT that represents a single prod-
uct. Its variables would store the product’s ID and properties. A function READ_PRODUCT
would fill those variables in from the database.
Consumers need to call READ_PRODUCT every time before they interact with that function
group. They cannot rely on it to still contain the data from the last interaction because
some other consumer might have used the function group in the meantime, filling it
with a different product. This limitation clutters your business code with repeated (and
business-wise meaningless) initialization calls.

38 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


2.3 Prefer Object Orientation to Procedural Programming

The repeated calls to READ_PRODUCT also hurt performance. Introducing a clever buffer
that retains the last n “hot” products may avoid repeated database reads. But even so,
the function will overwrite the same variables, again and again, each time taking a little
extra time for no gain.
Now, consider how easy realizing the same functionality with classes and objects is in
comparison: The class PRODUCT represents an individual material. When instantiating
that class, no matter whether with NEW or with a static constructor method, its instance
variables are filled with the product’s ID and other properties. That object can now be
used by one or more consumers in a reliable way, without the need for additional buf-
fers and repeated initialization.

No Inheritance
A function group cannot inherit members from other function groups. While clean
ABAP argues that inheritance should be used rarely, inheritance does have its benefits
when applied wisely.
For example, consider you wanted to create a function group PERSONS and another func-
tion group EMPLOYEES as a specialization of PERSONS. Both PERSONS and EMPLOYEES should
share a common set of properties, such as names and addresses, and functions, such as
WRITE_EMAIL and GET_PICTURE.
Function groups by themselves are unable to realize this pattern. You’ll find names and
addresses in PERSONS, but not in EMPLOYEES. Similarly, PERSONS will offer the WRITE_EMAIL
and GET_PICTURE functions, but EMPLOYEES will not.
This situation may not seem problematic. Of course, you can write a design document
or other documentation that explains that EMPLOYEES can use all those things from PER-
SONS. But such “comment-only relationships” are dangerous because they cannot be
verified in the code and tend to break when people start forgetting about these connec-
tions as time goes on.
Classes, in comparison, realize this schema in a natural way. A class like EMPLOYEE can
simply be INHERITING FROM PERSON and see and use all properties and methods that are
propagated as PUBLIC or PROTECTED. Those relationships are obvious from the code, the
compiler verifies them, and the tools will even support you in refactoring them.

No Method Namespace
Functions may be in function groups, but they also remain in the same global name-
space. As a result, you cannot have two function groups with a function DO_SOMETHING.
You can, and may often find yourself with, two classes that have methods with identi-
cal names, but rebuilding this with functions requires that you add prefixes, suffixes,
and the like to your function names because all functions lie next to each other in the
ABAP Data Dictionary, and their names must be universally unique.

Personal Copy for Andrei Arabolea, [email protected] 39


2 The ABAP Language

No Interfaces
An interface is a contract that defines which functions a component must provide and
what their parameters must be. Consumers and providers must adhere to the interface
in the same strict way, and the compiler will reject code that does not comply.
Imagine you wanted to create a set of function groups that offer the same functions
with the same parameters. Following the previous section, you make a concession to
add a prefix to each function name, but otherwise, they should have the same signa-
tures. You could create an additional function group that defines the contract. Many
use a copy-paste template when creating new variants.
However, this approach lacks real safety. People can change any of these function
groups, without anybody noticing. No compiler and no static code check can verify that
the signatures still match.
Things are even worse when trying to make one function group implement several
templates at once. You cannot even ensure that the templates fit with each other,
meaning no overlaps in method and parameter names are introduced.
With classes and interfaces, these things come naturally. The interface defines the sig-
natures, and the classes must follow them. The compiler verifies that things fit together
and will reject deviating code. You can make classes implement several interfaces at
once, and you can even create hierarchies of interfaces that inherit from each other.

Weak Substitution
Without interfaces, you cannot reliably realize substitution, meaning redirecting calls
from one thing to another is both difficult and fragile, and you can’t be sure that it
works.
This problem is a shame for production-grade code because installing dependable
boundaries, which enable you to swap in and out different variants of code, depending
on the use case, is considerably harder.
Even more important, weak substitution is a disaster for unit tests, which cannot redi-
rect function calls to test doubles, unless you devise elaborate redirect patterns or
resort to fragile techniques like test seams.
You can redirect function calls, but consider how fragile this weak substitution is with-
out an interface as a reliable contract, for instance:

DATA function_name TYPE char30.


CALL FUNCTION function_name [...]

First, this code only works if you add it to each and every piece of code that calls
another function. Not only is this a lot of work, but this approach also pollutes your
code with a lot of unhelpful details.

40 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


2.3 Prefer Object Orientation to Procedural Programming

Second, other functions are not guaranteed to fit the parameters you expect. The com-
piler cannot discern what function_name might contain and thus accepts everything.
Errors then become visible too late, when your function call fails at runtime.
Third, you won’t be able to detect changes. For example, adding a mandatory parame-
ter to one of the called functions—and thus breaking the contract—will not lead to any
errors at design time.

Weak Variable Encapsulation


Looking at variables, you might think that a function group’s global variables are “pri-
vate,” such that they cannot be accessed from the outside. If you dig into the language,
however, you’ll find that no real memory protection ensuring the correct work mode
exists. Function group variables can be addressed from anywhere with manipulative
statements, for instance:

ASSIGN ('(my_report)gv_global_variable') TO <a_field_symbol>.

This possibility of manipulation is so fragile and little known that you’ll hardly encoun-
ter it in real programs. However, why run the risk if the object-oriented variant protects
your memory reliably against accidental manipulation?

No Method Encapsulation
Just like variables, function groups also do not really protect their methods. Functions
are public, which is intentional. However, form routines should be private, so that they
cannot be called from the outside.
However, ABAP allows calling any form routine from any piece of code, for instance:

PERFORM set_buffer_true IN PROGRAM my_program.

2.3.3 Differences in ABAP’s Object Orientation


Two cases exist where ABAP’s way of implementing object orientation is different from
what you may know from other object-oriented programming languages like C++, Java,
or C#. You could call these “limits,” but in fact, these cases do not really reduce the capa-
bilities of the language, only make the syntax a little more verbose. We’ll discuss both
cases in this section.

No Overloading
Overloading means that a class offers multiple methods with the exact same name, but
slightly different parameters.
For example, you could have a variant of the method log that receives a string input
parameter, and another variant of the method that receives an integer. The compiler

Personal Copy for Andrei Arabolea, [email protected] 41


2 The ABAP Language

analyzes the way in which the method is called and directs to the call to the correct
method.
While overloading is a common pattern in Java, ABAP does not support it and enforces
that each method name occurs only once in a class. This limitation is purely syntactical
and does not reduce the language’s expressiveness. For example, ABAP would force
you to distinguish the LOG_STRING from the LOG_INTEGER method, making the code less
nice to read, but we would still be able to log both strings and integers.
People sometimes argue that OPTIONAL input parameters can compensate for the lack of
overloading. However, this claim is only partially true. Overloading enables you to
express which combinations of parameters form valid inputs. Optional parameters
also allow the combining of input parameters that do not make sense together. For
example, a LOG method with two optional input parameters STRING and INTEGER could
be called with both at the same time, or none at all—both situations that the method’s
developer probably didn’t even think about and didn’t unit test, thus leading to unex-
pected results.

Interface Names Become Part of Method Names


When a class implements an interface, the class usually fully “takes over” the inter-
face’s methods, and the code looks as if the methods were original pieces of the class.
In Java, for example, if the class Person implements the interface BusinessPartner that
specifies a method called getEmail, you can write:

Person alice = new Person();


alice.getEmail();

In ABAP, methods that are taken over from interfaces add the interface’s name as a pre-
fix. For the earlier example, ABAPers will need to write:

DATA(bob) = NEW person( ).


bob->business_partner~get_email( ).

This pattern avoids name conflicts if a class implements multiple interfaces at once.
However, note that the interface’s name is not needed if the variable’s type clearly indi-
cates what method is intended, as shown in Listing 2.2.

DATA charles TYPE REF TO business_partner.


charles = NEW person( ).
charles->get_email( ).

Listing 2.2 Typing Variables as Interfaces Avoids Having to Include the Interface Names in a
Method Call

Again, this fact is not a limitation because you can still make a class realize several
interfaces perfectly fine. ABAP’s method calls may only look a little longer than in other
programming languages.

42 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


2.4 Favor Functional Language Constructs over Procedural Language Constructs

2.3.4 If You Have No Choice


In some cases in ABAP, you won’t be able to use object orientation to reach a goal. For
example, classes cannot serve remote function calls (RFCs), and you’ll need to resort to
functions for RFCs.
In these cases, we recommend that you use the procedural programming parts of
ABAP, but make sure they only redirect the call to an object-oriented piece of code, as
in Listing 2.3.

FUNCTION check_business_partner [...].


DATA(validator) = NEW biz_partner_validator( ).
result = validator->validate( business_partners ).
ENDFUNCTION.

Listing 2.3 A Function that Wraps a Class’ Method in a Straight-Forward Fashion

This “wrapping classes with functions” approach allows you to expose the function for
RFCs, while still enabling you to benefit from all advantages that the object-oriented
programming provides.
In turn, in some situations, your object-oriented code is forced to consume non-object-
oriented code from other teams or from legacy components. In these cases, you can
“wrap functions with classes” the other way around and stick to the object-oriented
world as long as possible.
For our earlier example, you would create an interface called business_partner_func-
tions that has a method check_business_partner with the exact same signature as the
function check_business_partner. Then, you would add a class that implements those
methods by simply redirecting the call to the function with the code shown in Listing 2.4.

METHOD check_business_partner.
CALL FUNCTION 'CHECK_BUSINESS_PARTNER' […].
ENDMETHOD.

Listing 2.4 A Method that Wraps a Function in a Straight-Forward Fashion

In most cases, you can now call that interface and class instead of the real function,
allowing you to replace those calls with test doubles and alternative implementations,
as needed.

2.4 Favor Functional Language Constructs over Procedural Language


Constructs
Procedural language constructs are constructs that require their own keywords, for
example, in the following:

DESCRIBE FIELD input LENGTH DATA(length) IN CHARACTER MODE.

Personal Copy for Andrei Arabolea, [email protected] 43


2 The ABAP Language

Functional language constructs resemble regular calls to methods, with any number of
IMPORTING parameters and a single RETURNING parameter, for example, in the following:

DATA(length) = strlen( input ).

In many cases, both variants will enable you to reach the same goal. However, we rec-
ommend preferring functional language constructs wherever possible, for a range of
reasons, such as the following:
쐍 More natural for today’s programmers
Modern programmers are more familiar with the functional style of writing code,
mostly because Java and C# only support functional programming. You may find
that programmers who come from such languages find the mere concept of verbose
procedural code that tries to imitate English rather unusual.
쐍 Shorter
Functional language constructs are usually shorter. The method names alone are
typically already shorter, for example strlen versus DESCRIBE FIELD. Input parame-
ters are also mostly shorter than typically nearly English clauses like IN CHARACTER
MODE.
쐍 No side effects
Functional language constructs are free of side effects, and especially do not modify
their inputs, thus avoiding a whole range of common “spillover” errors in a natural
way.
For example, the statement TRANSLATE my_variable TO UPPER CASE. will change the
variable’s content, while the equivalent DATA(uppercase) = to_upper( my_variable ).
will leave the original variable intact.
쐍 Inline data declarations
Functional language constructs typically support inline data declarations better,
thus reducing the amount of code you need to write to achieve the same thing.
For example, consider the following object instantiation:

DATA some_object TYPE REF TO some_class.


CREATE OBJECT some_object TYPE some_class.

In its functional variant, this code is reduced to a single line, as follows:

DATA(some_object) = NEW some_class( ).

쐍 Less to remember
Both variants require you to remember the keywords or method names that trigger
the statement, but the highly individual additions that procedural statements use
require that you to remember a lot of additional syntactical detail.
For example, the procedural way of getting an object reference, GET REFERENCE OF
accounts INTO accounts_reference., requires that you remember more details like
the words OF and INTO that do not add or vary the function, while the equivalent

44 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


2.4 Favor Functional Language Constructs over Procedural Language Constructs

functional construct accounts_reference = REF #( accounts ). requires only remem-


bering the word REF.
쐍 Method nesting
The returning value of functional language constructs can be used as direct input to
another function, allowing you to nest multiple calls in a single statement, as in the
following:

DATA(harmonized) = condense( to_upper( dirty_input ) ).

Overdoing method nesting can produce ugly code, but in sensible doses, nesting can
be a welcome way to shorten a series of low-level function calls.

The following examples are some typical functional language constructs that are easier
to use than their procedural equivalents:
쐍 Variable assignment
ABAP has its own statement MOVE … TO to assign values to variables:

MOVE 'A' TO variable.

However, it’s shorter, easier to read, and more intuitive to use a simple = to assign
values:

DATA(variable) = 'A'.

쐍 Uppercase conversion
ABAP has a whole range of statements that transform values in one way or another,
for example:

TRANSLATE lowercase TO UPPER CASE.

However, it’s usually easier to remember the more normalized form of built-in
methods that do the same:

DATA(uppercase) = to_upper( lowercase ).

쐍 Incrementing variables
ABAP has its own statements for calculation, such as SUBTRACT, MULTIPLY, and DIVIDE,
and the following:

ADD 1 TO index.

However, most people feel much more familiar with a regular mathematical for-
mula such as the following:

index = index + 1. " < NW 7.54

Meanwhile, most programmers are familiar with the short forms of increments:

index += 1. " >= NW 7.54

쐍 Object instantiation
ABAP has its own statement CREATE OBJECT to instantiate classes:

Personal Copy for Andrei Arabolea, [email protected] 45


2 The ABAP Language

CREATE OBJECT object TYPE /dirty/my_class.

The short form = NEW does the same with less letters, and also brings the variable to a
position where it’s easier to spot:

DATA(object) = NEW /clean/my_class( ).

쐍 Populating internal tables


Most programs deal with tabular data. ABAP does come with comfortable state-
ments to manipulate them, such as the following:

LOOP AT input INTO DATA(row).


INSERT row-text INTO TABLE result.
ENDLOOP.

However, there are newer short forms that condense these standard manipulations
even more and let the reader focus more on the content than the grammar:

result = VALUE #( FOR row IN input ( row-text ) ).

쐍 Finding a row in an internal table


ABAP’s READ TABLE statement is one of the most commonly used:

READ TABLE value_pairs INTO DATA(line) WITH KEY name = 'A'.

However, the angular brackets that serve other programming languages as accessors
for arrays allow condensing these statements even further:

" if entry must exist


DATA(line) = value_pairs[ name = 'A' ].
" if entry can be missing:
DATA(line) = VALUE #( value_pairs[ name = 'A' ] OPTIONAL ).

쐍 Verifying there is a row in an internal table


Checking whether there’s a suitable row in a table is also one of the most common
tasks in ABAP programming. However, observe how obscure the actual existence
check is when we rely on the old form with sy-subrc:

READ TABLE values TRANSPORTING NO FIELDS WITH KEY name = 'A'.


DATA(exists) = xsdbool( sy-subrc = 0 ).

Whereas the newer form line_exists clearly states what we really mean:

DATA(exists) = xsdbool( line_exists( values[ name = 'A' ] ) ).


IF line_exists( values[ name = 'A' ] ).

2.5 Avoid Obsolete Language Elements


When you upgrade your ABAP systems, make sure you check for language constructs
that have become outdated and are now considered obsolete. All versions of the ABAP

46 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


2.5 Avoid Obsolete Language Elements

Keyword Documentation provide an article called “Obsolete Language Elements” that


lists outdated statements, for example, http://s-prs.co/v519004 for SAP NetWeaver 7.54.
You should refrain from using those statements, for a variety of reasons:
쐍 Better readability
Using newer statement forms often improve the readability of your code without
any actual redesign. Section 2.4 lists some cases where this is true, but many more
exist. For example, the @-escaped “host” variables in the statement shown in Listing
2.5 make a little clearer what's a program variable and what’s a column in the data-
base.

SELECT *
FROM spfli
WHERE carrid = @carrier_id AND
connid = @connection_id
INTO TABLE @flights.

Listing 2.5 A SELECT Statement in Modern Form with @ to Escape Host Variables

Compare how this statement highlights what’s part of the database table (names
without @) and what’s part of the program that queries the database (names with @),
in contrast to the obsolete, unescaped form shown in Listing 2.6.

SELECT *
FROM spfli
WHERE carrid = carrier_id AND
connid = connection_id
INTO TABLE flights.

Listing 2.6 A SELECT Statement in Old Form with No Clear Highlighting of Host Variable

쐍 Simpler and safer operation


A variety of constructs were made obsolete because they do not fit modern pro-
gramming paradigms, even to the point where using these constructs is difficult
and/or error-prone.
A typical example are internal tables with header lines, where my_table would
address the header row of the table, while only my_table[] would address the actual
table content. Even experienced ABAPers repeatedly found themselves in a com-
mon pitfall—having forgotten the square brackets and accidentally modifying only
the header row, then requiring hours searching for the cause of the subtle bugs this
entails.
The shift of perspective when using ABAP structures more intensively improves the
interaction between structures and tables with better statements and leads to the
recommendation to avoid using tables with header lines entirely, thus also avoiding
these errors in a natural way.

Personal Copy for Andrei Arabolea, [email protected] 47


2 The ABAP Language

쐍 Ongoing improvement
Obsolete language constructs no longer receive improvements, and thus, you won’t
benefit from any future optimization in terms of processing speed and memory
consumption. Do not cut yourself off from such improvements.
쐍 Easier for new colleagues
Obsolete language elements are removed from ABAP training resources so that new
colleagues who newly learn the language would not learn about obsolete language
at all. As a consequence, new ABAPers may not be able to start coding right away but
may need additional upskilling in old-fashioned statements before safe code can be
produced.

2.6 Use Design Patterns Wisely


Software design patterns are elegant solutions to common programming problems.
While they can greatly simplify your design, design patterns can become harmful when
overused.
Two anti-patterns to keep in mind include the following:
쐍 The first anti-pattern is to apply a design pattern everywhere without evaluating
whether it makes sense in the individual places.
쐍 The second anti-pattern is to mix together too many design patterns, such that what
should become simpler instead becomes harder to understand.

We’ll discuss both in the following sections.

2.6.1 Too Many Singletons


A typical design pattern that is overused is the singleton pattern. This pattern ensures
that only one instance of a class exists by preventing outsiders from instantiating the
class on their own, thus forcing them to retrieve the same one-and-only instance
through a static method, for instance, in Listing 2.7.

"
CLASS some_class DEFINITION.
PUBLIC SECTION.
CLASS-METHODS:
class_constructor,
get_instance
RETURNING VALUE(result) TYPE REF TO some_class.
PRIVATE SECTION.
CLASS-DATA the_singleton TYPE REF TO some_class.
ENDCLASS.

CLASS some_class IMPLEMENTATION.

48 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


2.6 Use Design Patterns Wisely

METHOD class_constructor.
the_singleton = NEW some_class( ).
ENDMETHOD.

METHOD get_instance.
result = the_singleton.
ENDMETHOD.

ENDCLASS.

...

DATA(my_object) = some_class=>get_instance( ).
"

Listing 2.7 A Typical Implementation of the Singleton Pattern

This pattern is an excellent choice if you have good reason to force all consumers to use
the same instance. For example, the singleton class might have a buffer, and you’d like
to avoid consumers from getting contradictory data because they use different
instances with different buffer states.
However, in this scenario, we’ve often observed coders turn nearly every class into a
singleton, based on the sole reason that instantiating the same thing multiple times
does not “make sense.”
While perhaps true, people often overlook the fact that the singleton pattern is not free.
The pattern requires code to realize, code that must be written and tested and that can
be incorrect. Adopting this pattern also makes things more complicated. For example,
replacing singletons with test doubles in unit tests becomes more difficult.
You should always question whether the extra cost of adding a pattern is worth the
effort. After all, the consumers can still do a myriad of things that do not make sense,
and we cannot safeguard against all of them.
Do not confuse the singleton pattern with a static creation method. Static constructors
are designed to harmonize object creation. Static creation methods may also take con-
trol of whether a new object is created or an existing object reused, but this goal is not
their primary goal. The singleton pattern takes care of the one-instance-only aspect
alone.

2.6.2 Too Many Patterns Mixed


For the second anti-pattern—mixing too many patterns—imagine a class that is a sin-
gleton, decorator, builder, and observer at the same time. All these patterns address dif-
ferent needs and do not contradict each other, so they can be mixed. But should we mix
these patterns?

Personal Copy for Andrei Arabolea, [email protected] 49


2 The ABAP Language

Looking at the resulting class, you’ll have a hard time figuring out whether a specific
statement forms part of one pattern or another. The code mixes different intentions,
something that we strongly advise against with other guidelines such as “do one thing”
because mixing intentions is confusing.
To determine whether to apply a software design pattern, ask yourself the following
questions:
쐍 Does it really add value, or could I reach the same result without it?
쐍 Does adding the pattern water down the responsibilities too much?

2.7 Summary
We hope this chapter provided you a good overview of how clean ABAP fits with the
paradigms and basic language constructs of the ABAP programming language. You
should have new insights about where it makes sense to use newer language constructs
and how you can fit older constructs in elegant ways or evolve them into newer forms
over time. We hope we’ve made clear that clean ABAP is about making the most out of
the existing opportunities, not arguing about style or generational in-fighting.
As a general recap, we discussed the following clean ABAP guidelines in this chapter:
쐍 You can safely apply most of clean ABAP to legacy code.
쐍 You can avoid unavailable constructs or “emulate” them with methods to still get
clean code.
쐍 You can safely apply most of clean ABAP to performance-critical code.
쐍 It’s better to start clean and then optimize, instead of dirtying code with premature
optimization.
쐍 Performance isn’t guesswork but should be backed up with measurements.
쐍 Object-oriented programming helps in cleaning code.
쐍 There are key differences between object-oriented and purely procedural code.
쐍 Functional constructs can make your code easier to read than procedural state-
ments.
쐍 You should regularly review the statements you use.
쐍 Using design patterns just for the sake of them makes code worse, not better.
쐍 Mixing up too many design patterns makes code hard to understand.

The following chapter will make you more closely acquainted with classes and inter-
faces, probably the most important development objects that ABAP offers.

50 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Chapter 3
Classes and Interfaces
This chapter deals with classes, interfaces, and related constructs. We’ll
describe how to design and use these constructs from a clean code per-
spective and explore related object-oriented programming tenets and
principles.

ABAP has always been a programming language close to the database. The ABAP Data
Dictionary, Open SQL, and more recently core data services (CDS) views are important
tools that deal very effectively with data and database operations. Data still is and
always will be central to ABAP. The other side of the equation, and one that has tradi-
tionally been tackled by procedural programming, is behavior.
Ever since the introduction of ABAP Objects, the central design paradigm that deals
with program behavior in ABAP has shifted from procedural programming to object-
oriented programming. The ABAP flavor of object-oriented programming is similar to
many mainstream programming languages like Java and C#, centered around classes
and interfaces.
This chapter guides you through structuring class and interface design and high-level
code organization within ABAP Objects. In Section 3.1, we’ll describe several guidelines
for interfaces and classes and then, in Section 3.2, discuss the scope and visibility of
classes and its members. Finally, we’ll delve into how to define and use constructors
and related elements in Section 3.3.

3.1 Object Orientation


A detailed explanation of object-oriented design and development is beyond the scope
of this book, but many books have been written about this broad topic with its myriad
principles, patterns, and benefits.
Briefly, object-oriented programming allows for a better match between the design of
your system and the real-world problem it is trying to address and often results in a
system that’s more flexible, maintainable, and testable than what would be achieved
with procedural programming. Its main high-level characteristics are typically the fol-
lowing:

Personal Copy for Andrei Arabolea, [email protected] 51


3 Classes and Interfaces

쐍 Abstraction
The ability to define reusable concepts separate from their implementation details.
In ABAP, abstraction is about designing interfaces and classes.
쐍 Encapsulation
The packing of data and methods that work on that data together while allowing the
hiding of unimportant implementation details from consumers. Encapsulation is
realized in ABAP by classes; by scope (global or local); by visibility (public, protected,
or private); and by packages and package interfaces.
쐍 Inheritance
A form of reuse or redefinition where some concepts or implementations can be
based on other concepts. A class can inherit from another class, and a class or an
interface can implement or inherit from multiple interfaces.
쐍 Polymorphism
The ability to refer to a concept regardless of its concrete implementation, which can
take many forms. A concrete class instance can be referred to by a superclass refer-
ence; an object can be referred to by the interfaces it implements.

When the ABAP Objects paradigm was first introduced, ABAP developers that were
used to coding in function modules and function groups found an easy path to enter-
ing the new ABAP object-oriented world by considering classes as analogous to func-
tion groups and static methods as analogous to function modules.
However, this easy path was often overused and turned out to be a missed opportunity
to improve the design of the code and to learn more deeply about object-oriented pro-
gramming, its tenets, and its benefits.
In this section, we’ll discuss the many design decisions available for classes and inter-
faces and explore how best to facilitate object-oriented design in clean ABAP code.

3.1.1 Interfaces
Interfaces define the abstract representations of the concepts in your solution domain
and specify what kinds of operations they support. Interfaces are purely abstract; that
is, they have no implementation details, which makes them ideal for defining object
types and dependencies, for decoupling classes, and for improving isolation and test-
ability. In this section, we’ll walk through how to define interfaces and think about their
evolution.

Interface Declaration
The account interface shown in Listing 3.1 defines the operations available for the
“account” concept in your domain.

INTERFACE account
PUBLIC.

52 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.1 Object Orientation

TYPES:
BEGIN OF money,
amount TYPE decfloat34,
currency TYPE currency,
END OF money.

METHODS deposit
IMPORTING amount TYPE money.

METHODS withdraw
IMPORTING amount TYPE money.

METHODS get_balance
RETURNING VALUE(result) TYPE money.

ENDINTERFACE.

Listing 3.1 The account Interface Defining the Operations of the “Account” Abstract Type

Accounts have a balance (get_balance), and they support depositing (deposit) and with-
drawing (withdraw) money. The interface also defines the money structure type, which it
will use in its operations.
Structures and internal tables that are exclusive to the interface (like money in Listing 3.1)
can be declared within the interface. Where you declare these exclusive items depends
on how tightly you want these objects associated with the interface and how conve-
nient you want to make its external usage. For instance, an external usage that looks
like account~money as opposed to just money indicates that money is declared within the
account interface. For the account interface, in our example, money should actually be
declared in the ABAP Data Dictionary, since the structure type money likely has much
broader applicability than simply within accounts.
The get_balance method also doesn’t define which currency it returns its result in. This
choice is left for the concrete account implementations to decide. Each implementation
of the account interface can either only accept money in a specific currency or work
with a currency conversion service and define a target currency that get_balance would
use. These considerations should inform your design as you flesh out your domain
model.

An Interface Models the Behaviors of an Abstract Type


Use interfaces to model the abstract concepts in your domain and the operations that
they support. An interface defines “what” the classes that implement it do. A useful
way is to think that an object that does or acts-like the interfaces its class implements.
A checking_account acts-like an account.

Personal Copy for Andrei Arabolea, [email protected] 53


3 Classes and Interfaces

How far you go with decomposing behaviors depends on your design requirements. If
in your domain you could have clients that work with money sources independent of
money targets, having separate interfaces for each makes sense, and then you would
have the account interface inherit from both. This scenario is an example of the inter-
face segregation principle, the I in SOLID, which we’ll discuss later in this section.
According to the interface segregation principle, interfaces should be designed with
specific client needs in mind. Clients must drive interface design. Assuming the money
structure now lives in the ABAP Data Dictionary, you could use the design shown in
Listing 3.2.

INTERFACE money_source
PUBLIC.

METHODS withdraw
IMPORTING amount TYPE money.

ENDINTERFACE.

INTERFACE money_target
PUBLIC.

METHODS deposit
IMPORTING amount TYPE money.

ENDINTERFACE.

INTERFACE account
PUBLIC.

INTERFACES:
money_source,
money_target.

ALIASES:
withdraw FOR money_source~withdraw,
deposit FOR money_target~deposit.

METHODS get_balance
RETURNING VALUE(result) TYPE money.

ENDINTERFACE.

Listing 3.2 Further Segregated Domain: “Account” Now Decomposed in “Money Source” and
“Money Target”

54 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.1 Object Orientation

The account interface now inherits from the two other interfaces money_source and mon-
ey_target. Any class implementing account must also implement the interfaces account
inherits from. In this example, account also declares aliases for the inherited methods,
which facilitates their implementation and usage.

SOLID Principles
A useful set of object-oriented principles to always have in mind when designing are
the SOLID principles, defined in the following way:
쐍 Single-responsibility principle
An interface or class should have a single, focused responsibility.
쐍 Open-closed principle
Make your classes open for extension but closed for modification.
쐍 Liskov substitution principle
A subclass should follow the same semantics of its superclass and be able to
replace it without breaking its contract and invariants.
쐍 Interface segregation principle
Clients should depend on client-specific interfaces with only the set of operations
they require.
쐍 Dependency inversion principle
Depend on the abstract representation of underlying resources, not on their con-
crete implementations.

Many of the high-level recommendations you’ll learn in this book attempt to guide
your designs and implementations closer to following the SOLID principles.

Interfaces can define static methods, which you’ll also have to implement as static
methods in derived classes. However, calling a class’s implementation of such a
method requires knowledge about that specific class name, which defeats the purpose
of having an interface in the first place since it doesn’t result in an abstraction of the
concrete class.
A good use of static methods in interfaces, though, is to declare factory methods, which
could be called dynamically in a kind of plugin architecture, where the concrete class
names are stored in a table or retrieved through generic ABAP Data Dictionary func-
tions. More details on this topic can be found in Section 3.3.3.
Interfaces can also have instance attributes, static attributes, and constants. These
kinds of members declare interface data, which is a kind of implementation detail that
usually has no place in an interface.

Personal Copy for Andrei Arabolea, [email protected] 55


3 Classes and Interfaces

Only Declare Types and Methods in Interfaces


Interfaces should only be used to declare types that are exclusive to the interface and
instance methods (instance events also qualify). Static methods could be used as fac-
tory methods for generic usage. Any other construct defeats the purpose of an inter-
face.

Interface Evolution
An interface is a purely abstract concept. As such, interfaces are only useful if they are
ultimately implemented by one or more classes. This relationship between class and
interface creates a dependency in which changes to an interface will impact all the
classes that implement it and any code that uses the interface. For interfaces defined
for code external to your project, this relationship must be carefully managed, so that
client implementations don’t break.
Therefore, you must take great care when designing interfaces to be used externally to
your project. These interfaces should model the most abstract, but still useful, concepts
related to your domain and at the same time be as stable as possible. Even so, some-
times, you’ll need to extend an interface with new methods. Optional methods, which
are methods that don’t need to be implemented, can be used for extending an interface
without breaking compatibility with the classes that implement it. Optional methods
come in the following two flavors:
쐍 DEFAULT IGNORE
Methods marked with DEFAULT IGNORE, when not implemented in a class, are simply
treated as if they have an implementation with an empty body. These methods can
also be used to add optional methods to an interface when the interface is first cre-
ated, not just when extending it.
쐍 DEFAULT FAIL
Methods marked with DEFAULT FAIL, when not implemented in a class, throw a run-
time exception when called. These methods can be used to force clients to adapt
their code, without causing a syntax error when the new method is introduced.

For example, you can use the command design pattern to define a command-type
interface with optional pre- and postprocessing steps, as shown in Listing 3.3.

INTERFACE processor
PUBLIC.

METHODS pre_process
DEFAULT IGNORE
IMPORTING context TYPE REF TO context.

56 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.1 Object Orientation

METHODS process
IMPORTING context TYPE REF TO context.

METHODS post_process
DEFAULT IGNORE
IMPORTING context TYPE REF TO context.

ENDINTERFACE.

Listing 3.3 The processor Interface

In this example, the processor interface defines two optional methods for pre- and
postprocessing, both marked as DEFAULT IGNORE. Each method is passed a context (not
shown) with which the processing at each stage can be influenced.
Therefore, a class implementing the processor interface only needs to provide the pro-
cess method. The pre_process and post_process methods are optional and can be
skipped by implementing classes.

The Command Design Pattern


The command design pattern is one of the classic patterns presented in the seminal
book Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley,
1994).
The command design pattern is used to capture an action with all its required parame-
ters. The action, or command, can then be executed later.

3.1.2 Classes and Objects


Classes are used to write code that implements the behavior of the system, defining
“how” the system works. In this section, we’ll discuss different types of classes and the
crucial topics of inheritance and composition.

Classes in Action
Most classes in an application exist as part of a larger design. Classes depend on under-
lying resources and are in turn depended upon by other components. These dependen-
cies are better managed by using class instances (objects) with dependency injection
for the needed resources (described in more detail later in Section 3.3.2), which makes
the design more flexible, reusable, and testable.

Personal Copy for Andrei Arabolea, [email protected] 57


3 Classes and Interfaces

Use Classes as Blueprints to Create Objects


Coming from a procedural programming world where function modules were king, you
might be tempted to use classes with static methods to replicate the kind of designs
that had been the norm in ABAP.
Resist that urge. Embrace the object-oriented paradigm. Create classes that are meant
to be instantiated, with instance methods and attributes. Implement interfaces that
define the concepts your class represents. You’ll benefit from better flexibility and iso-
lation, which improves the design on multiple levels, including improved maintainabil-
ity and testability.

The piggy_bank class shown in Listing 3.4 implements the money_target interface and
uses a money_list (an internal table of money) to store all the money it gets. Until you
implement a way to break the piggy bank and recover all its money, there’s no way to
get money out of it.

CLASS piggy_bank DEFINITION


PUBLIC
FINAL
CREATE PUBLIC.

PUBLIC SECTION.

INTERFACES:
money_target.

ALIASES:
stash FOR money_target~deposit.

PRIVATE SECTION.

DATA:
money_list TYPE money_list.

ENDCLASS.

CLASS piggy_bank IMPLEMENTATION.

METHOD stash.
APPEND amount TO money_list.
ENDMETHOD.

ENDCLASS.

Listing 3.4 The piggy_bank Class: A Blueprint to Create Piggy Bank Objects

58 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.1 Object Orientation

Classes have a DEFINITION section, where the class interface is declared, and an IMPLE-
MENTATION section, where all its methods are implemented. To use the piggy_bank class,
you’ll need to create class instances, or objects, as shown in Listing 3.5.

DATA(my_piggy_bank) = NEW piggy_bank( ).

" stash money away


my_piggy_bank->stash( VALUE #( amount = 2 currency = 'EUR' ) ).

Listing 3.5 my_piggy_bank: An Instance of the piggy_bank Class

Since piggy_bank implements the money_target interface, any code that works with a
money_target interface can also work with an instance of piggy_bank, as shown in Listing
3.6. The method stash on piggy_bank is an alias for the method deposit on money_target.

DATA target TYPE REF TO money_target.


target = my_piggy_bank.

target->deposit( VALUE #( amount = 1 currency = 'EUR' ) ).

Listing 3.6 my_piggy_bank as a money_target

Abstract Classes
Abstract classes are classes that cannot be instantiated; they are designed to be base
classes. A concrete subclass will inherit from the abstract class and finish it off by
implementing its abstract methods. (More details about inheritance can be found later
in this section.)
Abstract classes can provide implementations for common methods and leave other,
more specific methods abstract. These classes can define high-level policies or algo-
rithms and leave the details of certain steps in their process for their subclasses to
implement.
These classes can provide a skeleton implementation for an interface, making it easier
for their subclasses to implement the interface by encapsulating common code that
many different classes would have to duplicate otherwise.
Although interfaces and abstract classes share some properties, they are not equivalent,
and you should not treat one as an alternative to the other. Classes can only inherit from
a single class, calcifying its inheritance hierarchy in the process. Interfaces are purely
abstract, and a class can implement multiple interfaces at the same time. Each interface
a class implements is a role the class can play. Its single base class (also known as a super-
class), on the other hand, is intrinsically connected with its core identity.
The partial implementations provided by abstract classes might not be appropriate or
fitting for all cases. Some classes might need to inherit from other classes, or parts of the
skeleton implementation might not be suitable for the subclass. In those situations,

Personal Copy for Andrei Arabolea, [email protected] 59


3 Classes and Interfaces

a class would be better off implementing the desired interface directly, skipping the
abstract class.

Think Interfaces First, Abstract Classes Second


Interfaces are for definitions; abstract classes are for partial implementations. Always
define your concepts with interfaces and use abstract classes to provide a skeleton
implementation that is common and useful for concrete subclasses to inherit and
refine.

Looking at the processor interface introduced earlier, a common use for the pre_pro-
cess method would be to check for authorizations before allowing the process to con-
tinue. That implementation is a partial implementation that can be delivered through
the authorizing_processor abstract class shown in Listing 3.7.

CLASS authorizing_processor DEFINITION


PUBLIC
ABSTRACT
CREATE PROTECTED.

PUBLIC SECTION.

INTERFACES:
processor ABSTRACT METHODS process.

PROTECTED SECTION.

METHODS is_authorized
ABSTRACT
RETURNING VALUE(result) type abap_bool.

ENDCLASS.

CLASS authorizing_processor IMPLEMENTATION.

METHOD processor~pre_process.
IF NOT is_authorized( ).
context->abort( ).
ENDIF.
ENDMETHOD.

ENDCLASS.

Listing 3.7 The authorizing_processor Abstract Class Implementing a Processor That Checks
for Authorization before Processing

60 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.1 Object Orientation

The authorizing_processor abstract class implements the processor optional method


processor~pre_process to call its is_authorized method, which is left abstract. Sub-
classes need to implement is_authorized and the main processor~process method.
Note how the processor~pre_process implementation uses the context~abort method
(not shown), which presumably stops whatever infrastructure is currently executing
the process before calling the processor~process method.
One general rule for abstract classes, apart from marking the class ABSTRACT, is that you
must have a protected constructor, using CREATE PROTECTED and placing the constructor
in the PROTECTED SECTION if you have an explicit constructor. (However, for a global class,
you should still place the constructor definition in the PUBLIC SECTION; more on this
topic in Section 3.3.1.) A constructor in a subclass also needs to call the abstract class
constructor. Similarly, any newly introduced abstract methods should be protected.
Protected members are visible in the class and any of its subclasses. Since an abstract
class cannot be instantiated and serves only as a superclass, making these elements
protected explicitly acknowledges the role the abstract class plays in a class hierarchy.

The Template Method Design Pattern


The authorizing_processor class is an example of the template method design pat-
tern (Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley,
1994).
The template method design pattern can define the skeleton of an algorithm in a
superclass while delegating concrete steps to its subclasses to implement. The autho-
rizing_processor class defines the steps that check for authorization and abort the
process if the authorization fails, leaving the concrete authorization check to sub-
classes.

Utility Classes
As mentioned earlier, a class is meant to serve as a blueprint to create objects that share
a common behavior and state. A static class, where all its members are static, is not
meant to be instantiated into an object. This kind of class is not conceptually a class in
the object-oriented sense but rather serves as a namespace to other constructs. In other
words, a static class is much more like a function group than a class.
Static classes are only appropriate as utility classes, which are stateless classes that pro-
vide basic functions and do not rely on any underlying resources or other instance-
level dependencies. Their use looks like built-in ABAP statements or functions. Mock-
ing or replacing their behavior when testing should make no sense to the application
logic that uses them.
Listing 3.8 shows an example of a utility class.

Personal Copy for Andrei Arabolea, [email protected] 61


3 Classes and Interfaces

CLASS money_utils DEFINITION


PUBLIC
ABSTRACT
FINAL.

PUBLIC SECTION.

CLASS-METHODS add
IMPORTING
money1 TYPE money
money2 TYPE money
RETURNING VALUE(result) TYPE money.

CLASS-METHODS add_all
IMPORTING
money_list TYPE money_list
RETURNING VALUE(result) TYPE money.

...

ENDCLASS.

CLASS money_utils IMPLEMENTATION.

...

ENDCLASS.

Listing 3.8 The money_utils Class Providing Common Utility Functions for money
and money_list

The money_utils class in Listing 3.8 provides common functions for working with the
money structure and the money_list internal table. These functions are basic, and you
should not expect different implementations than those provided. Even when testing
application code that uses these classes, there’s no reason to mock them.
Their usage looks like an ABAP built-in function, as shown in Listing 3.9.

DATA sub_total TYPE money.


DATA tax_total TYPE money.

...

DATA(total) = money_utils=>add(
money1 = sub_total
money2 = tax_total ).

Listing 3.9 Calling the money_utils=>add Function

62 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.1 Object Orientation

A utility class should be cohesive and provide a range of functions that deal with the
same concept, like money. A common anti-pattern is for an application or project to use
a single (or just a few) common utils classes where all utility functions are dumped,
even if these functions do not relate to the same concept.

Use Utility Classes for Basic Functions


Utility classes are meant to implement basic utility functions. These classes are not
conceptually classes. Instead, these classes should be basic and deal with a single con-
cept, like money, strings, or dates.
Utility classes provide a single, stable, and static implementation that is not reasonably
expected to change and does not really need to be mocked or replaced when testing
application code that uses them.

To create utility classes, we recommend you follow these rules:


쐍 Instances of utility classes make no sense, and therefore, you shouldn’t allow instan-
tiation. Prevent the instantiation of utility classes by marking the class ABSTRACT and
not providing any constructors, which will also make the implicitly created con-
structor unusable. Note that a utility class is not conceptually the same as an
abstract class. Marking the class ABSTRACT is just the way you disallow instantiation.
쐍 Inheriting from a utility class doesn’t make sense. Don’t allow inheritance by mark-
ing its definition FINAL and don’t create any PROTECTED members.
쐍 Utility classes should not be subclasses to other classes. Don’t use INHERITING FROM in
their definition. These classes should not implement any interfaces either.
쐍 Utility classes must only contain static methods. Use CLASS-METHODS as opposed to
METHODS.
쐍 Utility classes should not contain any state: no CLASS-DATA nor DATA. They can con-
tain CONSTANTS related to their topic.

Value Objects
A value object represents an object that has value semantics, where equality of two
objects is achieved when the objects contain the same visible state. Two date range
objects representing the range from “January 1st, 2020” to “December 31st, 2020” are
equal and interchangeable. Two sales orders for the same customer, with the same
products and quantities, are not equal or interchangeable and each represents a differ-
ent sales order. The date range is a value object; the sales order is an entity. Value
objects are defined by their values; entities, by their identities.
Our money structure from earlier is a kind of value object since it follows value seman-
tics. Commonly, in ABAP, value objects are represented as structures or internal tables

Personal Copy for Andrei Arabolea, [email protected] 63


3 Classes and Interfaces

in the ABAP Data Dictionary, perhaps supplemented by a utility class that provides
basic operations, like our money_utils class shown earlier in Listing 3.8.
Another characteristic of value objects, one that is not achievable with an ABAP struc-
ture, is that they should be immutable. Any operations with a value object that would
otherwise change their internal state should return a new value object with the desired
new state instead. The utility class money_utils does that for the money structure, but to
go a step further, you could have a value object class that enforces immutability in the
class itself, like the money_object class shown in Listing 3.10.

CLASS money_object DEFINITION


PUBLIC
FINAL
CREATE PUBLIC.

PUBLIC SECTION.

DATA:
amount TYPE decfloat34 READ-ONLY,
currency TYPE currency READ-ONLY.

METHODS constructor
IMPORTING
amount TYPE decfloat34
currency TYPE currency.

METHODS add
IMPORTING
other TYPE REF TO money_object
RETURNING VALUE(result) TYPE REF TO money_object.

...

ENDCLASS.

CLASS money_object IMPLEMENTATION.

METHOD constructor.

me->amount = amount.
me->currency = currency.

ENDMETHOD.

64 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.1 Object Orientation

METHOD add.

validate_currency( other->currency ).
result = NEW #(
amount = me->amount + other->amount
currency = me->currency ).

ENDMETHOD.

...

ENDCLASS.

Listing 3.10 The money_object Class: A Value Object Representing a Monetary Value

A Word on Naming
Contrary to the recommendation found in Chapter 5, Section 5.1.6, money_object uses
the “noise word” object as a suffix. Value objects in this chapter use that suffix to dif-
ferentiate from their structure counterpart, money in this case.
One could argue that object is not really a noise word for value objects, but an indicator
of the kind of pattern being used, much like the word factory.

A value object class should be FINAL and must have all its public data attributes marked
as READ-ONLY. You can use a constructor or a static factory method to create an instance
of the value object, which should take as parameters all the necessary data to fill in
those public attributes.
Even though the attributes are marked as READ-ONLY, you can still change these attri-
butes from within the class after instantiation. To maintain its immutability contract,
then, the class should not change those attributes after instantiation. As an example,
the add method in money_object returns a new money_object instance and does not
change the current instance attributes.

Inheritance
Implementation inheritance in ABAP Objects is realized when a class inherits from
another class. Classes can only inherit from a single parent class. The inherited-from
class is the base class, parent class, or superclass, and the inheriting class is the child or
subclass. The whole chain of inheritance, based on the top superclass and including all
its derived subclasses (and their subclasses and so on), defines an inheritance tree. A
schematic inheritance tree represented in a Unified Modeling Language (UML) dia-
gram is shown in Figure 3.1.

Personal Copy for Andrei Arabolea, [email protected] 65


3 Classes and Interfaces

B C

D E

Figure 3.1 An Inheritance Tree

In the inheritance tree shown in Figure 3.1, which has a single topmost class (A), each
class inherits from a single parent class. A single branch of the tree, like the branch
F -> D -> C -> A, defines an inheritance chain.
The root class of all inheritance trees in ABAP is the built-in object class. This class can
be used to type generic object references but is otherwise empty and does not contain
any members. The child class has access to all public and protected members of its
direct superclass and any non-redefined public and protected members of the super-
class’s superclass and so on, until the topmost class of its chain of inheritance. The child
class exposes the public members of its inheritance chain as its own and can also rede-
fine non-final methods with more specialized behavior. Let’s review the inheritance
example shown in Listing 3.11.

CLASS device DEFINITION


PUBLIC.

PUBLIC SECTION.

METHODS:
is_on
RETURNING VALUE(result) TYPE abap_bool,
is_off
RETURNING VALUE(result) TYPE abap_bool,
toggle.

PRIVATE SECTION.

DATA on TYPE abap_bool VALUE abap_false.

ENDCLASS.

CLASS device IMPLEMENTATION.

66 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.1 Object Orientation

METHOD is_on.
result = on.
ENDMETHOD.

METHOD is_off.
result = xsdbool( NOT is_on( ) ).
ENDMETHOD.

METHOD toggle.
on = xsdbool( NOT is_on( ) ).
ENDMETHOD.

ENDCLASS.

CLASS fan DEFINITION


PUBLIC
INHERITING FROM device.

PUBLIC SECTION.

CONSTANTS:
max_intensity TYPE int4 VALUE 10.

METHODS:
increase_intensity,
decrease_intensity,
read_intensity
RETURNING VALUE(result) TYPE int4.

PRIVATE SECTION.

METHODS:
constrain_intensity.

DATA intensity TYPE int4 VALUE 0.

ENDCLASS.

CLASS fan IMPLEMENTATION.

METHOD increase_intensity.
intensity += 1.
constrain_intensity( ).
ENDMETHOD.

Personal Copy for Andrei Arabolea, [email protected] 67


3 Classes and Interfaces

METHOD decrease_intensity.
intensity -= 1.
constrain_intensity( ).
ENDMETHOD.

METHOD read_intensity.
result = intensity.
ENDMETHOD.

METHOD constrain_intensity.
IF intensity < 0.
intensity = 0.
ELSEIF intensity > max_intensity.
intensity = max_intensity.
ENDIF.
ENDMETHOD.

ENDCLASS.

Listing 3.11 A Simple Class Hierarchy: fan Inheriting from device

This sample hierarchy shows a device superclass with a fan subclass. A device rep-
resents a machine that can be turned on or off. A fan is a device that not only can be
turned on or off but can also have its intensity adjusted. A diagram of these two classes,
with a selection of their public members, is shown in Figure 3.2.

device

is_on(): bool
is_off(): bool
toggle()

fan

increase_intensity()
decrease_intensity()
read_intensity(): int4

Figure 3.2 UML Diagram of Device/Fan Inheritance

68 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.1 Object Orientation

The child class can be referred to by a variable typed to any of the parent classes in its
inheritance chain. Methods called on that reference will be resolved and dispatched to
the right implementation based on the runtime instance of the class. This approach,
often referred to as dynamic dispatch, is one facet of the polymorphism implemented
in ABAP Objects, the other being the use of interface references. In fact, as Listing 3.12
shows, using a superclass variable to refer to a subclass is just like using a reference to
refer to an interface that the class implements (as earlier in Listing 3.6).

DATA my_device TYPE REF TO device.


my_device = NEW fan( ).
my_device->toggle( ).

Listing 3.12 fan Referenced by a Variable Typed to Its Superclass

Since child classes can substitute for parent classes, they must adhere to the full behav-
ioral contract of their superclass, so as not to break expectations of behavior in any cli-
ent code. This rule is the Liskov substitution principle, the L in SOLID. That’s why
inheritance relationships are usually referred to as is-a relationships. A fan is-a device
and should behave like a device as well.

Inheritance Models an Is-A Relationship


Inheritance is only appropriate if you’re modeling an is-a relationship. The child class
must be able to substitute for its superclass and must maintain its behavioral contract
and not break any of its invariants. Its identity is intrinsically connected with the iden-
tity of its parent classes.

Following the Liskov substitution principle is, however, easier said than done, since
you also need to consider the usages of the superclass and the expectations that client
code has of it. A square is not a rectangle, if the client code expects the ability to change
the rectangle’s width and height independently of each other to maintain a certain
area, for example. Since that expectation is predicated on the rectangle superclass, the
client code would break if given a square instance.

Avoid Deep Inheritance Hierarchies


Not only should you avoid breaking the parent’s class behavior, reasoning about an
inheritance relationship might require knowledge of an entire inheritance chain, from
the most derived up to the topmost class in the hierarchy.
A deep inheritance hierarchy only compounds the problem and is harder to design and
maintain in the long run.

Inheritance is an immensely powerful tool, but overuse can also lead to classes that are
fragile and prone to error. Changes to a superclass have ripple effects and impact the
behavior of derived classes in its entire inheritance tree.

Personal Copy for Andrei Arabolea, [email protected] 69


3 Classes and Interfaces

Design for Inheritance or Disallow It


Be careful when designing a class for inheritance, especially across package or project
boundaries. Document all the provided behavior of the superclass, its extension points,
and the expected responsibilities of subclasses. Mark methods that should not be rede-
fined as FINAL.
If you’re not designing a class for inheritance, disallow inheritance altogether by mark-
ing the class itself as FINAL.

One common inheritance idiom is to have an abstract superclass that provides a skele-
ton implementation of an interface, as already explored in the previous section about
abstract classes. Some design patterns work with inheritance, like template method
and factory method.
When dealing with legacy code, though, there are often classes that don’t implement
interfaces for which you want to be able to substitute or adjust in testing code. A testing
subclass or a mock can be used in these cases to redefine the undesired behavior for
testing, which is only possible if the class is not marked FINAL.

Composition
Composition is the general term used when a class refers to an instance of another class
as one of its attributes. The first class is composed of the second. Often referred to as a
has-a relationship, composition is a fundamental way to model interactions and sepa-
rate responsibilities in object-oriented design. A traffic light is composed of three lights:
red, yellow, and green. A car has four wheels and an engine. An employee has an
address and a telephone number.
Picking up where the device design shown in Figure 3.2 left off, let’s say we have a new
requirement to protect devices from overheating. We’ll need to install thermal sensors
on our devices and have each device automatically turn off when its temperature
reaches a critical threshold. The sensors implement the thermal_sensor interface
shown in Listing 3.13, with its single critical_temperature_reached event, which is
raised when a defined temperature threshold has been reached.

INTERFACE thermal_sensor
PUBLIC.

EVENTS:
critical_temperature_reached.

ENDINTERFACE.

Listing 3.13 The thermal_sensor Interface

70 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.1 Object Orientation

Then, to protect a device, we’ll wrap it in a thermal switch, which turns the device off if
the critical temperature event is raised. A thermal switch presents itself as a device but
is not a device. Inheritance from device would be the wrong way to implement it. Fol-
lowing our own advice to declare the abstract concepts of our design with interfaces, as
described earlier in Section 3.1.1, we’ll refactor the device class and extract a more
abstract interface for switchable gadgets, which can be used for our new thermal
switch. The refactored elements are shown in Listing 3.14.

INTERFACE switchable
PUBLIC.

METHODS:
is_on
RETURNING VALUE(result) TYPE abap_bool,
is_off
RETURNING VALUE(result) TYPE abap_bool,
toggle.

ENDINTERFACE.

...

CLASS device DEFINITION


PUBLIC.

PUBLIC SECTION.

INTERFACES: switchable.

ALIASES:
is_on FOR switchable~is_on,
is_off FOR switchable~is_off,
toggle FOR switchable~toggle.

PRIVATE SECTION.

DATA on TYPE abap_bool VALUE abap_false.

ENDCLASS.

Listing 3.14 The device Class and the Extracted switchable Interface

To contain the impact of the change to the device class, the switchable interface meth-
ods are aliased to the former device method names.

Personal Copy for Andrei Arabolea, [email protected] 71


3 Classes and Interfaces

The “Extract Interface” Refactoring


Refactoring is the process of changing code to improve its overall design without
changing its behavior.
”Extract interface” is one of the original refactorings presented in the book Refactoring:
Improving the Design of Existing Code (Addison-Wesley, 1999). This technique consists
of extracting a suitable interface out of an existing class to provide a better abstraction
for its clients.

Armed with the switchable and the thermal_sensor interfaces, we can finally tackle the
thermal_switch class, shown in Listing 3.15.

CLASS thermal_switch DEFINITION


FINAL
PUBLIC.

PUBLIC SECTION.

INTERFACES:
switchable.

METHODS:
constructor
IMPORTING
managed_device TYPE REF TO device
sensor TYPE REF TO thermal_sensor,
trip
FOR EVENT critical_temperature_reached OF thermal_sensor,
reset.

PRIVATE SECTION.

DATA:
managed_device TYPE REF TO device,
is_tripped TYPE abap_bool VALUE abap_false.

ENDCLASS.

CLASS thermal_switch IMPLEMENTATION.

METHOD constructor.
me->managed_device = managed_device.
SET HANDLER trip FOR sensor.
ENDMETHOD.

72 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.1 Object Orientation

METHOD switchable~is_on.
result = managed_device->is_on( ).
ENDMETHOD.

METHOD switchable~is_off.
result = managed_device->is_off( ).
ENDMETHOD.

METHOD switchable~toggle.
CHECK is_tripped = abap_false.
managed_device->toggle( ).
ENDMETHOD.

METHOD trip.
is_tripped = abap_true.
IF managed_device->is_on( ).
managed_device->toggle( ).
ENDIF.
ENDMETHOD.

METHOD reset.
is_tripped = abap_false.
ENDMETHOD.

ENDCLASS.

Listing 3.15 The thermal_switch Class Wrapping a device Instance

The thermal_switch class is composed of a device instance and a thermal_sensor


instance. The class receives both instances in its constructor and stores the device
instance in its managed_device attribute. The thermal_sensor instance is stored implic-
itly since the class registers its trip method as a handler to the thermal_sensor~criti-
cal_temperature_reached event. The thermal_switch class serves as a proxy to the
managed device instance, forwarding the corresponding switchable interface method
calls, while turning off and protecting the device from turning on if the thermal switch
has been tripped by a temperature event. The reset method can be called to reset the
switch to its original state.
The current design diagram is shown in Figure 3.3.

Prefer Composition to Inheritance


“Prefer composition to inheritance” is a common trope in object-oriented circles, and
for a good reason.

Personal Copy for Andrei Arabolea, [email protected] 73


3 Classes and Interfaces

Composition is in general a better strategy for code reuse and object design than inher-
itance. With composition, you’ll end up with many small, independent objects, each of
which serves one specific purpose. Thus, changes to an object are localized. These
objects can be combined into more complex objects by simple forwarding and delega-
tion patterns.

«interface»
switchable

is_on(): bool
is_off(): bool
toggle()

«interface»
thermal_switch
managed_device thermal_sensor
device
«handler» trip()
«event»
reset()
critical_temperature_reached()

trip() handles
thermal_sensor~crical_temperature_reached()

Figure 3.3 UML Diagram of the thermal_switch Class Composition

When the composing object wants to present to clients an abstraction consistent with
the composed object, both should implement a common interface. The composing
object usually wraps and forwards those interface calls to the composed object, as in
our thermal_switch example.
The composing object can also present a completely different interface than the com-
posed object, and just use it as part of its internal implementation. This encapsulation
is an important level of protection against changes to the composed object that could
otherwise have a ripple effect and directly impact the composing object’s clients if
inheritance were used.
Many classic structural design patterns are built on top of composition, for instance,
the proxy, adapter, bridge, decorator, and composite design patterns.

74 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.1 Object Orientation

3.1.3 State
Classes can maintain state in the form of attributes. For a class to be cohesive and fol-
low the single-responsibility principle (the S in SOLID), the state and methods of a class
should work together to achieve a common goal.
The message_container class in Listing 3.16 shows the definition of a class that main-
tains a list of messages (the state) and provides operations that work on that state (the
methods). A class that maintains state is often referred to as a stateful class.

CLASS message_container DEFINITION


PUBLIC
FINAL
CREATE PUBLIC.

PUBLIC SECTION.

METHODS:
add_message
IMPORTING message TYPE message.

PRIVATE SECTION.

DATA messages TYPE message_table.

ENDCLASS.

Listing 3.16 A Stateful Message Container

Classes that maintain and manipulate state are intrinsically harder to manage. These
classes can have many failure modes that can make the state inconsistent and break
the class’s invariants. A client of the message container shown in Listing 3.16 must keep
the container in sync with whatever global state it is tracking; adding, removing, and
clearing messages at the right moments. The thermal_switch class we saw earlier in List-
ing 3.15 must carefully manage its internal state (the attribute is_tripped) with the state
of its wrapped device. Making a class immutable, where its state is fixed and cannot
change, alleviates this problem.
Classes that don’t have any internal state, on the other hand, usually provide some
kind of processing where its methods work with its inputs and produce an output with-
out any side effects. Listing 3.17 shows the definition section of a stateless class for con-
verting an XML payload to an internal ABAP data type. This class doesn’t keep any state
to provide its service.

Personal Copy for Andrei Arabolea, [email protected] 75


3 Classes and Interfaces

CLASS xml_converter DEFINITION


PUBLIC
FINAL
CREATE PUBLIC.

PUBLIC SECTION.

METHODS convert
IMPORTING
file_content TYPE xstring
RETURNING
VALUE(result) TYPE some_inbound_message.

ENDCLASS.

Listing 3.17 The Stateless xml_converter Class

Don’t Mix Stateful and Stateless Paradigms in the Same Class


Maintaining a stateful class is hard enough. Mixing stateful and stateless methods in
the same class will make the class harder to understand and less cohesive.
Make sure your class has a single focus and all methods and attributes work together
to achieve this goal.

3.2 Scope and Visibility


Scope and visibility are facets of the encapsulation functionality present in ABAP
Objects. In this section, we’ll discuss clean ABAP recommendations for class scope and
class member visibility.

3.2.1 Global and Local Scope


The ABAP Data Dictionary defines a global scope for all its classes, interfaces, and other
elements. All global classes and interfaces in an ABAP deployment are essentially part
of this global scope and share the same namespace, where two elements, regardless of
their package assignment, cannot have the same name.
This global namespace, along with the fact that classes and interface names are limited
to 30 characters, is the reason why prefixes are so widespread in the ABAP world.
Therefore, global class and interface names should be carefully chosen, and prefix rules
should be discussed, agreed to, and followed to avoid clashes, which we’ll describe in
more detail in Chapter 5, Section 5.3.

76 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.2 Scope and Visibility

A local class is a class that’s defined in the local area of a global class. This class is only
accessible by its enclosing global class and is hidden from outside code, including the
global subclasses of the global class. This class is effectively private to the global class.
In ABAP Development Tools (ADT), local classes used in the private definition section
of the global class must be defined under the Class-relevant Local Types tab of the
global class, while their implementation must be maintained under the Local Types tab.
Local classes that don’t need to be referenced from the global class private section can
be fully defined and implemented under the Local Types tab. A special type of local class
is a local test class, which is defined and implemented under the Test Classes tab. These
tabs can be found at the bottom of the global class editor panel, as shown in Figure 3.4.

Figure 3.4 Global Class Editor Panel Showing Available Tabs

Use Global Classes by Default


Global classes should be preferred over local classes because global classes allow your
design to be better decoupled, reusable, discoverable, and testable. Local classes are
only suitable for the specific, private implementation details of your global classes.

Even though local classes are completely private to a global class, you should not use
local classes to restrict access to your classes. The accessibility of your global classes to
outside code should be controlled by using an encapsulated package and proper pack-
age interfaces for exposed elements (see Chapter 13).

Personal Copy for Andrei Arabolea, [email protected] 77


3 Classes and Interfaces

Local classes should not be used extensively; they should not be used to create a com-
plete subsystem within the local area of a global class, for example.
A suitable use of a local class is to implement an interface needed by the global class
that only makes sense for that specific global class. Local classes can also be made
friends with the global class if they need to access private data of the global class, which
we’ll describe in more detail in Section 3.2.2.
For example, using the iterator pattern, let’s say we have a value object representing a
message (message_object, not shown) and the global interfaces shown in Listing 3.18.

INTERFACE message_iterator
PUBLIC.

METHODS:
has_next
RETURNING VALUE(result) TYPE abap_bool,
get_next
RETURNING VALUE(result) TYPE ref to message_object
RAISING no_element_exception.

ENDINTERFACE.

INTERFACE message_collection
PUBLIC.

METHODS:
add
IMPORTING message TYPE ref to message_object,
get_iterator
RETURNING VALUE(result) TYPE REF TO message_iterator.

ENDINTERFACE.

Listing 3.18 A Message Collection and Its Iterator

The message_collection interface depends on the message_iterator interface to allow


iteration over its messages. Any class implementing message_collection will both need
to decide how to store the individual message objects that are passed via the add
method and need to provide a way to iterate over its stored message objects with an
implementation of the message_iterator interface. This implementation will be so spe-
cific to that message collection that it makes sense to create the message iterator
implementation as a local class.
The message_list global class shown in Listing 3.19 implements the message_collection
interface.

78 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.2 Scope and Visibility

CLASS message_list DEFINITION


FINAL
PUBLIC.

PUBLIC SECTION.

INTERFACES: message_collection.

PRIVATE SECTION.

DATA:
messages TYPE message_object_table.

ENDCLASS.

CLASS message_list IMPLEMENTATION.

METHOD message_collection~add.
APPEND message TO messages.
ENDMETHOD.

METHOD message_collection~get_iterator.
result = NEW message_object_table_iterator( messages ).
ENDMETHOD.

ENDCLASS.

Listing 3.19 message_list Implementing message_collection and Using a Local Type and a
Local Class

The private section of the message_list class definition uses a local type that’s declared
under the Class-relevant Local Types tab, the message_object_table local type, shown in
Listing 3.20.

TYPES:
message_object_table
TYPE STANDARD TABLE OF REF TO message_object.

Listing 3.20 The message_object_table Local Type

The message_iterator implementation used in the get_iterator method is the local


class message_object_table_iterator that is defined and implemented under the Local
Types tab of the message_list class, as shown in Listing 3.21.

Personal Copy for Andrei Arabolea, [email protected] 79


3 Classes and Interfaces

CLASS message_object_table_iterator DEFINITION


FINAL.

PUBLIC SECTION.

INTERFACES: message_iterator.

METHODS:
constructor
IMPORTING messages TYPE message_object_table.

PRIVATE SECTION.

DATA:
messages TYPE message_object_table,
current_index TYPE int4.

ENDCLASS.

CLASS message_object_table_iterator IMPLEMENTATION.

METHOD constructor.
me->messages = messages.
current_index = 0.
ENDMETHOD.

METHOD message_iterator~has_next.
DATA(next_index) = current_index + 1.
result = xsdbool( line_exists( messages[ next_index ] ) ).
ENDMETHOD.

METHOD message_iterator~get_next.
TRY.
DATA(next_index) = current_index + 1.
result = messages[ next_index ].
current_index = next_index.
CATCH cx_sy_itab_line_not_found.
RAISE EXCEPTION NEW no_element_exception( ).
ENDTRY.
ENDMETHOD.

ENDCLASS.

Listing 3.21 The Local Class That Implements the message_iterator Interface

80 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.2 Scope and Visibility

When get_iterator is called on the message_list, a new message_object_table_iterator


instance is returned with a snapshot of the messages contained in the global class. Cli-
ent code will always deal with that instance via a reference to the message_iterator
interface. The implementation details of message_list, including the message_object_
table type and the message_object_table_iterator class, are encapsulated and hidden
from the outside world as local elements.

3.2.2 Visibility
Well-designed classes tend to provide a public and cohesive set of elements that make
sense for its abstraction and hide all the implementation details that do not contribute
to said abstraction.
The message_list class shown earlier in Listing 3.19 implements the message_collection
interface, which becomes part of its own public interface. The way it implements that
interface is hidden from clients in its private section. This class even makes use of local
elements (a type and a class) that are completely hidden from the outside world.
The authorizing_processor class shown earlier in Listing 3.7 is an abstract base class
that declares a protected abstract method that is meant to be redefined by child classes.
As part of the internal policy of the class, that method should not be public, but because
it’s meant for subclasses, it should also not be private.
Private and local members are only visible in the class itself. Protected members are
visible in the class and any of its subclasses, and public members are visible by any cli-
ent that has access to the class.

Class Members Visibility Should be Minimized


The visibility of members in a class should be minimized, which not only makes the
class’s public interface leaner and easier to consume but also protects client code from
the impact of changes in the implementation details of the class.
Private visibility should be considered by default. Choose protected visibility only when
needed by subclasses. Public visibility should be used only if the member is important
for consumers and part of the abstraction that the class represents.
As much as possible, define the public face of classes in interfaces (Section 3.1.1). Pro-
vide aliases to interface methods as appropriate.

A class can also be friends with another class, which will make its private and protected
members visible to that other class. Friendship like this breaks the encapsulation of the
class and should only be used in rare circumstances, for example, involving a class and
its local classes or for local unit test classes. We’ll see an example in Chapter 12, Section
12.3. The accessibility of global classes can be controlled with package interfaces. For
more details, see Chapter 13.

Personal Copy for Andrei Arabolea, [email protected] 81


3 Classes and Interfaces

Data attributes should also be private by default. If you need access to those attributes
from the public or even the protected interfaces of the class, you should provide acces-
sor methods for the data attribute. These accessors usually take the form of a get and a
set method, or a variation thereof. The data attribute and its accessor methods are col-
lectively referred to as a property. The accessor methods effectively become an inter-
face to the attribute and offer some protection in the way the class implements the
property access.
The device and fan classes described earlier in Section 3.1.2 (during our discussion of
inheritance) declare their attributes as private and provide accessors to read and
change those attributes.
An immutable attribute is assigned a value when the class is instantiated, and this
value never changes. A class that only has immutable attributes is an immutable class.
An example of an immutable class is the value object money_object, shown in Section
3.1.2 (during our discussion of value objects).

Consider Making Immutable Attributes Read-Only


We recommend implementing immutable attributes as public, read-only attributes, as
in the money_object class shown earlier in Listing 3.10. Public read-only attributes are
easier to consume and more readable than the corresponding accessor method.
You can still use an accessor, though, if you prefer that consumers be shielded from the
way that the value is stored in your class, which also allows some flexibility to change
the way it’s stored without breaking client code. Use an accessor in an interface to cre-
ate an abstraction to the value being stored.

The public attributes section of the money_object class is shown in Listing 3.22.

PUBLIC SECTION.

DATA:
amount TYPE decfloat34 READ-ONLY,
currency TYPE currency READ-ONLY.

Listing 3.22 Public Read-Only Attributes

Only Use Read-Only Attributes in Certain Situations


Read-only attributes are only allowed in the PUBLIC SECTION of a class and can still be
changed by code within the class itself, its friends, and its subclasses.
This behavior contradicts the more widespread notion of read-only attributes found in
other languages, where the expectation is for a write-once-modify-never behavior.
This difference can result in nasty surprises and should be avoided.
To enforce a write-once-modify-never behavior, you must follow these rules:

82 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.3 Constructors

1. Do not change read-only attributes values from within the class. Only assign these
attributes once on instantiation.
2. Do not allow the class to be inherited from by making it FINAL.
3. Do not declare any friend classes to the class.

The money_object class constructor shown in Listing 3.23 assigns the received values to
its public read-only attributes. The class never changes its values after instantiation.

METHOD constructor.

me->amount = amount.
me->currency = currency.

ENDMETHOD.

Listing 3.23 The money_object Constructor

Public read-only attributes make sense at the class level, not at the interface level. As
such, these attributes should be relegated to value classes, like value objects and excep-
tion classes.

3.3 Constructors
To create instances of your classes, ultimately, a constructor must be invoked. In this
section, you’ll learn how constructors work, how to use them, and some patterns and
idioms of object construction.

3.3.1 Scope and Visibility


In a class declaration statement, you can decide on the visibility of the instance con-
structor by annotating the class with the following options:
쐍 CREATE PUBLIC
The constructor is public and can be called from any code that has access to the class.
쐍 CREATE PROTECTED
The constructor is protected and can only be called by the class, its subclasses, or its
friends.
쐍 CREATE PRIVATE
The constructor is private and can only be called by the class itself or its friends.

When you omit the constructor visibility option, the default is CREATE PUBLIC.

Personal Copy for Andrei Arabolea, [email protected] 83


3 Classes and Interfaces

If you don’t provide a constructor in your class, a default constructor is created, which
is callable as if declared in the visibility section defined by the CREATE option. This
default constructor can be thought of as a constructor with an empty body. If your class
is a subclass, the default constructor will still call the superclass constructor.
Even though that visibility option determines how the constructor can be called, the
constructor itself can be declared in a less restrictive section. For example, a class
marked with CREATE PROTECTED can have its constructor declared in its PROTECTED SECTION
as well as in its PUBLIC SECTION.

Declare Global Classes Constructors in the PUBLIC SECTION


Even if your global class is marked with CREATE PROTECTED or CREATE PRIVATE, you
should still declare your constructor in the PUBLIC SECTION of the class.
This surprising recommendation is only required because of a technical limitation of
the ABAP compiler, which stores the components of global classes internally according
to their visibility section. Depending on the way the class is used, the ABAP compiler
only considers parts of the class, and if it can’t see a certain constructor, it will issue
syntax errors when it shouldn’t.

Based on the processor interface shown in Listing 3.3, you can have an abstract base
class that chains processors together and adds its chained processor to the execution
context (not shown) in its post_process method implementation, as shown in Listing
3.24.

CLASS chaining_processor DEFINITION


PUBLIC
ABSTRACT
CREATE PROTECTED.

PUBLIC SECTION.

INTERFACES:
processor ABSTRACT METHODS process.

METHODS constructor
IMPORTING next TYPE REF TO processor.

PRIVATE SECTION.

DATA:
next TYPE REF TO processor.

ENDCLASS.

84 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.3 Constructors

CLASS chaining_processor IMPLEMENTATION.

METHOD constructor.
me->next = next.
ENDMETHOD.

METHOD processor~post_process.
IF next IS BOUND.
context->add_processor( next ).
ENDIF.
ENDMETHOD.

ENDCLASS.

Listing 3.24 The chaining_processor Base Class

Note that the chaining_processor class is marked as CREATE PROTECTED, which means that
its constructor can only be called by itself and its subclasses. However, the constructor
is still declared in the PUBLIC SECTION of the class, which is only a requirement because
chaining_processor is a global class.

Declare Local Classes Constructors in the Correct Section


For local classes, you should declare the constructor in the corresponding section that
the class definition is annotated with in the following way:
쐍 CREATE PUBLIC or no declaration goes in the PUBLIC SECTION.
쐍 CREATE PROTECTED goes in the PROTECTED SECTION.
쐍 CREATE PRIVATE goes in the PRIVATE SECTION.

3.3.2 Dependency Injection


Most classes in an application depend on other classes that help to carry out their
responsibilities. To allow for a better separation of concerns and better isolation, you
should decouple classes using interfaces and dependency injection.
One form of dependency injection is when a class declares a constructor that takes the
necessary dependencies as parameters. These instances should be typed to interfaces
and the class should not internally create any concrete instances to its needed
resources.
Later, when you need to create concrete instances of your classes, you can use factories
that wire together the different instances and fulfill the necessary dependencies for
each component.

Personal Copy for Andrei Arabolea, [email protected] 85


3 Classes and Interfaces

Use Dependency Injection to Decouple from Underlying Resources


Declare your constructor to take external dependencies as parameters.
If an obvious concrete implementation for a dependency exists, you can make that
parameter optional and use the default if the parameter is not provided. However, you
should still use a reference to the interface that the resource implements or a suitable
base class. Your class should work with the most abstract concept that represents your
dependency.

The thermal_switch constructor, shown earlier in Listing 3.15, is an example of a con-


structor that takes the necessary dependencies via its parameters, specifically a device
and a thermal_sensor. The class is not dependent on any device subclass or any concrete
thermal_sensor implementation. You could still improve the design by taking a refer-
ence to a switchable (which is an interface) instead of a device.

3.3.3 Static Creation Methods


ABAP does not support method overloading, where multiple methods have the same
name and differ only in their parameters. Therefore, you cannot declare multiple con-
structors for different construction requirements. Instead, you would declare a con-
structor with multiple optional parameters. However, multiple optional parameters
can make a method more complex and harder to work with. For more information on
this topic, refer to Chapter 4, Section 4.2.
Instead of, or along with, exposing a constructor to create instances of your class, you
can use static creation methods, which are static methods that take the necessary con-
structor parameters and return an instance of the class.
We’ll walk through using static creation methods with both classes and interfaces in
the following sections.

Static Creation Methods in Classes


To see the flexibility of static creation methods, look at the base class of the ABAP Run-
time Type Services (RTTS) hierarchy, the class cl_abap_typedescr. This class must be
able to return numerous different instances of subclasses from its static creation meth-
ods, depending on the type that you want it to provide information about. The method
cl_abap_typedescr=>describe_by_data will use the input parameter to return the right
runtime instance, as shown in Listing 3.25.

DATA(change) = VALUE money( amount = 2 ).


DATA(money_descr) =
cl_abap_typedescr=>describe_by_data( change ).
" money_descr is an instance of cl_abap_structdescr

86 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.3 Constructors

...

DATA(wallet) = VALUE money_list( ( change ) ).


DATA(wallet_descr) =
cl_abap_typedescr=>describe_by_data( wallet ).
" wallet_descr is an instance of cl_abap_tabledescr

Listing 3.25 The cl_abap_typedescr=>describe_by_data Static Creation Method

Consider Static Creation Methods


Static creation methods offer many advantages over constructors.
If you have a constructor with multiple optional parameters, multiple static creation
methods with descriptive names can make the available options clearer and more
understandable. You can make the constructor private or protected to encourage the
use of static creation methods.
Static creation methods also allow more flexibility since, unlike with a constructor,
you’re not required to always work with a newly created instance of a specific class.
For example, a static creation method can cache return values and return the same
instance for the same input parameters, which can be useful for immutable classes.
This kind of method can also return an instance of a subclass of the return type and can
be declared in a completely different class than the one from which it returns
instances.

The simple fact that static creation methods can have meaningful names can bring a lot
of clarity to client code if your class has multiple ways of being instantiated.

Static Creation Methods in Interfaces


A constructor is a special kind of method, one that cannot be declared in an interface. If
you need a way to generically create instances of classes implementing an interface,
you can declare a static creation method in the interface, which serves as an abstract
constructor of sorts.
Let’s say we need to register and dynamically instantiate processors implementing the
processor interface introduced earlier in Listing 3.3. The resulting interface will include
a static creation method, as shown in Listing 3.26.

INTERFACE processor
PUBLIC.

CLASS-METHODS create
DEFAULT FAIL
IMPORTING config TYPE REF TO config
RETURNING VALUE(result) type ref to processor.

Personal Copy for Andrei Arabolea, [email protected] 87


3 Classes and Interfaces

METHODS pre_process
DEFAULT IGNORE
IMPORTING context TYPE REF TO context.

METHODS process
IMPORTING context TYPE REF TO context.

METHODS post_process
DEFAULT IGNORE
IMPORTING context TYPE REF TO context.

ENDINTERFACE.

Listing 3.26 Interface Now with a Static Creation Method

Now, apart from implementing the method process, processors that should be dynam-
ically instantiated by our infrastructure code will need to implement the static create
method, which takes a config object (not shown) and returns a processor instance. Note
that the method was annotated with DEFAULT FAIL, since we don’t want to require that
all processors participate in this dynamic instantiation. However, the ones that want to
be dynamically instantiated need to implement create, or else they will fail at runtime.
Our infrastructure code needs a concrete class name, for which create will be called
dynamically. This name is stored in a database table that configures the processor that
will be used. Then, all the code needs to do is call create as declared in the interface and
an instance of a processor will be ready to use, as shown in Listing 3.27.

" class_name comes from a database table


DATA class_name TYPE seoclsname.

...

DATA my_processor TYPE REF TO processor.


" create the processor
CALL METHOD (class_name)=>processor~create
EXPORTING config = my_config
RECEIVING result = my_processor.
" use the newly created processor...

Listing 3.27 Usage of the processor~create Method

Note that this example is a specialized use of static methods that requires dynamic pro-
gramming. Dynamic programming can make your code harder to understand and
should only be used when there’s a tangible benefit to the architecture. In this example,
the code works generically with processor instances and should not know about con-
crete processor names, which are stored as data in a configuration table.

88 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.3 Constructors

3.3.4 Creational Patterns


Static creation methods are a type of factory method but do not follow the factory
method pattern presented in Design Patterns: Elements of Reusable Object-Oriented
Software (Addison-Wesley, 1994). The factory method pattern involves the definition of
a factory method for an underlying dependency in a superclass to be implemented or
redefined by subclasses. You can also leverage other creational patterns to abstract
away object construction. We’ll look at some of these design patterns next and provide
general guidance on when and how to use them.

Builder
First, let’s look at the builder pattern. If you have a complex object that requires many
different parameters to be created, splitting the construction of the object to a separate
class altogether might make sense. This separate class is the builder class. The builder
class’s sole responsibility is to collect all the necessary data and ultimately construct
the target object. The builder can split the collection of the necessary data into multiple
methods and can validate all the data before creating the target object, bringing more
clarity and correctness to the creation process. The client will create the builder, then
call the necessary methods to set the data for the target object, and ultimately call a
build method that returns the target object with all the necessary data.
For example, Listing 3.28 shows how creating a savings account for a customer might
be achieved with a builder.

DATA(my_builder) = account_builder=>create_savings_account(
currency = 'EUR' ).
my_builder->for_customer( my_customer ).
my_builder->with_address( my_customer_main_address ).
my_builder->with_initial_balance(
amount = 1000
from_account = my_customer_checking_account ).
DATA(my_customer_savings_account) = my_builder->build( ).

Listing 3.28 An Account Builder

This builder has a static creation method that decides which type of account to build
and the account’s currency (create_savings_account with the currency input parame-
ter). Then, by using its various methods, the client sets up the necessary data for the
account, the customer ID, customer address, and an initial balance. Finally, the build
method returns a savings account object configured with the data collected by the
builder.

Singleton
A singleton is an object for which only a single instance makes sense. All clients work
with the same instance. The singleton class controls its instantiation and ensures that

Personal Copy for Andrei Arabolea, [email protected] 89


3 Classes and Interfaces

every consumer is working with the same instance in the same state and with the same
data.

Only Use Singleton when Multiple Instances Don’t Make Sense


Do not use the singleton pattern out of habit or because of performance concerns. The
singleton design pattern, which might be the most overused pattern, can produce
unexpected side effects and needlessly complicates testing.
If no design reasons exist for a single object, then do not use the singleton design pat-
tern. When you do use this design pattern, use an interface or a creation method that
abstracts from the instantiation requirements of the singleton. In other words, try to
make the fact that your object is a singleton invisible to consumers.

A singleton should have a private constructor that’s only called from a static creation
method. This method will create the single instance, if not already created, and return
it. The creation of the single instance can also happen in a static constructor (class_
constructor), which is a special method that’s called before any element of the class is
used by any consumers. The single instance is stored as a static attribute. Check out the
example shown in Listing 3.29, which uses a static constructor.

CLASS singleton DEFINITION


PUBLIC
FINAL
CREATE PRIVATE.

PUBLIC SECTION.

CLASS-METHODS:
class_constructor,
get_instance RETURNING VALUE(result) TYPE REF TO singleton.

PRIVATE SECTION.

CLASS-DATA:
single_instance TYPE REF TO singleton.

ENDCLASS.

CLASS singleton IMPLEMENTATION.

METHOD class_constructor.
single_instance = NEW #( ).
ENDMETHOD.

90 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.3 Constructors

METHOD get_instance.
result = single_instance.
ENDMETHOD.

ENDCLASS.

Listing 3.29 Singleton Example Using a Static Constructor

Factory
Apart from static creation methods or the factory method pattern, you can declare that
a class or an interface is a factory for another class or interface, with instance methods
for the creation of the target object. In this way, your clients are decoupled from the
concrete classes of the constructed objects.
We could extend the processor design shown earlier in Listing 3.26 and introduce a fac-
tory interface for processors, as shown in Listing 3.30.

INTERFACE processor_factory
PUBLIC.

METHODS:
create
IMPORTING config TYPE REF TO config
RETURNING VALUE(result) TYPE REF TO processor.

ENDINTERFACE.

Listing 3.30 The processor_factory Interface

For client code living outside your package, factories like processor_factory and the
interface of their created objects, like processor, could be exposed through your pack-
age interface. Together with a way to get a concrete factory, this approach abstracts cli-
ent code from any processor implementations and forces clients to only work with
their abstract representations, in the form of their interfaces. You are freer to change
implementation details and better control the evolution of your design.
The abstract factory pattern is a specialization of this idea, where the factory class or
interface defines methods that create many different related object types.

3.3.5 Instantiation
To instantiate a class, you’ll need to call its constructor and assign the resulting object
to a compatible reference variable. A compatible reference variable is a variable typed
either to the target class itself, any of its superclasses, or any of the interfaces it imple-
ments.

Personal Copy for Andrei Arabolea, [email protected] 91


3 Classes and Interfaces

You can create an instance in two ways: using the NEW operator or using a CREATE OBJECT
statement.
We looked at an example of the NEW operator earlier in Listing 3.5. An instance of the
piggy_bank class is created and assigned to a variable typed to the class itself in the same
statement:

DATA(my_piggy_bank) = NEW piggy_bank( ).

If your variable is defined separately from the instantiation, you can omit the class
name and use the # character. ABAP will infer the correct type to be piggy_bank, for
instance, in Listing 3.31.

DATA my_piggy_bank TYPE REF TO piggy_bank.


...
my_piggy_bank = NEW #( ).

Listing 3.31 Type Inference With the # Character

This approach is particularly useful for RETURNING or EXPORTING parameters to methods,


where the concrete type is already defined in the method signature and you don’t need
to repeat it in the NEW statement.
However, most of the time, you should prefer typing your variables to interfaces. In
that case, you’ll need to be explicit when instantiating the concrete class, as in Listing
3.32.

DATA target TYPE REF TO money_target.


...
target = NEW piggy_bank( ).

Listing 3.32 Specific Class Name Used for Instantiation

Alternatively, you can cast to the desired type when declaring and instantiating the
variable in the same statement, as in the following code:

DATA(target) = CAST money_target( NEW piggy_bank( ) ).

You can use a CREATE OBJECT statement to create object instances, but the variable dec-
laration must be done separately, as in Listing 3.33.

DATA my_piggy_bank TYPE REF TO piggy_bank.


...
CREATE OBJECT my_piggy_bank.

Listing 3.33 Using CREATE OBJECT

The object type is also required when assigning to a superclass or interface reference, as
in Listing 3.34.

92 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


3.4 Summary

DATA target TYPE REF TO money_target.


...
CREATE OBJECT target TYPE piggy_bank.

Listing 3.34 Using CREATE OBJECT with a Specific Class

Prefer NEW to CREATE OBJECT


Using NEW is more succinct and readable than using the more verbose CREATE OBJECT,
and it also works with type inference when possible.
Parameters for the CREATE OBJECT statement need to be declared after the EXPORTING
keyword, which makes it even more verbose.

CREATE OBJECT is only necessary when using dynamic programming to create an


instance, which should be rare. An example is shown in Listing 3.35.

" target_class_name comes from some configuration table


DATA target_class_name TYPE seoclsname.
...
DATA target TYPE money_target.
CREATE OBJECT target TYPE (target_class_name).

Listing 3.35 Using CREATE OBJECT with a Dynamic Class Name

3.4 Summary
If data is the heart of ABAP, behavior is its brain. In this chapter, we looked at how you
can design and code the high-level elements of ABAP Objects: classes, interfaces, and
related objects. The ABAP clean code guidelines we explored in this chapter include the
following:
쐍 An interface models the behaviors of an abstract type.
쐍 Only declare types and methods in interfaces.
쐍 Use classes as blueprints to create objects.
쐍 Think interfaces first; abstract classes second.
쐍 Use utility classes for basic functions.
쐍 Inheritance models an is-a relationship.
쐍 Avoid deep inheritance hierarchies.
쐍 Design for inheritance or disallow it.
쐍 Prefer composition to inheritance.
쐍 Don’t mix stateful and stateless paradigms in the same class.
쐍 Use global classes by default.

Personal Copy for Andrei Arabolea, [email protected] 93


3 Classes and Interfaces

쐍 Class members visibility should be minimized.


쐍 Consider making immutable attributes read-only.
쐍 Only use read-only attributes in certain situations.
쐍 Declare global classes constructors in the PUBLIC SECTION.
쐍 Declare local classes constructors in the correct section.
쐍 Use dependency injection to decouple from underlying resources.
쐍 Consider static creation methods.
쐍 Only use singleton when multiple instances don’t make sense.
쐍 Prefer NEW to CREATE OBJECT.

In the next chapter, we’ll look at the building blocks of clean classes: clean methods.

94 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Chapter 4
Methods
This chapter deals with clean ABAP methods. We’ll walk through key
method concepts such as method design, method body, and control
flow. We’ll also explore the preferred way to call methods with clean
ABAP.

Methods are the ultimate containers of executable code in ABAP Objects. Other types
of structures that directly contain code are function modules, programs, and subrou-
tines, but as discussed in Chapter 2, Section 2.3, methods are the preferred low-level
building blocks for clean ABAP code.
Many other chapters in this book describe detailed clean code patterns and recommen-
dations for ABAP code within methods. This chapter deals with the design, declaration,
and usage of methods; high-level control flow within methods; method body guide-
lines; and invoking methods. We’ll start with an in-depth discussion on methods in the
context of object-oriented programming in Section 4.1. Then, in Section 4.2, we’ll dive
deeply into method parameters and their use before turning, in Section 4.3, to clean
code principles to apply to the method body. To finish this chapter, we’ll show you how
to call your methods cleanly in Section 4.4.

4.1 Object-Oriented Programming


In this section, we’ll discuss how to make methods work better within an object-
oriented design, picking up on our earlier discussion of object-oriented programming
in Chapter 3, Section 3.1.

4.1.1 Static and Instance Methods


Static methods are methods declared with the CLASS-METHODS keyword and are attached
to the class itself, not to instances of the class. To call a static method, you’ll use the
class name directly, without needing to create an instance of that class. In this way,
static methods are not truly object-oriented. They do not participate in the class inher-
itance hierarchy and do not participate in dynamic dispatch (thus, the term “static”).
Listing 4.1 shows you how to declare a static method.

Personal Copy for Andrei Arabolea, [email protected] 95


4 Methods

CLASS blog_post DEFINITION


PUBLIC.

PUBLIC SECTION.

CLASS-METHODS:
publish.

ENDCLASS.

Listing 4.1 Static Method Definition

You can call this method using the class name directly, followed by a fat arrow (=>):

blog_post=>publish( ).

Since static methods constrain the behavior to a specific implementation, they are not
flexible.
Instance methods, meanwhile, are attached to instances of the class. Instance methods
are declared with the METHODS keyword, as shown in Listing 4.2.

CLASS blog_post DEFINITION


PUBLIC.

PUBLIC SECTION.

METHODS:
publish.

ENDCLASS.

Listing 4.2 Instance Method Definition

To call an instance method, you need an instance of the class, followed by a thin arrow
(->):

DATA my_blog_post TYPE REF TO blog_post.


...
my_blog_post->publish( ).

Consider Using Instance Methods by Default


The key to the flexibility of instance methods is the fact that they are attached to
instances of classes. At runtime, a specific instance could refer to any subclass in the
hierarchy of the declared class variable (including itself), which makes the method call
“dynamic” or “virtual.”

96 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.1 Object-Oriented Programming

Also, the behavior of instance methods can be redefined and more easily mocked in
unit tests. Their data scope is the scope of the class instance, and resources can be con-
figured per instance. For these reasons, we recommend using instance methods by
default, rather than static methods.
Look at the related discussion involving classes in Chapter 3, Section 3.1.2.

Methods should be static in the following cases: for static creation methods and for
utility methods.
Static creation methods are methods that work as specialized constructors (as described
earlier in Chapter 3, Section 3.3.3). These methods return an instance of the owning
class or a subclass, as shown in Listing 4.3, where the create_as_copy method serves as
a static creation method by creating a copy of the passed in source_blog_post instance.

CLASS blog_post DEFINITION


PUBLIC.

PUBLIC SECTION.

CLASS-METHODS:
create_as_copy
IMPORTING
source_blog_post TYPE REF TO blog_post
RETURNING
VALUE(result) TYPE REF TO blog_post.

...

ENDCLASS.

Listing 4.3 Static Creation Method Example

Utility methods are methods that don’t depend on any underlying resources and per-
form a static operation that is not reasonably expected to change and does not need
any parameterized behavior. These methods should usually be part of a utility class
that provides related operations (see Chapter 3, Section 3.1.2). An example of the defini-
tion of a utility method is shown in Listing 4.4, where the fahrenheit_to_celsius
method performs a simple temperature conversion. This kind of operation does not
require any variation and is perfectly fine as a utility method.

CLASS temperature_conversion DEFINITION


PUBLIC
ABSTRACT
FINAL.

Personal Copy for Andrei Arabolea, [email protected] 97


4 Methods

PUBLIC SECTION.

CLASS-METHODS fahrenheit_to_celsius
IMPORTING
temperature_in_fahrenheit TYPE temperature
RETURNING
VALUE(result) TYPE temperature.

...

ENDCLASS.

Listing 4.4 Utility Method Example

Both static and instance methods are implemented with the METHOD … ENDMETHOD con-
struct in the class implementation area, as shown in Listing 4.5 for the static method
temperature_conversion=>fahrenheit_to_celsius.

CLASS temperature_conversion IMPLEMENTATION.

METHOD fahrenheit_to_celsius.
result = ( temperature_in_fahrenheit - 32 ) * 5 / 9.
ENDMETHOD.

ENDCLASS.

Listing 4.5 Static Method Implementation

Don’t Call Static Methods through Instance Variables


It is possible to call a static method through an instance of a class. However, a static
method is attached to the class itself, and calling it through an instance is redundant
and a potential source of confusion.
Make the fact that you’re calling a static method clear by always qualifying the method
call with the class name.

4.1.2 Public Instance Methods


Public instance methods in a class effectively define its interface to outside consumers.
Anyone with access to the class can call that class’s public instance methods.
To better manage dependencies and improve isolation, you should decouple from con-
crete classes and focus on the abstraction that the class presents. In this way, replacing
implementation details with alternative implementations, like mock instances for
testing, will be easier.

98 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.1 Object-Oriented Programming

Public Instance Methods Should Be Part of an Interface


For a class to serve as a good citizen in an overall design, the class should implement
interfaces that define its abstract behavior. Consequently, most, if not all, of its public
instance methods should be part of an interface.
In this way, the class itself becomes a mere implementation of the concepts repre-
sented by the interfaces it implements, and consumers don’t need to depend directly
on the class at all. This important step enables the dependency inversion principle, the
D in SOLID.

Not all classes need to implement interfaces. Exception classes, value objects (see Chap-
ter 3, Section 3.1.2), and enumeration classes (see Chapter 6, Section 6.2.2), for example,
usually don’t need to present an abstraction over their operations and, as such, aren’t
usually backed by interfaces. In general, classes that represent values or entities that
only contain data, and not services, won’t implement interfaces.
For more information about interfaces and their advantages, refer to Chapter 3, Section
3.1.1.
Let’s revisit and improve the design of the thermal_switch class presented in Chapter 3,
Section 3.1.2, specifically in Listing 3.15. The thermal_switch class’s main service is to pro-
tect a wrapped device from overheating. Its methods are trip (to trip the switch and
turn the device off) and reset (to reset the switch to a normal state), which were imple-
mented as direct public instance methods on the class.
To make those methods into interface methods and better abstract consumers of a
thermal protector from the specific implementation present in the thermal switch, we
can extract the interface thermal_protector, as shown in Listing 4.6.

INTERFACE thermal_protector
PUBLIC.

METHODS:
trip
FOR EVENT critical_temperature_reached OF thermal_sensor,
reset.

ENDINTERFACE.

Listing 4.6 The thermal_protector Interface

The thermal_switch class now implements thermal_protector, which results in all its
public instance methods being backed by interfaces. Listing 4.7 shows its refactored
PUBLIC SECTION.

Personal Copy for Andrei Arabolea, [email protected] 99


4 Methods

CLASS thermal_switch DEFINITION


FINAL
PUBLIC.

PUBLIC SECTION.

INTERFACES:
switchable,
thermal_protector.

ALIASES:
trip FOR thermal_protector~trip,
reset FOR thermal_protector~reset.

METHODS:
constructor
IMPORTING
managed_device TYPE REF TO switchable
sensor TYPE REF TO thermal_sensor.
...

Listing 4.7 The Refactored thermal_switch Public Section

The resulting Unified Modeling Language (UML) design is shown in Figure 4.1, which
shows that a thermal_switch is both a thermal_protector and a switchable.

«interface» «interface» «interface»


switchable thermal_protector thermal_sensor
is_on(): bool «handler» trip() «event»
is_off(): bool reset() critical_temperature_reached()
toggle()

managed_device
device thermal_switch

Figure 4.1 The thermal_switch and thermal_protector Design

4.1.3 Method Redefinition


Within a subclass, you can redefine instance methods that are not final and have a spe-
cialized implementation for the subclass that should be called in lieu of the superclass
implementation.

100 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.1 Object-Oriented Programming

Let’s revisit the fan class we introduced in Chapter 3. Now, let’s say we have a new
requirement to create a fan subclass that steps over multiple values when its intensity
is changed. We can redefine the appropriate methods with the desired implementa-
tion, as shown in Listing 4.8.

CLASS step_fan DEFINITION


PUBLIC
FINAL
INHERITING FROM fan.

PUBLIC SECTION.

METHODS:
constructor
IMPORTING step TYPE int4,
increase_intensity REDEFINITION,
decrease_intensity REDEFINITION.

PRIVATE SECTION.

DATA:
step TYPE int4.

ENDCLASS.

CLASS step_fan IMPLEMENTATION.

METHOD constructor.
super->constructor( ).
me->step = step.
ENDMETHOD.

METHOD increase_intensity.
DO step TIMES.
super->increase_intensity( ).
ENDDO.
ENDMETHOD.

METHOD decrease_intensity.
DO step TIMES.
super->decrease_intensity( ).
ENDDO.
ENDMETHOD.

ENDCLASS.

Listing 4.8 The step_fan Subclass

Personal Copy for Andrei Arabolea, [email protected] 101


4 Methods

The step_fan class shown in Listing 4.8 inherits from fan and redefines the methods
increase_intensity and decrease_intensity to execute them in a certain number of
steps. Note how the methods are declared again with the REDEFINITION keyword. The
redefined methods can then call the corresponding superclass version with the super->
syntax. Note also that the constructor is required to call the superclass constructor as
its first statement.

Be Careful When Redefining Methods


As explored in Chapter 3, Section 3.1.2, inheritance is a powerful tool that is often hard
to get right.
When you have method redefinitions, some behavior present in the superclass is
adjusted to fulfill a new requirement. This adjustment could break clients’ assump-
tions about the superclass, which violates the Liskov substitution principle, the L in
SOLID.
Minimize the number of methods that are redefined. Strive to only implement meth-
ods that are abstract in the superclass. This approach makes the design easier to rea-
son about since each class has only a single version of each method. This kind of
hierarchy is called a normalized hierarchy in the book Working Effectively with Legacy
Code (Prentice Hall, 2004).

4.2 Parameters
Method parameters fall broadly into three kinds or categories: input, output, and
input-output parameters.
Input parameters are declared with the IMPORTING keyword. Output parameters can be
declared as EXPORTING parameters or as a single RETURNING parameter. Input-output
parameters are declared with the CHANGING keyword.
One common source of confusion for novice ABAP developers is that, apart from the
CHANGING keyword, these keywords have other keywords as counterparts when invoking
methods, as listed in Table 4.1.

Declaration Invocation

IMPORTING EXPORTING

EXPORTING IMPORTING

RETURNING RECEIVING

CHANGING CHANGING

Table 4.1 Parameters Declaration and Invocation Counterparts

102 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.2 Parameters

Some invocation keywords are optional depending on how the method is declared and
called. In this section, we’ll cover guidelines on parameter declarations. Section 4.4 will
discuss guidelines for method invocation.

4.2.1 How Many Input Parameters Are Too Many?


Let’s look at the input parameters for the method declaration shown in Listing 4.9.

METHODS add_item
IMPORTING
product_id TYPE product_id
product_group TYPE product_group
amount TYPE int4
unit TYPE unit_of_measure.

Listing 4.9 Too Many Input Parameters?

The add_item method shown in Listing 4.9 has many parameters that describe separate
concepts, all lumped together as input parameters. Creating new structures or classes
for these concepts makes the method clearer and easier to test and improves the over-
all design, as shown in Listing 4.10.

METHODS add_item
IMPORTING
product TYPE REF TO product
quantity TYPE quantity.

Listing 4.10 Minimized Number of Input Parameters

Too many input parameters may also be an indication that the method does more than
one thing. More information on this topic is found in Section 4.3.1.

Aim for as Few Input Parameters as Possible


The number of input parameters for a method can significantly affect how usable and
understandable the method is to callers, the complexity of its internal implementa-
tion, and how hard it is to test it. In general, the less input parameters a method has,
the easier it is to consume it, understand its operation, and test it.
Minimize the number of IMPORTING parameters to your methods. Most methods
should have less than three input parameters. Three or more parameters makes the
sheer number of possible parameter value combinations harder to use and test.

4.2.2 Optional Input Parameters


ABAP does not support method overloads, which occur when methods with the same
name exist that only differ in their parameters. As a result, optional parameters, which

Personal Copy for Andrei Arabolea, [email protected] 103


4 Methods

are parameters marked with the modifier OPTIONAL, have historically been used to con-
trol different behaviors within methods.
A typical example is a method that processes either a table of items or a single item, as
shown in Listing 4.11.

METHODS process
IMPORTING
item_to_process TYPE item OPTIONAL
items_to_process TYPE item_table OPTIONAL.

Listing 4.11 Mutually Exclusive Optional Parameters

This kind of method signature is confusing to the caller and harder to implement. What
is not clear is which parameters are required, which combinations of parameters are
valid, and which parameters are mutually exclusive. The implementation must check
for all valid combinations and fail or branch accordingly.

Split the Behavior Instead of Adding Optional Parameters


Different behaviors should be implemented in different methods (see Section 4.3.1).
Separate methods with properly chosen names and specific parameters for each case
should be created. Using separate methods provides clear guidance for which parame-
ter combinations are valid and expected and are easier to implement.

For the example shown in Listing 4.11, separating the functionality results in the meth-
ods shown in Listing 4.12.

METHODS:

process_item
IMPORTING
item_to_process TYPE item,

process_items
IMPORTING
items_to_process TYPE item_table.

Listing 4.12 Separate Methods for Separate Functionalities

Default parameter values, which are accomplished by annotating a parameter with the
DEFAULT modifier and including a default value, should be used to advertise that the
parameter is optional for the caller, but not for the method, which will use the default
value if one is not provided. For an example of a default parameter, look at the to_csv
method in Listing 4.13. The parameter separator is optional and, if not assigned by the
caller, will have the value `,` (a comma) by default.

104 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.2 Parameters

METHODS to_csv
IMPORTING
separator TYPE string DEFAULT `,`
RETURNING
...

Listing 4.13 Parameter with Default Value

4.2.3 Preferred Input Parameters


If all input parameters to a method are optional, either by marking them as OPTIONAL or
using a default value, then one of them can be annotated as a PREFERRED PARAMETER, as
shown in Listing 4.14.

METHODS to_csv
IMPORTING
separator TYPE string DEFAULT `,` PREFERRED PARAMETER
quote type string DEFAULT `"`
RETURNING
...

Listing 4.14 Method with Preferred Parameter

When calling a method with a parameter marked as a PREFERRED PARAMETER and provid-
ing a single parameter, you don’t need to name the parameter at the call site; the pre-
ferred parameter will automatically be used. Thus, when calling the to_csv method
defined in Listing 4.14 with a single parameter, that parameter, if not named, will be
bound to the preferred parameter, for instance, in the following call:

DATA(item_as_csv) = item->to_csv( `;` ).

Although this style makes the call more concise, which parameter is being supplied
might not be clear, thus making the code harder to understand. Even if unnecessary,
naming the parameter can increase readability, such as in the following code:

DATA(item_as_csv) = item->to_csv( separator = `;` ).

If the parameter is not marked as a PREFERRED PARAMETER, the caller will be forced to
name the parameter when calling it.

Use a Preferred Parameter Sparingly


Only use a PREFERRED PARAMETER if the method name and its preferred parameter are
clear and easy to understand when the method is being called, especially when omit-
ting the parameter name (see also Section 4.4.4).

Personal Copy for Andrei Arabolea, [email protected] 105


4 Methods

4.2.4 Boolean Input Parameters


Imagine you have a credit score service class that provides a method for calculating the
credit score of a customer. The definition of this class is shown in Listing 4.15, where the
calculate_credit_score method uses a Boolean parameter to control whether to use
strong heuristics in the calculation.

CLASS credit_score_service DEFINITION


PUBLIC
FINAL.

PUBLIC SECTION.

METHODS:

constructor
IMPORTING customer TYPE REF TO customer,

calculate_credit_score
IMPORTING use_strong_heuristics TYPE abap_bool
RETURNING VALUE(credit_score) TYPE int4.

PRIVATE SECTION.

DATA:
customer TYPE REF TO customer.

ENDCLASS.

Listing 4.15 Credit Score Service with a Method with a Boolean Parameter

The calculate_credit_score method can be called in the following way:

DATA(my_credit_score) =
my_credit_score_service->calculate_credit_score( abap_true ).

In this code, what that Boolean parameter means in the call is not clear. You can allevi-
ate this problem by naming the parameter (see Section 4.4.4), as in the following code:

DATA(my_credit_score) =
my_credit_score_service->calculate_credit_score(
use_strong_heuristics = abap_true ).

This version improves things a bit even though the burden is on the client to use this
style. Moreover, an issue with the semantics of the method still exists.
It’s not hard to imagine that the implementation of calculate_credit_score will look
something like the code shown in Listing 4.16.

106 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.2 Parameters

METHOD calculate_credit_score.

IF use_strong_heuristics = abap_true.
" use a slower but more robust algorithm....

ELSE.
" use a faster but less robust algorithm....

ENDIF.

ENDMETHOD.

Listing 4.16 Branching Implementation

When a method takes a Boolean input parameter, usually the method does two things
instead of one (see also Section 4.3.1).

Create Separate Methods Instead of Using Boolean Parameters


Using Boolean parameters to control decisions and branching inside of a method’s
body makes the method more complex and harder to use and maintain.
Different behaviors should be implemented in different methods with properly chosen
names.

In our example, the calculate_credit_score method should be broken up into two


methods, each focused on a different algorithm, as shown in Listing 4.17.

METHODS:

calculate_simple_credit_score
RETURNING VALUE(credit_score) TYPE int4,

calculate_complex_credit_score
RETURNING VALUE(credit_score) TYPE int4.

Listing 4.17 Separate Methods Instead of a Branching Method

In this way, not only is implementing each method individually easier, consuming
those methods separately is also easier. If needed, common code within the original
method could be refactored into supporting private methods.
You can even go a step further and create separate classes for each scenario. For our
credit score service, we could have an interface with a simple calculate_credit_score
method with a separate class implementing each algorithm. Thus, even the decision of
which algorithm to use is lifted from client code. The client code itself would only
depend on the interface and its simpler method.

Personal Copy for Andrei Arabolea, [email protected] 107


4 Methods

4.2.5 EXPORTING Parameters


Results that a method provides to the caller can be declared either as EXPORTING param-
eters or a single RETURNING parameter.
EXPORTING parameters allow you to define multiple outputs from a method, as shown in
Listing 4.18.

METHODS check_business_partners
IMPORTING
business_partners TYPE business_partners
EXPORTING
result TYPE result_type
failed_keys TYPE /bobf/t_frw_key
messages TYPE /bobf/t_frw_message.

Listing 4.18 Multiple EXPORTING Parameters

The check_business_partners method can validate a list of business partners provided


in the input parameter business_partners. The outcome of this validation is returned
in three output parameters: result is the overall status of the validation, failed_keys is
a table of the business partner keys that failed validation, and messages is a list of mes-
sages generated as part of the validation.
EXPORTING parameters are optional for the caller, so when calling check_business_part-
ners, you can capture the output parameters that you’re interested in, as shown in List-
ing 4.19.

check_business_partners(
EXPORTING
business_partners = my_business_partners
IMPORTING
result = DATA(business_partners_check_result)
messages = DATA(business_partner_messages) ).

Listing 4.19 Invoking a Method with EXPORTING Parameters

Note how you need to provide the counterpart keywords to the declaration of IMPORTING
and EXPORTING parameters, which flips them to EXPORTING and IMPORTING, respectively.

Minimize the Number of EXPORTING Parameters


The presence of many EXPORTING parameters is an indication that your method might
be doing more than one thing, which makes the method harder to implement and
harder to consume. A good method does one thing (Section 4.3.1), and that sharp focus
should be reflected by the outputs of the method as well.

108 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.2 Parameters

If the output parameters of your method do not form a logical entity, split the method
into several focused methods. If they do form a logical entity, group parameters into a
more cohesive structure or a class and use this structure to minimize the number of
EXPORTING parameters.

For the check_business_partners method, the output parameters form a logical entity,
a check result, which could be made explicit as a check_result structure. Then, the
check_result structure can be used to type the now single EXPORTING parameter of the
method, as shown in Listing 4.20.

TYPES:
BEGIN OF check_result,
result TYPE result_type,
failed_keys TYPE /bobf/t_frw_key,
messages TYPE /bobf/t_frw_message,
END OF check_result.

METHODS check_business_partners
IMPORTING
business_partners TYPE business_partners
EXPORTING
result TYPE check_result.

Listing 4.20 Minimizing EXPORTING Parameters

Note that this approach is still not the cleanest way to declare check_business_partners,
which we’ll explore next.

4.2.6 RETURNING Parameters


RETURNING parameters allow you to declare a single RETURNING parameter for a method,
which can be thought of as the result of the method call.
The last iteration of our check_business_partners method shown in Listing 4.20 ended
up with a single EXPORTING parameter. Calling that method is shown in Listing 4.21.

check_business_partners(
EXPORTING
business_partners = my_business_partners
IMPORTING
result = DATA(business_partners_check_result) ).

Listing 4.21 Calling a Method with a Single EXPORTING Parameter

Personal Copy for Andrei Arabolea, [email protected] 109


4 Methods

Prefer RETURNING to EXPORTING


As you minimize the number of EXPORTING parameters, when you end up with a single
EXPORTING parameter, make this parameter a RETURNING parameter.
A RETURNING parameter is a more conventional way to declare an output parameter.
This kind of parameter makes calls to the method shorter and allows for method chain-
ing (when you chain a method call to the result of another method call without assign-
ing the return value to any intermediate variable).
Some performance recommendations advise using EXPORTING parameters passed by
reference for large tables of data (described in more detail in Section 4.2.8). However,
using RETURNING parameters for these cases is usually acceptable. If you have proof of
poor performance and of a tangible benefit, you can resort to the more cumbersome
procedural style that comes with using EXPORTING. As usual with performance issues,
measure and adjust accordingly.

Listing 4.22 shows the final refactoring of the definition of the check_business_partners
method, in which the EXPORTING parameter is changed to a RETURNING parameter.

METHODS check_business_partners
IMPORTING
business_partners TYPE string
RETURNING
VALUE(result) TYPE check_result.

Listing 4.22 RETURNING Parameter Declaration

Calls to check_business_partners are much cleaner, as in the following code:

DATA(business_partners_check_result) =
check_business_partners( my_business_partners ).

Consider Using result as the Name of RETURNING Parameters


The ability to name a RETURNING parameter is not a common capability in many main-
stream programming languages. In those languages, a method simply returns the out-
put with a special statement, for example, return 42.
Good method names obviate the need to name the RETURNING parameter. The RETURN-
ING parameter name would do little more than parrot the method name or repeat
something obvious, perhaps even create conflict with other members of the class.
Consumers also rarely refer to the RETURNING parameter name when invoking your
method. They either assign the result of the method to a variable, use it as part of a
larger expression, or ignore the result altogether.

110 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.2 Parameters

Therefore, we recommend simply calling the RETURNING parameter result or some-


thing similar. What your methods are returning will be obvious and at the same time
invisible, almost like a keyword of the language itself.
Use a meaningful name for the RETURNING parameter only if what it stands for is not
obvious, for example, in methods that return me for method chaining or in methods
that create an entity but don’t return the created entity, only its key.

4.2.7 CHANGING Parameters


You can use CHANGING parameters when a value is both an input and an output to a
method. In this case, the method can change a component of the parameter as part of
its operation. This approach only makes sense for structures and internal tables, which
are values with internal components. A structure has fields that can be changed, while
an internal table has rows that can be changed.
For example, let’s say you have many entities that need to be validated, and you want
to collect all their validation messages in a single message table. You can use the exam-
ple method shown in Listing 4.23 in your entity classes.

METHODS validate
CHANGING
messages TYPE message_table.

Listing 4.23 CHANGING Parameter Declaration

This instance method will validate the current entity and append any generated valida-
tion messages to the collecting parameter messages, which is an internal table of mes-
sages. The messages parameter could already contain messages (perhaps added earlier
by other validation methods), and as a result, by using the same variable in a number
of validate calls, all the generated messages will be collected in the parameter, as shown
in Listing 4.24.

Use CHANGING Parameters Sparingly


CHANGING parameters should be reserved for cases where an existing structure or inter-
nal table that is potentially already filled with data is changed, like messages in the val-
idate method example.
Use input parameters for object references that you work with, since your method can
still call instance methods on the parameter to change its internal state.
If you fill up or replace the parameter completely (i.e., you assign a new value to the
parameter), an EXPORTING parameter or a RETURNING parameter is more appropriate.

Personal Copy for Andrei Arabolea, [email protected] 111


4 Methods

DATA messages TYPE message_table.


LOOP AT entities INTO DATA(entity).
entity->validate( CHANGING messages = messages ).
ENDLOOP.

Listing 4.24 CHANGING Parameter Usage

To eliminate the CHANGING parameter of the validate method, you can introduce a class
that represents a message container, which will collect messages and also provide
many more convenient methods for a list of messages. As an example, look at the mes-
sage_container class in Listing 3.16, discussed in Chapter 3, Section 3.1.3.
The refactored validate method should look like Listing 4.25.

METHODS validate
IMPORTING
message_container TYPE ref to message_container.

Listing 4.25 Refactored CHANGING Parameter to IMPORTING Reference

This version is also easier to consume since the method now doesn’t have a CHANGING
parameter, as in Listing 4.26.

DATA(message_container) = NEW message_container( ).


LOOP AT entities INTO DATA(entity).
entity->validate( message_container ).
ENDLOOP.

Listing 4.26 Using the Refactored Method

Avoid Using More Than One Kind of Output Parameter


Avoid combining EXPORTING, CHANGING, and RETURNING parameters in the same method.
Different kinds of output parameters indicate that the method might do more than
one thing, which is confusing to readers and makes calling the method needlessly com-
plicated.

4.2.8 Pass-by-Value and Pass-by-Reference


Apart from the input or output category of parameters, you can also declare parame-
ters to be pass-by-value or pass-by-reference.
Pass-by-value is when a copy of the parameter value is made before calling the method,
and the method parameter is assigned to that copy. Note that, when you pass an object
reference by value, a copy of the reference is made, which is just a pointer that points to
the same object in memory.

112 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.2 Parameters

ABAP has important optimizations for value copy semantics. When a value is assigned
to another variable or parameter, a copy is only made when needed, usually when
changing something in the copy. This approach is commonly referred to as copy-on-
write optimization.
Pass-by-reference is when a reference to the original value is passed to the method. If
the method can reassign the value, the calling method will also be impacted by the
change.
Pass-by-reference is the default for IMPORTING, EXPORTING, and CHANGING parameters.
Pass-by-value is the only accepted option for RETURNING parameters. As a result, you’ll
always see the VALUE modifier in RETURNING parameters. This modifier can also be used
for other parameter categories to make them pass-by-value.
Although rarely used, the syntax for pass-by-reference can also be made explicit with
the REFERENCE modifier. For example, the code shown earlier in Listing 4.10 can be
rewritten into the code shown in Listing 4.27.

METHODS add_item
IMPORTING
REFERENCE(product) TYPE REF TO product
REFERENCE(quantity) TYPE quantity.

Listing 4.27 Explicit Pass-by-Reference Syntax

Do Not Reassign IMPORTING Parameters Passed by Value


Whenever an input parameter is passed by reference, which is the default, the parame-
ter cannot be reassigned inside the method.
A pass-by-value IMPORTING parameter can be reassigned within the method. But, since
this parameter is a copy, its new value will not be visible to the caller.
This approach is a poor practice that you should avoid. IMPORTING parameters are
meant to be inputs to a method, and reassigning them doesn’t make sense. Use proper
local variables instead if you need to use the input as a starting value for a computa-
tion and then update the local variables instead. Use a pass-by-reference input param-
eter if you don’t need the parameter to be pass-by-value.

To make the quantity parameter shown in Listing 4.27 pass-by-value, you can end up
with the declaration shown in Listing 4.28.

METHODS add_item
IMPORTING
REFERENCE(product) TYPE REF TO product
VALUE(quantity) TYPE quantity.

Listing 4.28 Pass-by-Value Input Parameter

Personal Copy for Andrei Arabolea, [email protected] 113


4 Methods

Mind the Semantics of EXPORTING Parameters, Prefer Pass-By-Value


Parameters passed by reference ultimately point to existing memory areas that may
have already been filled. EXPORTING parameters that are passed by reference will retain
their original value before the method was called. This makes them work like CHANGING
parameters (Section 4.2.7).
To make an EXPORTING parameter passed by reference behave like a true output param-
eter, it’s important to actually assign a value to it. It might make sense to clear the
value at the beginning of the method if the parameter could end up unassigned, per-
haps because of the method logic or an exception that is raised early on.
Another option is to make the EXPORTING parameter into a pass-by-value parameter,
which will make it behave like a RETURNING parameter. EXPORTING parameters that are
passed by value are handed over as new, separate memory areas that are empty.
There’s no need to preemptively clear them, just like there’s no need to clear RETURNING
parameters.
Therefore, prefer pass-by-value EXPORTING parameters.
Pass-by-reference EXPORTING parameters should be used when there’s a measurable
performance hit when using pass-by-value. One such case is when you process large
internal tables in batches. You can reuse an existing memory area repeatedly so as not
to incur the cost of allocation for each batch. Cases like these should be rare. Aim for
clarity and correctness first and improve the performance only when it becomes an
issue.

As an example of EXPORTING parameters, look at the declaration of the try_parse_int


method in Listing 4.29.

METHODS try_parse_int
IMPORTING
text TYPE string
EXPORTING
success TYPE abap_bool
result TYPE int4.

Listing 4.29 Method with EXPORTING Parameters

To make sure that the pass-by-reference EXPORTING parameters to try_parse_int are ini-
tialized before the method returns, the method body starts by setting and clearing the
parameters, as shown in Listing 4.30.

METHOD try_parse_int.

success = abap_false.
clear result.

114 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.3 Method Body

...

ENDMETHOD.

Listing 4.30 Initializing EXPORTING Parameters

If you change the EXPORTING parameters to pass-by-value, as shown in Listing 4.31, the
parameters will be initial by default when entering the method, and there’ll be no need
to clear or assign them to their initial value.

METHODS try_parse_int
IMPORTING
text TYPE string
EXPORTING
VALUE(success) TYPE abap_bool
VALUE(result) TYPE int4.

Listing 4.31 Pass-by-Value EXPORTING Parameters

Another advantage of EXPORTING parameters that are pass-by-value is that they prevent
a situation where the same variable could be assigned to an input and an output parame-
ter by the caller at the same time, and the method could clear the IMPORTING parameter
value indirectly by clearing the output EXPORTING parameter before doing its work.

CHANGING Parameters Passed by Value Don’t Make Sense


CHANGING parameters are both inputs and outputs to a method. Even though you can
make a CHANGING parameter a pass-by-value parameter with the VALUE modifier, doing
so does not change the way the CHANGING parameter works and generally does not make
sense. Leave CHANGING parameters with their default pass-by-reference semantics.

4.3 Method Body


Code is more often read than it is written, and thus the code within a method should be
optimized for reading. Not only will the method be easier to read, it will also be easier
to consume, change, adapt, and test. You’ll find reasoning about the method easier and
will spot defects more quickly. In this section, we’ll explore some important guidelines
to ensure the code within your methods is clear, understandable, and flexible.

4.3.1 Do One Thing


To be easier to read, test, and maintain, a method should not only have a single respon-
sibility, but it should perform that responsibility in the simplest way possible. A
method should do one thing, and only one thing.

Personal Copy for Andrei Arabolea, [email protected] 115


4 Methods

Many clean ABAP rules we discuss in this book help make your methods more focused
and closer to the goal of doing only one thing. A method probably only does one thing
if it follows the following rules:
쐍 It has a small number of input parameters.
쐍 It doesn’t include Boolean parameters.
쐍 It has exactly one output parameter.
쐍 It only throws one type of exception.
쐍 It is small.
쐍 It stays at a single level of abstraction.
쐍 You cannot extract other meaningful methods.
쐍 You cannot meaningfully group its statements into sections.

Let’s start by looking at the definition of a method for logging exceptions, as shown in
Listing 4.32.

METHODS
log_exception
IMPORTING
exception_instance TYPE REF TO cx_root.

Listing 4.32 Definition of a Method to Log Exceptions

The log_exception method will be used by some batch processing code whenever the
batch processing program encounters an exception. The code will call log_exception to
write information about the exception_instance to a log file and then abort the batch
process. Log files only accept a maximum number of characters per line. A user can
later read these logs and get more information about the exception to determine what
went wrong with the processing.
Our first implementation of the log_exception method is shown in Listing 4.33.

METHOD log_exception.
DATA(current_exception) = exception_instance.
WHILE current_exception IS BOUND.
" log a header with the exception class name
DATA(class_name) =
cl_abap_classdescr=>get_class_name( current_exception ).
log_line(
|---- Exception occurred: { class_name }| ).

" split the exception text into lines and log them
DATA(exception_text) = current_exception->get_text( ).
DATA(lines) = split_text_into_lines( exception_text ).
LOOP AT lines into data(line).

116 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.3 Method Body

log_line( line ).
ENDLOOP.

" move to the previous exception


current_exception = current_exception->previous.
IF current_exception IS BOUND.
log_line( `---- Exception has previous exception` ).
ENDIF.
ENDWHILE.
ENDMETHOD.

Listing 4.33 log_exception First Implementation

The supporting log_line method logs a single line of text to the log file, and the split_
text_into_lines method splits a long text into multiple lines using a fixed number of
columns for each line. This method returns a table of strings representing the lines.
Even though the log_exception method has a single responsibility, many steps are
needed to perform its tasks, for instance, the following:
1. Store the exception in a local variable.
2. Start a loop while the exception is bound (not null).
3. Get the class name of the exception.
4. Log a header including the class name.
5. Split the exception text into lines.
6. Loop and log each line.
7. Move to the previous exception.
8. Repeat the first loop.

These steps hint at many things that the method does: It loops to log the previous
exception, queries the exception class name, and splits the exception text into lines.
The next section will discuss a clean ABAP rule that improves the log_exception
method until the method finally does only one thing in as few steps as possible.

4.3.2 Descend One Level of Abstraction


A method should tell a story that’s easy to follow and understand. Its body should stay
at the same level of abstraction, which is just below the responsibility denoted by its
name.
If more details of the story are needed, you can delve into the methods called by the
first method until one of two things happen. Either you’ll reach basic statements of the
language that implement some low-level algorithm, or you’ll reach a method call that
belongs to an interface.

Personal Copy for Andrei Arabolea, [email protected] 117


4 Methods

Either way, you should have a complete understanding of how a part of your code does
its work and whether the code coordinates with other dependencies. For more infor-
mation on class design and the crucial role of interfaces, check out Chapter 3.
That’s the beauty of well-crafted abstractions. The design is decoupled enough that you
only need to understand the parts of the system that you currently require. You won’t
need to read the whole codebase to understand what a certain part of the system does.

Descend One Level of Abstraction


Statements in a method should be one level of abstraction below the method name
itself. All these statements should be on the same level of abstraction.
This rule is called the “Stepdown Rule” in the book Clean Code: A Handbook of Agile
Software Craftsmanship (Pearson, 2008) and called the “Single Level of Abstraction
Principle (SLAP)” in the book The Productive Programmer (O’Reilly, 2008). The concept
probably first appeared in the book Smalltalk Best Practice Patterns (Prentice Hall,
1996), where it was called the “Composed Method Pattern.”

How can you keep a method focused on a single level of abstraction? One approach is
to explain what the method does in a few sentences or steps, which should all be
related to its goal. These steps should not go into more detail than is needed to under-
stand the responsibility of the method. The steps then should become the downstream
methods the method calls or the statements it executes.

Extract Method Refactoring


The extract method refactoring is a fundamental refactoring approach presented in
the book Refactoring: Improving the Design of Existing Code (Addison-Wesley, 1999).
You extract cohesive parts of a method into new methods, which are then called by the
original method in the place from which the functionality was extracted.
This refactoring is supported by ABAP Development Tools (ADT). Select the section of a
method you want to extract and press (Alt)+(Shift)+(M) on Windows or
(Cmd)+(Alt)+(M) on macOS to start the extract method refactoring wizard.

The log_exception method shown previously in Listing 4.33 is divided into sections of
statements with separate responsibilities, delimited by clarifying comments. The sec-
tion comments in the method describe the following steps:
1. Log a header with the exception class name.
2. Split the exception text into lines and log them.
3. Move to the previous exception (and repeat).

Not all these sections are at the same level of abstraction. We want to know at a high-
level what it means to log an exception. Details about getting the exception class name

118 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.3 Method Body

and splitting the exception text into lines is at a level of abstraction below the level
we’re interested in. We’re talking about details we’re not ready to delve into just yet.
Taking a step back and thinking about what the method should accomplish, we might
come up with the following steps, which are at the same level of abstraction:
1. Log a header for the exception.
2. Log a body for the exception.
3. Log the previous exception, if any.

This series of steps will translate into the final methods shown in Listing 4.34.

METHOD log_exception.
log_exception_header( exception_instance ).
log_exception_body( exception_instance ).
log_previous_exception( exception_instance->previous ).
ENDMETHOD.

METHOD log_exception_header.
DATA(class_name) =
cl_abap_classdescr=>get_class_name( exception_instance ).
log_text(
|---- Exception occurred: { class_name }| ).
ENDMETHOD.

METHOD log_exception_body.
DATA(exception_text) = exception_instance->get_text( ).
log_text( exception_text ).
ENDMETHOD.

METHOD log_previous_exception.
CHECK exception_instance IS BOUND.
log_text( `---- Exception has previous exception` ).
log_exception( exception_instance ).
ENDMETHOD.

METHOD log_text.
DATA(lines) = split_text_into_lines( text ).
log_lines( lines ).
ENDMETHOD.

METHOD log_lines.
LOOP AT lines INTO DATA(line).
log_line( line ).
ENDLOOP.
ENDMETHOD.

Listing 4.34 Each Method at a Single Level of Abstraction

Personal Copy for Andrei Arabolea, [email protected] 119


4 Methods

These methods are now each at the same level of abstraction and one level below the
responsibility implied by their names.
Apart from making your methods simple and easy to understand, breaking down
methods in this way might unearth interesting possibilities in the design. Whole new
classes and subfeatures that were tangled up in the code may come to light and provide
you with an enriched vocabulary for your project. Your design becomes less coupled,
more cohesive, and much cleaner.
You can start thinking about logging the exception body, not just logging the excep-
tion text. This approach might mean, later on, that you’ll also include the exception
stack trace with the text. You might start to think how to separate the formatting from
the logging, and a whole new exception_formatter class or interface, with its own set of
responsibilities, might emerge.

4.3.3 Keep Methods Small


ABAP is a notoriously verbose language. Coupled with a ton of legacy code, you may
find gargantuan methods or function modules spanning thousands of lines of code.
These sprawling methods are hard to understand, never mind debug and change.
When you’re confronted with a data declaration section as big as the code shown in
Listing 4.35, you already know this method does way more than one thing.

DATA:
class TYPE vseoclass,
attributes TYPE seoo_attributes_r,
methods TYPE seoo_methods_r,
events TYPE seoo_events_r,
types TYPE seoo_types_r,
aliases TYPE seoo_aliases_r,
implementings TYPE seor_implementings_r,
inheritance TYPE vseoextend,
friendships TYPE seof_friendships_r,
typepusages TYPE seot_typepusages_r,
clsdeferrds TYPE seot_clsdeferrds_r,
intdeferrds TYPE seot_intdeferrds_r,
attribute TYPE vseoattrib,
method TYPE vseomethod,
event TYPE vseoevent,
type TYPE vseotype,
alias TYPE seoaliases,
implementing TYPE vseoimplem,
friendship TYPE seofriends,
typepusage TYPE vseotypep,

120 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.3 Method Body

clsdeferrd TYPE vseocdefer,


intdeferrd TYPE vseoidefer,
new_clskey_save TYPE seoclskey.

Listing 4.35 Too Many Data Declarations: A Prelude to a Huge Method

Keep Methods Small


How small a method should be can’t be easily quantified. Methods should be as small
as possible, but not smaller.
As a rule of thumb, methods should have less than 20 statements, but optimally 5
statements or less. Note that we’re measuring in statements, not lines of code.
Using the rules in this chapter, like the single level of abstraction principle, and extract-
ing meaningful methods to create new, smaller methods will get you closer to this
goal.

Look at the original log_exception method shown earlier in Listing 4.33. You might
have thought this method was reasonably sized. However, as shown earlier in Listing
4.34, still many cohesive parts could be made into simpler methods. The resulting
methods were much smaller than the original and much cleaner. The resulting log_
exception method reads like the steps it implements. You can read it and understand
what it does at a high-level quickly without even delving into the other methods.
Now, look at the log_exception_body method, shown in Listing 4.36.

METHOD log_exception_body.
DATA(exception_text) = exception_instance->get_text( ).
log_text( exception_text ).
ENDMETHOD.

Listing 4.36 The log_exception_body Method

You might think you can still extract another method from it, resulting in the code
shown in Listing 4.37.

METHOD log_exception_body.
log_exception_text( exception_instance ).
ENDMETHOD.

METHOD log_exception_text.
DATA(exception_text) = exception_instance->get_text( ).
log_text( exception_text ).
ENDMETHOD.

Listing 4.37 The Extracted log_exception_text Method Is Redundant

Personal Copy for Andrei Arabolea, [email protected] 121


4 Methods

A new log_exception_text method could have a place, perhaps if log_exception_body


did more than just log the exception text. At this point, however, the new method is
simply a reiteration of the old one and doesn’t add much semantic value over the exist-
ing method.
Stop extracting methods when you feel comfortable that the method expresses its
intent clearly and simply through its statements.

4.3.4 Fail Fast


Often, when an error occurs or an exception is thrown, the root cause of the problem
can be traced to some parameter having an invalid value introduced earlier in the pro-
gram flow. The closer that exception is to the place the error was introduced, the easier
the error is to find and fix.

Fail Fast
Document and check the input parameters to your methods and fail with an exception
as early as possible if they are not valid for the execution of the method.
This recommendation is especially important for public and protected methods. Pri-
vate methods could also benefit from validation (or using ASSERT) should the program
and the assumptions that they make change, but usually you can rely on the validation
already performed by the methods that call them.
Validating later is not only hard to spot and understand but might waste important
resources. Not validating might make your code fail in hard-to-predict ways and might
result in invalid data and undesirable behaviors.

The step_fan class, shown earlier in Listing 4.8, is not validating its step input parame-
ter to the constructor. This value is used to step over a fixed amount when changing
the fan intensity. A negative value or zero wouldn’t work, and a value that’s bigger than
the maximum intensity wouldn’t make sense. The code should validate this value and
fail at the earliest possible spot, which is on construction, as shown in Listing 4.38.

METHOD constructor.
super->constructor( ).
IF step < 1 OR step > max_intensity.
RAISE EXCEPTION NEW invalid_argument_exception(
argument_name = `step`
argument_value = step ).
ENDIF.
me->step = step.
ENDMETHOD.

Listing 4.38 Constructor Validation

122 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.3 Method Body

The step_fan constructor first calls the superclass constructor and then validates its
input parameter and raises an exception right away if it’s invalid. It’s important to add
as much information to the exception as possible. In this case, the custom invalid_
argument_exception includes the argument name and the invalid argument value.

Focus on the Happy Path or Error Handling


A method body should emphasize the happy path it’s built for. If it needs to perform
error handling, it’s important that this error handling code doesn’t divert attention
from the main intent of the method. If it does, put the error handling code in a dedi-
cated method. For more information on this topic, refer to Chapter 11.

As it stands, the step_fan constructor error handling code dominates its logic. It makes
sense to break it down so that it’s more readable, as shown in Listing 4.39.

METHOD constructor.
super->constructor( ).
validate_step( step ).
me->step = step.
ENDMETHOD.

METHOD validate_step.
IF step < 1 OR step > max_intensity.
RAISE EXCEPTION NEW invalid_argument_exception(
argument_name = `step`
argument_value = step ).
ENDIF.
ENDMETHOD.

Listing 4.39 Separate Error Handling Method

Note that this kind of validation is meant to aid other developers fix their calling code,
and not for end users, since this validation is very technical in nature. End users could
get business-relevant validation errors in the form of messages that are presented to
them. Earlier in Section 4.2.7, Listing 4.26, we presented the code to validate a list of
entities. If this is part of a larger method that does some processing with the entities,
failing early might mean failing if any of those validations generated an error message.
Adding in some refactoring, we might end up with the code shown in Listing 4.40.

DATA(message_container) = NEW message_container( ).


validate_entities( message_container ).
message_container->raise_if_in_error( ).

...

Listing 4.40 Raising When a Message Container Has Error Messages

Personal Copy for Andrei Arabolea, [email protected] 123


4 Methods

In this case, the message_container class has a method that raises an exception if it con-
tains any error messages. Validation messages are added to the exception, which could
be caught by an upper layer and then shown to end users in a suitable manner. For
example, they could be OData error messages that are then displayed in a message pop-
over in an SAPUI5 application.

4.3.5 CHECK versus RETURN


Another type of validation is when you simply return early if the arguments to a
method don’t meet its expectations. In these cases, it’s fine for the method to simply
return instead of raising an exception.
Earlier in Listing 4.34, you saw an example of this “return early” behavior for the
method log_previous_exception. If the exception_instance parameter is not bound
(which means that the value is null), the method will return early without completing
its work.
Another method that would benefit from this kind of validation is the main log_excep-
tion method itself. If the exception instance is not bound, the method simply returns
without doing any work, as shown in Listing 4.41.

METHOD log_exception.
CHECK exception_instance IS BOUND.
log_exception_header( exception_instance ).
log_exception_body( exception_instance ).
log_previous_exception( exception_instance->previous ).
ENDMETHOD.

Listing 4.41 Check If the Exception Instance Is Bound

Both methods now use the CHECK statement. This statement reads perfectly fine but is
not a common statement and might cause some confusion. What happens when the
check condition evaluates to false might not be clear.
Another way to implement this kind of validation is to use a condition check followed
by a RETURN statement, as shown in Listing 4.42.

METHOD log_exception.
IF exception_instance IS NOT BOUND.
RETURN.
ENDIF.
log_exception_header( exception_instance ).
log_exception_body( exception_instance ).
log_previous_exception( exception_instance->previous ).
ENDMETHOD.

Listing 4.42 Return If the Exception Instance Is Not Bound

124 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.4 Calling Methods

This step adds another indentation level, which might break the visual flow of the code.
However, how you or your team feel about using CHECK statements is up to you. Com-
munity discussion might suggest that the statement is so unclear that few people will
understand its intended behavior.
No matter what you choose, you should not use CHECK outside of the initialization sec-
tion of a method. The statement behaves differently in different positions and may
lead to unclear, unexpected effects.
For example, a CHECK statement inside a LOOP ends the current iteration of the loop and
proceeds to the next iteration. In this context, a reader might mistakenly expect the
CHECK statement to end the method or exit the loop. In this case, we recommend using
an IF statement in combination with CONTINUE instead, since CONTINUE can only be used
inside loops.

4.4 Calling Methods


At this point, we’ve explored many aspects of calling a method in ABAP, from calling
static methods, where you’ll specify the class name using a fat arrow (=>), to calling
instance methods, where’ll you need a reference to an instance of the class and use the
thin arrow syntax (->).
Interface methods can be called the same way as class methods if you have a reference
pointing to the interface type. If, however, you have a reference to a class and want to
call an interface method on it, you need to add the interface name and a tilde (~) to the
start of the method call. For example, if you have an instance of a thermal_switch
(shown earlier in Listing 4.7) in the my_thermal_switch variable, you can call its interface
method switchable~is_on in the following way:

DATA(is_on) = my_thermal_switch->switchable~is_on( ).

The class can also alias the interface method to make the call look like a normal class
instance method call. An example of an aliased interface method and its usage can be
found in Chapter 3, Section 3.1.2.
With regard to how parameters are passed to methods, many options are available,
depending on how the method is declared. In this section, we’ll explore some guide-
lines to make your method invocations clearer and more readable.

4.4.1 Passing Input Parameters


Listing 4.43 shows a possible declaration of the method split_text_into_lines, which
we used earlier in Listing 4.33.

Personal Copy for Andrei Arabolea, [email protected] 125


4 Methods

METHODS:
split_text_into_lines
IMPORTING
text TYPE string
columns_per_line TYPE int4 DEFAULT 40
RETURNING
VALUE(lines) TYPE string_table.

Listing 4.43 split_text_into_lines Declaration

When only providing input parameters to a method and assigning its RETURNING param-
eter to a variable, you can simply call the method, as shown in Listing 4.44.

DATA(lines) =
split_text_into_lines(
text = text
columns_per_line = 80 ).

Listing 4.44 Calling a Method with Input Parameters and a RETURNING Parameter

You can also explicitly annotate the parameter category before the parameter list. In
the case of IMPORTING parameters, the caller must use the counterpart keyword EXPORT-
ING (Section 4.2.5), as shown in Listing 4.45.

DATA(lines) =
split_text_into_lines(
EXPORTING
text = text
columns_per_line = 80 ).

Listing 4.45 Explicitly Using EXPORTING

Omit the Optional EXPORTING Keyword


When all you have are input parameters and possibly a returning parameter, the
EXPORTING keyword is optional in a method call.
However, this style is unnecessarily verbose and redundant. Omit the optional EXPORT-
ING keyword in these cases.

4.4.2 Capturing Output Parameters


RETURNING parameters can be simply assigned as a value coming out of the method call
itself, as we’ve seen in Listing 4.44.
A RETURNING parameter from a method can also be captured alongside the parameter
list by using the RECEIVING keyword on a method call, as shown in Listing 4.46.

126 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.4 Calling Methods

split_text_into_lines(
EXPORTING
text = text
columns_per_line = 80
RECEIVING
result = DATA(lines) ).

Listing 4.46 Using the RECEIVING Keyword

Omit the RECEIVING Keyword


The call style using the RECEIVING keyword is unnecessarily verbose and forces you to
specify the EXPORTING keyword for input parameters.
To make the code more readable, omit the RECEIVING keyword and capture the return
value of the method directly.

The EXPORTING parameters from a method are captured by using the IMPORTING keyword
in a call. You’ve already seen an example of a call like this earlier in Listing 4.19. Another
example is shown in Listing 4.47, which calls the try_parse_int method declared earlier
in Listing 4.29.

try_parse_int(
EXPORTING
text = `42`
IMPORTING
success = DATA(parse_succeeded)
result = DATA(parsed_int) ).

Listing 4.47 Capturing EXPORTING Parameters with the IMPORTING Keyword

Any time an EXPORTING or CHANGING parameter is used, you must declare the parameter
category with the proper keyword in the method call. As described earlier in Section 4.2,
EXPORTING parameters are called with the IMPORTING keyword, and CHANGING parameters
are called with the CHANGING keyword, as shown earlier in Listing 4.24. This rule also
forces you to explicitly define the keyword EXPORTING for IMPORTING parameters.

4.4.3 The CALL METHOD Construct


Another way to call a method in ABAP is to use a style reminiscent of the way function
modules are called, with the CALL METHOD construct, as shown in Listing 4.48.
This call style doesn’t use parentheses around the parameters. With this approach, you
are forced to declare all keywords for capturing parameters, including the RECEIVING
keyword for a RETURNING parameter.

Personal Copy for Andrei Arabolea, [email protected] 127


4 Methods

CALL METHOD split_text_into_lines


EXPORTING
text = text
RECEIVING
result = DATA(lines).

Listing 4.48 Using CALL METHOD

Reserve CALL METHOD for Dynamic Method Calls


The CALL METHOD construct for calling methods is obsolete and unnecessarily verbose.
Use the call style with parentheses to make the code more consistent and easier to
read and to better support other clean code patterns discussed in this chapter.
Only use the CALL METHOD construct for dynamic method calls, where the class or the
method name is resolved at runtime, as in Listing 4.49.
CALL METHOD modify->(method_name)
EXPORTING
node = my_bo_c=>node-item
key = item->key
data = item
changed_fields = changed_fields.
Listing 4.49 CALL METHOD for Dynamic Method Calls

However, we recommend you design your application around interfaces and avoid
using dynamic method calls whenever possible.

4.4.4 Optional Parameter Name


When a method has a single non-optional input parameter or a PREFERRED PARAMETER,
the caller can provide a single unnamed argument for that parameter. This call style,
when using a properly named variable or constant, can make the calling code more
concise and still clean and readable, as in the following code:

DATA(lines) = split_text_into_lines( text ).

Naming the parameter in this case, as in the following code, is unnecessarily verbose
and redundant:

DATA(lines) = split_text_into_lines( text = text ).

At times, however, a method name alone fails to help the reader understand what the
supplied argument means. In this case, adding the parameter name increases readabil-
ity, as in the following code:

car->drive( speed = 50 ).

128 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.4 Calling Methods

Use the Optional Parameter Name If It Adds Clarity


Make the code clear at the call site. Only use a named parameter when the label makes
the code easy to understand. Omit the named parameter when it’s redundant or add-
ing unnecessary noise to the code.

4.4.5 Self-Reference
In the code inside instance methods, the self-reference me is implicitly set by the system
and can be used to access the current class instance. This self-reference is equivalent to
the self-reference this in languages like Java and C#.
Picking up on the version of the log_exception method shown earlier in Listing 4.41, we
could implement the same code by explicitly using the self-reference me, as shown in
Listing 4.50.

METHOD log_exception.
CHECK exception_instance IS BOUND.
me->log_exception_header( exception_instance ).
me->log_exception_body( exception_instance ).
me->log_previous_exception( exception_instance->previous ).
ENDMETHOD.

Listing 4.50 Using the Self-Reference me to Call Instance Methods

Omit the Self-Reference When Not Needed


You should omit the self-reference me when calling instance methods or accessing
instance members of the containing class. Using me is redundant and doesn’t add any
clarity to the code.
However, if a parameter or a variable name shadows a class instance member, then
you’re forced to use me to disambiguate, for example, in the step_fan constructor
shown in Listing 4.51.
METHOD constructor.
super->constructor( ).
validate_step( step ).
me->step = step.
ENDMETHOD.
Listing 4.51 Using me to Assign to an Instance Member of the Class

Only use me when you need to reference a shadowed class instance member. Other-
wise, the self-reference is redundant and only adds clutter to your code.

Personal Copy for Andrei Arabolea, [email protected] 129


4 Methods

4.5 Summary
In this chapter, we covered some important guidelines on how to think about, design,
and use methods in clean ABAP code. Let’s recap the clean ABAP rules for methods we
covered.
We started with some guidance on object-oriented design, including the following rec-
ommendations:
쐍 Consider using instance methods by default.
쐍 Don’t call static methods through instance variables.
쐍 Public instance methods should be part of an interface.
쐍 Be careful when redefining methods.

Then, we took you on a grand tour of how parameters work and how to design them,
while exploring the following recommendations:
쐍 Aim for as few input parameters as possible.
쐍 Split the behavior instead of adding optional parameters.
쐍 Use a preferred parameter sparingly.
쐍 Create separate methods instead of using Boolean parameters.
쐍 Minimize the number of EXPORTING parameters.
쐍 Prefer RETURNING to EXPORTING.
쐍 Consider using result as the name of RETURNING parameters.
쐍 Use CHANGING parameters sparingly.
쐍 Avoid using more than one kind of output parameter.
쐍 Do not reassign IMPORTING parameters passed by value.
쐍 Mind the semantics of EXPORTING parameters, prefer pass-by-value.
쐍 Pass-by-value CHANGING parameters don’t make sense.

In Section 4.3, we delved into some important considerations when designing the body
within methods:
쐍 A method should do one thing, and only one thing.
쐍 Descend one level of abstraction.
쐍 Keep methods small.
쐍 Fail fast.
쐍 Focus on the happy path or error handling.

Finally, we ended this chapter with a discussion of several clean ABAP aphorisms
related to invoking methods:
쐍 Omit the optional EXPORTING keyword.

130 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


4.5 Summary

쐍 Omit the RECEIVING keyword.


쐍 Reserve CALL METHOD for dynamic method calls.
쐍 Use the optional parameter name if it adds clarity.
쐍 Omit the self-reference when not needed.

In the next chapter, we’ll look at the crucial topic of naming, and you’ll learn the impor-
tance of good names and how they help improve your ABAP code.

Personal Copy for Andrei Arabolea, [email protected] 131


© 2025 by Rheinwerk Publishing Inc., Boston (MA)
Chapter 5
Names
A good way to guide a crowd increasingly interested in clean ABAP is to
give concrete examples for what are considered proper names. What may
seem obvious to one developer can be a mystery to another developer.

While most clean code recommendations do not clash with currently established ABAP
guidelines, a frequent question from ABAP developers is how to proceed with names,
which is the most drastic difference from previous practice. In this chapter, we’ll pro-
vide recommendations for clean ABAP names that will better reveal your intention as
the developer, which in turn will make your code easier to understand and maintain.
While a naming convention may seem a burden to some developers, adhering to clear
naming conventions is key to the success of any project involving more than one
developer, which is almost always the case. A proper naming convention can help
ensure consistency across developers and teams, and most importantly in ABAP, a
proper naming convention prevents potential naming collisions, which we’ll discuss
later.
In this chapter, we’ll focus on helping you define proper names and describe numerous
best practices in Section 5.1. Then, in Section 5.2, we’ll shed light on ABAP’s peculiarities
and show you how to properly leverage affixes in Section 5.3. At the end, in Section 5.4,
we’ll make some recommendations on how to deal with names in legacy code.

5.1 Good Naming


In this section, we’ll define what a good name is, focusing particularly on the usage of
descriptive names, plural forms, abbreviations, and things to avoid.

5.1.1 Descriptive Names


A descriptive name not only conveys content and meaning but can be quickly under-
stood by anyone, even outsiders.
Some examples of meaningful names include the following:
쐍 CONSTANTS max_wait_time_in_seconds TYPE i.
The constant’s value is clearly defined as the maximum number of seconds to wait.

Personal Copy for Andrei Arabolea, [email protected] 133


5 Names

쐍 DATA customizing_entries TYPE my_customizing_type.


The variable clearly contains customizing entries.
쐍 CLASS /clean/user_preference_reader ...
The class contains logic to read user preferences.
쐍 METHODS read_user_preferences.
This method directly suggests that an invocation would retrieve the preferences for
a given user.

Avoid referring to data types or other technical details since doing so would prevent
readers from quickly grasping the semantic meaning of a statement.
The following examples illustrate how unsuitable names can cause trouble for readers:
쐍 CONSTANTS sysubrc_04 TYPE sysubrc ...
What is the meaning of the value 04? We can’t be sure.
쐍 DATA iso3166tab TYPE STANDARD TABLE ...
The word “iso3166tab” sounds quite technical, but what’s its semantic meaning? A
reader would first need to examine the data definition itself to understand its mean-
ing, thus losing his or her focus on the actual code being read.
쐍 METHODS read_t005 ...
This statement clearly reads some technical data labeled t005, but what’s that data
like, and what is it for?
쐍 CLASS /dirty/t005_reader ...
This statement seems to allow the reading of t005 data, but do we need this data? We
must look into the data definition to figure out that it’s actually meant to read coun-
tries.

Finally, avoid compensating for bad names with comments explaining your intent.
Instead, rename the identifier, and you won’t have to write those comments in the first
place. For more information about comments, refer to Chapter 9.

5.1.2 Domain Terms


Presenting the right intention in your names will help anyone working in the project.
As such, using terms from either the solution domain or the problem domain is pre-
ferred.
Although finding good names takes time, this task always pays off in the end by result-
ing in a less confusing codebase, smoother knowledge transfer, reduced risk of mis-
takes, and easier maintenance. Anyone who later encounters your code can become
productive more quickly.
While some good names may already be well established (i.e., computer science terms
like queue or tree or business field terms like account or ledger), you may have to come

134 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


5.1 Good Naming

up with something that has not been done before (for example, a draft activator).
Sometimes bouncing new names off your colleagues is an efficient way of finding the
sweet spot without overengineering a name.
Layers that are business-like will sound best when named according to the problem
domain. This recommendation is especially true for components that are designed
with domain-driven design, such as application programming interfaces (APIs) and
business objects. This helps give a clear intention of what the layer is about and avoids
the layer serving as a container for objects with other purposes.

Domain-Driven Design and the Ubiquitous Language


In his book Domain-Driven Design: Tackling Complexity in the Heart of Software, Eric
Evans uses the term “ubiquitous language” to refer to a language shared by everyone
in a project, such as the team, developers, domain experts, and other stakeholders.

Layers that provide mostly technical functionality, such as factory classes and abstract
algorithms, will sound best when named according to the solution domain. This allows
anyone with the minimum skill set to understand what’s involved, and also allows a
developer to search for artifacts with well-established names.
In any case, do not make up your own language. We need to be able to exchange infor-
mation between developers, product owners, partners, and customers, so choose
names that everyone can relate to without constantly referring to a homemade dictio-
nary.

5.1.3 Plural or Singular?


Using the plural or singular forms is a good indicator of the cardinality of the content,
without looking further into the code or even using the debugger.
While multiple cardinalities can be used in object models, distinguishing the plural
(0...n) and singular (1) forms in code identifiers is quite sufficient.
By having names such as both “countries” and “country,” a reader can understand the
semantic meaning without looking up the definition of the actual type. For example,
Listing 5.1 shows a clear relationship between both identifiers.

DATA countries TYPE something.


DATA country TYPE REF TO something_else.

LOOP AT countries REFERENCE INTO country.


" Do Something
ENDLOOP.

Listing 5.1 Singular and Plural Providing a Semantic Link between Identifiers

Personal Copy for Andrei Arabolea, [email protected] 135


5 Names

Our advice on singular versus plural primarily targets identifiers like variables and
properties. For development objects, competing patterns may also make sense, for
example, the widely used convention to name database tables “transparent tables” in
the singular form.

5.1.4 Abbreviations
With regard to abbreviations, being as verbose as possible is preferable. Therefore,
when enough space is available, write out full names. However, when reaching length
limitations, use abbreviations (or acronyms) with care, with the following recommen-
dations in mind:
쐍 Abbreviate unimportant words first
Abbreviating things may appear efficient at first; however, abbreviations can
quickly become ambiguous. Does cust mean customizing, customer, or custom? All
three are quite common within SAP applications.
쐍 Avoid using an existing abbreviation for something else
Anyone can come up with an abbreviation; however, people often overlook potential
overlaps with already established abbreviations. For example, does ATP mean after
tax profit, available to promise, or asynchronous terminal processing? These over-
laps can confuse everyone, especially readers with a wide range of expertise in differ-
ent domains. While avoiding collisions entirely might be impossible (googling any
abbreviation or acronym always brings plenty of different meanings), you should at
least try to avoid collisions within the system or application you’re developing.
쐍 Use the same abbreviations consistently everywhere
People will search for keywords to find relevant code for various goals, from trouble-
shooting, to refactoring, to pure knowledge boost. You can easily facilitate this
access by using the same abbreviation consistently everywhere. For example,
always abbreviate “marketing” as mktg, instead of using different variants based on
the space available such as mkt, marktng, and so on.

5.1.5 Naming Classes and Methods


In the object-oriented programming world, we either manage objects or perform
actions on those objects. Nouns and verbs directly support that distinction in any piece
of code. Using nouns and verbs consistently will help with code readability by exposing
the semantic meaning directly without having to refer to the declaration context.
Use nouns or noun phrases for classes, interfaces, and objects, as shown in the follow-
ing examples:
쐍 CLASS account
쐍 CLASS user_preferences
쐍 INTERFACE customizing_reader

136 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


5.1 Good Naming

Use verbs or verb phrases for methods, as shown in the following examples:
쐍 METHODS withdraw
쐍 METHODS add_message
쐍 METHODS read_entries

Boolean methods are easier to read when starting with verbs like is_ or has_:

IF is_empty( table ).

Functions should be treated like methods:

FUNCTION /clean/read_alerts

5.1.6 Noise Words


Noise words in text are like fillers in speeches (e.g., huh, um, well); they bring no value
and break the flow of the code and can hinder its understanding. Avoid terms that
bring no value such as data, info, object, or variable. For instance, account and alert are
usually sufficient, and you don’t need account_data or alert_object.
An alternative to completely eliminating those noise words is to replace them with
something specific that really adds value, such as in the following examples:
쐍 user_preferences instead of user_info
쐍 response_time_in_seconds instead of response_time_variable

5.1.7 Term Consistency


You’ve seen by now that consistency is a key ingredient in a clean codebase. Another
area where consistency can be applied is the choice of terms. While one could argue
that using synonyms does not change the semantic meaning, it still reduces code read-
ability and makes any refactoring potential less obvious.
Get, fetch, read, retrieve, or query suggest more or less the same idea, but switching
between these terms does not contribute in the larger scheme of things. Choose a term
for a concept and stick to it: Don’t mix in other synonyms. Synonyms will make readers
waste time trying to find a distinction that’s not there.
For instance, the following methods are preferable:

METHODS read_this.
METHODS read_that.
METHODS read_those.

While the following method names might cause the reader to question the meaning:

METHODS read_this.
METHODS retrieve_that.
METHODS query_those.

Personal Copy for Andrei Arabolea, [email protected] 137


5 Names

5.1.8 Design Patterns in Code


In your career, you’ll encounter plenty of well-established design patterns, such as the
singleton, factory, façade, composite, decorator, iterator, observer, and strategy design
patterns.
Use well-known pattern names only if you truly mean them; otherwise, you’ll create
expectations that can’t be met on the part of consumers or anyone else reading your
code. For example, don’t call your class file_factory unless this class really imple-
ments the factory design pattern.

5.1.9 Hungarian Notation and Prefixes


In computer programming, Hungarian notation is a convention for naming variables
and functions in a way that indicates its intention or kind, also known as its types.
Historically, Hungarian notation was helpful for code that was printed out on paper so
that readers could easily understand the intention of an identifier. However, with mod-
ern integrated development environments (IDEs) like ABAP Development Tools (ADT)
that offer a quick lookup, this intent or type encoding through prefixes no longer
brings value, and instead, type encoding creates noise in the code and reduces its read-
ability. You basically must ignore all those encodings if you want to grasp the overall
intent of the code itself. Developing ABAP in SAP GUI (for example, using Transaction
SE80) doesn’t offer the convenience of a quick lookup, which is why ADT is preferable.
As such, we recommend getting rid of all encoding prefixes, as in the following code:

METHOD add_two_numbers.
result = a + b.
ENDMETHOD.

This code is more readable than the following code:

METHOD add_two_numbers.
rv_result = iv_a + iv_b.
ENDMETHOD.

While we recommend you avoid Hungarian prefixes, not all prefixes should be
avoided, which we’ll cover in Section 5.3.

5.2 ABAP Peculiarities


ABAP has a long evolution history, and because it runs on a plethora of systems from
different ages, ABAP must remain backward compatible. This requirement comes with
some constraints that are generally taken for granted, which we’ll discuss in the follow-
ing sections.

138 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


5.2 ABAP Peculiarities

5.2.1 Group of Objects with Possible Name Collisions


The names you can give to objects in ABAP have different constraints. For example,
you can use 16 characters for the name of a database table and 30 characters for the
name of a class. As a result, coming up with more meaningful, self-explanatory names
can be quite difficult. Additionally, certain objects share the same naming pool. For
instance, you must be aware of three groups of objects with possible name collisions:
쐍 Data elements, structures, table types, views, and database tables
쐍 Elementary and collective search help
쐍 Classes and interfaces

As a result, a given name cannot be reused between a data element and a structure, or
between a class and an interface. However, objects from different groups could poten-
tially share the same name (class user and database table user), although this similarity
may not make much sense without being more specific. To cope with these constraints,
we’ll describe later in Section 5.3 how you can leverage affixes and keep names mean-
ingful.

5.2.2 snake_case versus camelCase


The ABAP language is case insensitive. Thus, the following code will run and won’t pro-
duce any syntax errors:

DATA counter type I VALUE 0.


COUNTER = CoUnTer + 1.

Any developer could reformat the code by adjusting the settings in the ABAP Pretty
Printer, as shown in Figure 5.1, putting everything in a different case, and this change
wouldn’t impact the functionality of the code. (For more information on the Pretty
Printer, see Chapter 10, Section 10.3.)

Figure 5.1 Example of the ABAP Pretty Printer Settings

Personal Copy for Andrei Arabolea, [email protected] 139


5 Names

Thus, using snake_case with ABAP, and avoiding camelCase, is important, except for
core data services (CDS) views, which follow SAP’s virtual data model naming conven-
tion (for more details, see http://s-prs.co/v519005). As shown in Table 5.1, the snake_
case identifier is readable even after the code has been reformatted.

Original Code Reformatted Code

request_processor REQUEST_PROCESSOR

requestProcessor REQUESTPROCESSOR

Table 5.1 Impact on Readability When Formatting ABAP Code

5.3 Affixes: Prefixes, Suffixes, and Infixes


Simply put, an affix is a word, abbreviation, or acronym used consistently in multiple
identifiers. Affixes can either be at the beginning (prefix), at the end (suffix), or some-
where in the middle (infix). Using affixes allows you to achieve the following goals:
쐍 Avoid naming collisions
쐍 Make code more readable
쐍 Ease searching
쐍 Bring attention to critical areas to developers

Although Hungarian prefixes should be avoided, affixes can sometimes be a necessity


or serve as assets. The following subsections describe the purposes of various affixes,
indicating which are absolutely necessary, which might be helpful to use, and which
don’t bring any value at all.

5.3.1 The Musts


In Section 5.2.1, we covered a few groups of objects that can be defined in ABAP with
names that can collide with other objects. Table 5.2 highlights the prefixes that are
absolutely necessary. While ABAP doesn’t force you to include these prefixes except
when using namespaces, these are critical to keep in mind in your code.
Some examples with the prefixes that must be used include the following:
쐍 ACMED_PRODUCT
A database table that contains products.
쐍 ACMEV_PRODUCTS
A view for accessing product information.
쐍 ACMET_PRODUCTS
A table type for holding information about several products.
쐍 ACMES_PRODUCT
The structure of a product, which provides access to all its fields.

140 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


5.3 Affixes: Prefixes, Suffixes, and Infixes

Convention Purpose

Prefix with the component name To avoid collisions in GTADIR, which is the standard
(e.g., ACME) SAP table that stores the global object directory.

Prefix with a single character in the To avoid collisions in the ABAP Data Dictionary
ABAP Data Dictionary object names (Transaction SE11) objects and to mitigate the limited
(e.g., ACMET_<object>): set of characters that can be used for specifying a
쐍 T for table meaningful name.
쐍 S for structure
쐍 V for view
쐍 D for data table
쐍 C for customizing table
쐍 I for system table

Table 5.2 Prefixes That Must Be Used

5.3.2 Helpful Affixes


While not required, several affixes are helpful because they increase clarity in the code,
put emphasis on the intent of each object, and clearly set the expectations for all con-
sumers. These recommendations are listed in Table 5.3.

Convention Purpose

Suffix package interfaces with either PIF, To make the scope (public, internal, or
IIF, or RIF restricted) of a package interface more explicit.
We’ll describe packages in more detail in Chap-
ter 13.

Suffix constant classes with _C To group them properly and to reserve charac-
ters for meaningful names (in contrast to _CON-
STANTS or _CONST, which consume more
characters).

Prefix class names with either TD_, TH_, For test doubles, test helpers, and test classes,
or TC_ respectively; thus, you should drop CL_ from all
other classes to save on those characters and
provide more meaningful names.

Prefix interfaces with IF_ To easily find and highlight interfaces.

Prefix exception classes with CX_ To easily find and highlight exceptions.

Prefix business add-in (BAdI) classes with To distinguish default/example/impl BAdI


either BD_, BX_, or BI_ classes.

Table 5.3 Helpful Affixes

Personal Copy for Andrei Arabolea, [email protected] 141


5 Names

Prefixes for Interfaces versus Classes


It may seem odd that we recommend including the prefix IF_ for interfaces and omit-
ting CL_ for classes. This is because there are always more classes than interfaces in a
system, and therefore sparing three characters for classes helps in giving them better
names as there are naturally more classes. Moreover, keeping IF_* makes it easier to
search for interfaces. As we want to put emphasis on interfaces, they have to be easily
searched for.

Some examples of how helpful affixes can be used include the following:
쐍 IF_MY_CLEAN_API
The interface that defines my clean API.
쐍 MY_CLEAN_API
The implementation class of my clean API.
쐍 MY_CLEAN_API_C
Constants we can use with my clean API.
쐍 TD_MY_CLEAN_API
A test double implementation to substitute my clean API.
쐍 CX_MY_CLEAN_API_IMPROPER_INPUT
An exception that my clean API can raise when receiving an improper input.

5.3.3 Affixes That Don’t Bring Value


We recommended avoiding Hungarian notation prefixes, but some additional affixes
also don’t bring value and should be avoided, such as the following:
쐍 Hungarian notation for variables, method parameters, or class attributes.
쐍 You may be tempted to use LC_ for local classes or local constants, but we recom-
mend you avoid doing so and instead rely on static checks for possible usage errors.
LC_doesn’t bring value and consumes three characters which could be used for more
meaningful names.
쐍 Any technical prefix (e.g., CM_) for a message class (defined in Transaction SE91).
These prefixes don’t bring value, and the name is reserved for message classes only.
쐍 ES or ESC for enhancement spot or composite enhancement spot names to quickly
distinguish their type.

5.4 Dealing with Legacy Code


When working on completely new code, embracing a new naming convention is easy.
However, developers are often asked to deal with legacy code. With regard to names,

142 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


5.5 Summary

while we make some recommendations in this section, you should discuss with your
team and decide how to proceed. No one-size-fits-all approach exists.
The following list contains some ideas, sorted from the most daring to the most conser-
vative approach:
1. Whenever something is changed, use the new naming convention and apply the Boy
Scout Rule (leave something in a better state than you found it). Leveraging the
refactoring power of ABAP Development Tools (ADT) is key because the rename
functionality can save you a lot of time and effort.
2. Apply the new naming convention to everything except the public methods.
3. Only apply the new naming convention to new methods.
4. Only apply the new naming convention to test includes.
5. Leave the legacy code as is and keep the former naming convention for any new
changes in it.

To avoid surprises, communication must be open with your team, and you also can
define conditions and thresholds for following one or more of the naming recommen-
dation we listed. An example of team agreement might involve the following rules:
쐍 If we fix a single line, we’ll stick to the former convention.
쐍 If we modify more than 80% of the code, we’ll apply clean names.

With critical objects, which may be used by other consumers like an API or a BAdI, you
must be particularly careful. Some rules might be strict and must be followed, or you
may risk incompatible changes.

5.5 Summary
Common sense is not so common, and every developer brings his or her knowledge
and unique background to a project. Thus, taking the time, sometimes going through
several iterations, to choose names wisely can make a huge difference between simple
and readable code that anyone can immediately jump in, troubleshoot, fix, or aug-
ment, versus complex and confusing code that requires a steep learning curve and
causes delays before developers can work with the code.
More specifically, this chapter covered the following topics:
쐍 Choose descriptive names.
쐍 Stick to domain terms.
쐍 Apply plural or singular according to what the identifier represents.
쐍 Abbreviate carefully and stick to the same abbreviation consistently.
쐍 Name classes with nouns and methods with verbs.
쐍 Avoid noise words that don't bring value.

Personal Copy for Andrei Arabolea, [email protected] 143


5 Names

쐍 Use terms consistently and avoid synonyms.


쐍 Use design pattern names only when they are really being used.
쐍 Avoid Hungarian prefixes.
쐍 Pay attention to possible name collisions.
쐍 Stick with the snake_case with ABAP.
쐍 Use affixes diligently.
쐍 Come up with a team agreement on how to deal with legacy code.

Now that we’ve covered choosing proper names, in the next chapter, we’ll explain how
these recommendations more concretely apply to variables and literals.

144 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Chapter 6
Variables and Literals
Variables make up a big part of every program’s code. One of the most
basic elements of code, variables are often used to store an intermediate
state, or sometimes a developer is forced to use them. This chapter
shows that variables should be used consciously, and sometimes more is
more in that additional variables can bring additional understanding in
the code.

Software written in ABAP is built with lots of programs, modules-pools, and function
modules. One thing that all these elements have in common is that a list of variables is
declared upfront, or even fill an include that only contains declarations. On one hand,
the declaration of variables can give the reader a first impression about the scope of the
code. On the other hand, often so many declarations are listed at the start that under-
standing what the actual scope is can be difficult, even overwhelming. In this chapter,
we’ll show you how even the declaration of variables has an impact on your code and
can make your code better.
To start, in Section 6.1, we’ll introduce you to the concept of variables and guide you on
how and where variables are declared. You’ll need to consider several important details
even when simply declaring a variable. Then, in Section 6.2, we’ll elaborate on con-
stants, especially in combination with a central place to store constants for maximum
reusability. The following sections will cover special types of variables: strings in Sec-
tion 6.3 and Booleans in Section 6.4. Both types have special behaviors regarding clean
code that must be considered. Section 6.5 introduces you to regular expressions and
their usage in clean code. Regular expressions are a powerful tool to make certain
checks and comparisons. Finally, in Section 6.6, we’ll cover the operator REDUCE, which
is also powerful and should be handled with caution in a clean code environment.

6.1 Variables
In programming languages, variables are a key component to store intermediate
results for further reference. ABAP offers several ways to declare and work with vari-
ables. In this section, we’ll provide insights on how to cleanly work with variables.

Personal Copy for Andrei Arabolea, [email protected] 145


6 Variables and Literals

6.1.1 Declaring Variables


One of the most basic elements of every programming language is the usage of vari-
ables. The differences between languages can be significant, but still all programming
languages rely on variables.
Over decades, ABAP was written in such a way that variables were declared at the begin-
ning of a piece of code to serve as an overview of all the necessary variables, as shown
in Listing 6.1. A long list of predefined variables is an indication that a lot of code will
follow. Understanding a long function that needs a long list of declarations is already a
hard task. These predefined variables, declared in the beginning, make this task even
harder because the reader must remember how a variable was declared or must go back
to the declaration again. Of course, forward navigation (moving the screen to the dec-
laration of the variable) makes jumping around easier, but the flow of reading is bro-
ken, and the function is harder to understand.
When written with clean code principles in mind, a function would most likely be short
but also the variables would make it easier to understand. However, even in a smaller
function, variables are necessary and must be declared.

DATA: lt_item type standard table of ty_item,


lv_sold_to type sold_to_id,
lv_county type i,
ls_logical_control type tcs_control,
ls_item type ty_item.

Listing 6.1 Declarations with Data Chaining

Whenever possible, the declaration of a variable should be done in a line with the DATA
keyword. With ABAP 7.40, the DATA keyword was enhanced to support inline declara-
tions with automatic type derivation. In the following example, declaring the messages
variable upfront would not add value:

DATA(messages) = container->get_messages( ).

The type is derived from the RETURNING parameter of the get_messages method, which
reduces complexity for the reader, who doesn’t need to know the exact type to under-
stand that the result will contain messages.
Inline declarations should be leveraged whenever possible. Some methods or func-
tions use EXPORTING parameters, which are more flexible since these methods/func-
tions can be typed generically. In these cases, the type cannot be derived but rather is a
prerequisite for the method. Other examples are the exporting and tables parameters
of function modules, but these elements should be not be called directly. For better
testability, these elements should be only be used when wrapped around a class-based

146 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


6.1 Variables

interface. Furthermore, in class-based method calls, the inline declaration is available


as described.
This simplification is not only introduced for the DATA keyword but also for field sym-
bols and references. For reference, in the following code, the DATA keyword is reused
and the addition of REFERENCE INTO specifies that the resulting variable should be a ref-
erence:

READ TABLE items REFERENCE INTO data(item) index 1.

Not only can declarations be the results of operations, they can also be used for direct
assignments. As shown in Listing 6.2, the operator VALUE combines these two aspects
into a single statement.

DATA(items) = VALUE order_items( ( id = '10'


product = '348951'
quantity = 10 ) ).

Listing 6.2 Working with VALUE

The result of this statement is an internal table of type reduced_items, which has a sin-
gle entry with the values assigned in the code. Often this approach is used for preparing
data for unit tests. In productive code, the resulting variable should only be used once,
as shown in Listing 6.3. Then, the declaration is done anonymously, and the result is
passed to the calling statement.

INSERT VALUE #( product = '348951'


quantity = 10 ) INTO TABLE items.

Listing 6.3 Anonymous Variable for Inserts

The statement shown in Listing 6.3 creates a variable of the line type of the table items,
and the values for two fields of the structure are filled. In this example, the values are
static, but in the same way, a mapping can be made for fields that do not share the same
name. But move-corresponding requires the same name to work, since fields are moved
based on the name from the source to the target. Thus, if the name differs, the corre-
sponding move is not working. Especially since this variable for the line is not needed
afterwards, the anonymous variant is cleaner since the code is bloated with a variable
that is only used once.
The example shown in Listing 6.3 also introduces the hash operator, which enables
type inference. You can use this operator with several inline operators like VALUE, FIL-
TER, and REDUCE. The resulting type is derived by the context of the statement. In our
example, the type can be derived from the table in combination with the INSERT state-
ment to take the line type of the table. Using the hash operator can also increase read-
ability and make the code less complex if no additional value provides the type.

Personal Copy for Andrei Arabolea, [email protected] 147


6 Variables and Literals

Sometimes, when working with tables, you’ll want to work with a table that is only a
subset of the current table at hand. To create a new table containing only some entries
from the original table, the keyword FILTER can be utilized. When the following state-
ment is executed, for example, the resulting new table that is returned contains entries
that match the given criteria:

DATA(buffered_partners) = FILTER #( it_partner except in partner_


store where ID = ID ).

This statement creates a new table that only contains partner information that is not
already available in the partner_store and therefore reduces the data entries that fur-
ther must be handled. With the FILTER operation, code can be cleaner because one spe-
cific statement creates the set of relevant entries. All following statements can be sure
that only the relevant entries are available. With this feature, the number of checks can
be reduced in subsequent loops, checks, or ifs.
However, if the purpose of the code is to manipulate data in an internal table, FILTER
might not be the correct approach. The table behaves like a local copy with a different
set of entries. To manipulate entries in an internal table, you should work with the
WHERE addition to the LOOP statement.

6.1.2 Branches and Scope


In the previous section, we explained why an upfront declaration should be avoided in
clean code if possible. In some cases, you might technically be able to declare a variable
inline, but following this path is still not recommended. Listing 6.4 shows an example
of an inline declaration that is accepted by the compiler. During runtime, the code is
correctly executed.

IF messages->has_entries = abap_true.
DATA(success) = abap_true.
ELSE.
success = abap_false.
ENDIF.

Listing 6.4 Inline Declaration in Optional Branch

The next example, shown in Listing 6.5, is short and easy to understand by reading the
code from top to bottom. As you read, you’ll first see the declaration of the variable
success and, after that, read the ELSE branch.
In more complex cases, for instance, in a debugging session, you might directly run
into the ELSE branch and come across the assignment. Most likely, after a short time,
what this line does will be clear, and perhaps it could be deleted after all.

148 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


6.1 Variables

DATA success type abap_bool.


IF messages->has_entries = abap_true.
success = abap_true.
ELSE.
success = abap_false.
ENDIF.

Listing 6.5 Conscious Upfront Declaration

The point is that understanding the assignment takes more time just because the flow
of reading is broken during complex cases (for example, a debugging session), which
makes the code also harder to understand. In this case, an upfront declaration supports
the reader’s understanding and improves the code.
In contrast to some other programming languages, declarations in ABAP are not bound
to the scope in which they are declared. In other languages, a variable declared within
an IF branch may only be known within this branch, but not outside it.
On one hand, this behavior in ABAP has advantages whenever the variable should carry
information outside of its branch. In this case, you can still declare the variable at its
first use.
On the other hand, this behavior often has unwanted side effects, such as the variable
having a different state than expected. As an example, if a looping variable is used after
its loop, special cases must be taken care of. One of these special cases is a loop over an
empty internal table. In this case, the variable might be empty. Another example is
when a field symbol is used that had not been assigned; a runtime error will occur.
To avoid unintended side effects, variables should be declared at the same position as
in other languages where the scope in which the variable is known is more restrictive.
For instance, in Java, the variables declared within a loop are not known outside the
loop. The risk of creating bugs based on these side effects can be reduced by directly
informing the reader that the variable will also be used after the branch. Any upfront
declaration in a clean code environment will be recognized as intentional and supports
a better understanding of the code.

6.1.3 Declaration Chaining


As mentioned in the beginning of this chapter, often upfront declarations will play a
big part in your code. Although not recommended with clean ABAP principles, having
an area for upfront declarations might still make sense. In these cases, either upfront
declaration is a part of legacy coding that cannot yet be refactored, or the declarations
cannot be performed inline. In the latter case, spreading variable declarations through
the code would make the code harder to read and understand.

Personal Copy for Andrei Arabolea, [email protected] 149


6 Variables and Literals

If you need to declare several variables at one place, ABAP allows the chaining of the
keyword DATA. Chaining means that the keyword is followed by a colon and must not be
repeated for every variable, as shown earlier in Listing 6.1. The different variables are
divided by commas. This approach reduces the number of keywords, but still can be
jumped to separately if written on different lines.
Experienced developers have seen declaration chaining a lot, perhaps in most func-
tions. However, only a little value is gained from this chaining approach. With code
completion capabilities and large monitors, sparing four characters in a line for a dec-
laration doesn’t seem necessary. In contrast, adding these four characters has a posi-
tive impact on understanding the code. Chained declarations may give the incorrect
impression that they belong together, in their usage within the code. If this impression
is not true, we recommend declaring the variables separately to emphasize their inde-
pendence. Of course, if the variables have a strong relationship (e.g., like the declaration
of an internal table and its corresponding line types), the declaration can be chained to
underline their bond. But these cases are rather rare, and therefore, we recommend not
chaining up declarations, as shown in Listing 6.6.

DATA lt_item TYPE standard table of ty_item.


DATA lv_sold_to TYPE sold_to_id.
DATA lv_counter TYPE i.
DATA ls_logical_control TYPE tcs_control.
DATA ls_item tTYPE ty_item.

Listing 6.6 Declarations without Data Chaining

The same reasoning also applies to the alignment of the TYPE keyword following DATA as
applied in Listing 6.6. This also breaks the bond between variables that have no connec-
tion.
Following the exception mentioned earlier, our variable declarations can be improved
even further. The one exception that is given for chaining is that variables that belong
together can be chained together and formatted accordingly, which is shown in Listing
6.7.

DATA: ls_item TYPE ty_item,


lt_item TYPE standard table of ty_item.
DATA lv_sold_to TYPE sold_to_id.
DATA lv_counter TYPE i.
DATA ls_logical_control TYPE tcs_control.

Listing 6.7 Declarations with Proper Chaining

If these principles are applied consciously, the readability of your code can be signifi-
cantly improved in the ways that only seem minor.

150 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


6.1 Variables

6.1.4 Looping Variables


In ABAP, a lot of coding works with internal tables and their modification. A common
pattern is to first declare the table itself followed by a structure representing a single
entry of the table. This is the LOOP INTO option. The actual code could look like Listing 6.8.

DATA messages TYPE tct_message.


DATA message TYPE tcs_message.
DATA highest_severity TYPE severity.

messages = container->get_messages( ).
LOOP AT messages INTO message.
IF highest_severity > message-severity.
highest_severity = message-severity.
ENDIF.
ENDLOOP.

Listing 6.8 Classical LOOP INTO Pattern

Alternatives for a looping variable are REFERENCE INTO and ASSIGNING to define the type of
the creating variable. All these options can be used also for inline declarations if the
table has a static type. An inline declaration for importing variables of type ANY or field
symbols of type ANY TABLE will not be possible.
The resulting code in Listing 6.9 shows that the reduction in lines of code improves
readability. Since the variables are introduced only when necessary, the complexity of
the code is built up step by step and is thus easier to understand. It is not important to
introduce the variable message directly at the beginning of the code nor is it important
to state what exact technical type a message has. Knowing that the message severity is
included in the structure is enough for understanding the code and also reduces the
code.

DATA highest_severity TYPE severity.

DATA(messages_from_order_processing) = order_processor->get_messages( ).
LOOP AT messages_from_order_processing ASSIGNING FIELD-SYMBOL(<message_from_
order_processing>).
IF highest_severity > <message_from_order_processing>-severity.
highest_severity = <message_from_order_processing>-severity.
ENDIF.
ENDLOOP.

Listing 6.9 Inline Loop into Assigning

The alternative to using an ASSINGNING loop variable is to use the same approach but
with a REFERENCE INTO variable. Technically, the difference is that the variable only

Personal Copy for Andrei Arabolea, [email protected] 151


6 Variables and Literals

contains a pointer to the original values. This means that if the values within the ref-
erence are changed, the source values are also changed. This can be used elegantly
when the original table should be altered. When adapted to the REFERNCE INTO version,
the code changes to the variant shown in Listing 6.10.

DATA highest_severity TYPE severity.

DATA(messages_from_order_processing) = order_processor->get_messages( ).
LOOP AT messages_from_order_processing
REFERENCE INTO DATA(message_from_order_processing).
IF highest_severity > message_from_order_processing->severity.
highest_severity = message_from_order_processing->severity.
ENDIF.
ENDLOOP.

Listing 6.10 Inline Loop REFERENCE INTO

As we’ve discussed, several options are available for defining the looping variable. If
you have no reason to use a local copy of the entry, the LOOP INTO option should be
avoided. Working with ASSIGNING should be the default when looping.

Field Symbol versus Reference


Field symbols and references have similar impacts on readability, but a field symbol
has advantages in terms of execution performance and therefore is recommended.

Every option we’ve mentioned so far has value and can be discussed. But, if not techni-
cally required, the looping variable should always be declared inline to reflect a logical
bond that does exist. Additionally, the name should be similar with the table and the
table line, but still precise enough that these elements are not confused.

6.2 Constants
A constant is like a variable but without the possibility to be changed. A constant holds
a value and can be referenced multiple times but is immutable. Several reasons for
working with constants exist, which are easy to consume in different locations. In the
following sections, we’ll describe how constant values can be handled to clean up your
code.

6.2.1 Proper Use of Constants


Programs and functions often have constant values that represent different things:
Some constants are status values, type differentiations, or even expressions like +1. The

152 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


6.2 Constants

last example is referred to as a magic number, which somehow adds or subtracts one
from a value. Constants are not numbers; they are single characters that represent
something specific. For instance, different status values can be seen as magic numbers
that cannot be understood without deeper knowledge. A software engineer familiar
with magic numbers will still need to understand the context to see why the number is
necessary. When writing clean code, magic numbers should not be directly used.
Instead, magic numbers should be hidden behind a constant. Also, the other examples
like status values, message values, and domain values should be represented by con-
stants.
Using constants instead of literals has more than one advantage. Magic numbers are
hard to understand. A +1 is rather simple to understand because often an index starts
with the value 1 but a count starts with 0. To fetch the correct entry, the count must be
increased by 1. Character values for differentiation are harder to understand because
the reader doesn’t know exactly what the exact character means and whether the char-
acter falls within the range of expected values.
Hidden behind a constant, the actual character is not exposed, and the reader can be
provided with more details by the naming of the constant. Applied to the example
shown in Listing 6.11, you’ll see that the comparison component->type = 'E' hides what
the character E represents.

IF component->type = 'E'.
ASSIGN COMPONENT component->name OF STRUCTURE structure to FIELD-
SYMBOL(<value>).
ELSEIF component->type = 'S'.
“Do something else.
ENDIF.

Listing 6.11 Anti-Pattern: Magic Numbers in Decisions

Preferably, a constant is extracted into its own development object that can be reused
everywhere the magic number is used. Its own development object enables a high
degree of reusability and abstraction from the actual character value since the value
itself is not important for the consumer. In Listing 6.12, the class cl_component_type has
static attributes representing the different types. However, this goal could also be
achieved with a local constant within the function or program.

IF component->type = cl_component_type=>element.
ASSIGN COMPONENT component->name OF structure STRUCTURE to FIELD-
SYMBOL(<value>).
ELSEIF component->type = cl_component_type=>structure.
“Do something else.
ENDIF.

Listing 6.12 Hiding Magic Numbers behind Constants

Personal Copy for Andrei Arabolea, [email protected] 153


6 Variables and Literals

A common approach for managing constants is adding them as constants to an inter-


face. All the requirements we’ve mentioned in this section can be fulfilled with an
interface. The actual values can be hidden behind named constants that provide con-
text, and the constants can be reused by referencing the interface. This alternative
object is called an enumeration class, which we’ll discuss next. Interfaces are used on
clean code projects to define the external view of an object, can serve as the main link-
ages between functions, and can represent dependencies. However, for managing con-
stants, enumeration classes are superior.

6.2.2 Enumeration Classes


The interface-based approach has several key differences from class-based constant
handling. The main advantages of using a class-based design is the ability to offer addi-
tional capabilities to the consumer or to reduce risks. Besides missing functionalities,
interfaces can be misused by implementing them in a consuming class. The implemen-
tation of an interface in a class does reduce the length of the code since the interface
name can be skipped as a prefix to the constant name. However, this shorthand
obscures the fact that the constant is defined outside of the class at hand, which might
be confusing to readers and can become an obstacle to understanding the class.
The added value of using enumeration classes is that an enumeration class can hold
additional methods as a convenience for consumers while still maintaining good sepa-
ration of concerns. In an enumeration class for our message severity example
described earlier in Section 6.1.4, the method is_more_severe_than can be added to com-
pare two severities. Since this method is implemented in the same class, the method
can be directly enhanced once a new value is introduced without forcing all users of the
class to adapt their coding based on the new value.
Another advantage of using an enumeration class is the ability to implement unit tests
for this class. Of course, the methods added to the class will need unit tests for func-
tional testing, but the constant itself can also be tested. As ABAP Data Dictionary
domains are not well integrated into the object-oriented world, many classes are cre-
ated to have the domain’s fixed values as constants and can be referenced the same
using a well-defined, clean name. Unfortunately, the domain and the constants are not
synched. If one of these elements is changed, the corresponding object must be
changed as well. A unit test on an enumeration class can ensure that, for all constants,
a corresponding value is defined in the domain and that all the domain’s fixed values
are represented as constants. If two development objects are not in sync anymore, the
unit test will fail and point to the forgotten step.
The ABAP keyword ENUM is a particularly good alternative if a set of constants are only
relevant in a single class and do not have a corresponding domain. In this case, the data
type generated by the keyword can be used throughout the class, and type safety can be
ensured. If an enumeration class has a corresponding ABAP Data Dictionary type, the

154 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


6.2 Constants

keyword might only create confusion because the statement always generates the cor-
responding data type. Now, you would have two conflicting data types, which could
cause errors.

Enumeration Classes
Even though ABAP offers the ENUM keyword for enumerations, we didn’t use this key-
word for our enumeration classes in this chapter. Our example classes try to create an
enumeration class-like behavior by construction but are not technically enumeration
classes.

One major advantage of enumeration in other programming languages is type safety.


Only values that are defined can be passed to a parameter. To achieve the same type
safety in ABAP, a few more steps are necessary, and you cannot simply define a class
and store constants in that class. Even though additional possibilities like good names
and unit tests can be used, the compiler cannot check whether a passed value is logi-
cally compatible.
In ABAP, two different patterns are available for implementing an enumeration class:
the constant pattern and the object pattern. The interface pattern and the collection
pattern both rely on interfaces and are therefore inferior and only described briefly in
this chapter.
The constant pattern, shown in Listing 6.13, is a class with the addition ABSTRACT FINAL
so that no subclass can be created to enhance its scope nor can instances of the class be
instantiated. An instance of the class is not desirable since creating an instance yields
no benefits. Creating an instance always takes some time, which can be saved. In this
case, the class is not used in an object-oriented way but instead serves as a container to
hold constants that are addressed statically. Therefore, no instances are necessary. The
addition FINAL is important so that no subclass can change the behavior or add addi-
tional values.

CLASS message_severity DEFINITION PUBLIC ABSTRACT FINAL.


PUBLIC SECTION.
CONSTANTS:
BEGIN OF message_severity,
warning type symsgty value 'W',
error type symsgty value 'E',
END OF message_severity.
ENDCLASS.

CLASS message_severity IMPLEMENTATION.


ENDCLASS.

Listing 6.13 Enumeration Class in Constant Pattern

Personal Copy for Andrei Arabolea, [email protected] 155


6 Variables and Literals

As mentioned earlier, one big advantage of using an enumeration class over a constant
interface is the ability to ensure, with a unit test, that the domain’s fixed values are in
sync with the values available in the constant structure. Listing 6.14 shows the local
unit test class for our message_severity enumeration class. The approach is only depen-
dent on the constant structure of the class that is tested and the name of the ABAP Data
Dictionary domain.

CLASS ltcl_constant_pattern DEFINITION CREATE PRIVATE


FOR TESTING RISK LEVEL HARMLESS DURATION SHORT.
PRIVATE SECTION.
DATA domain_values TYPE STANDARD TABLE OF dd07v.
DATA components_of_const_structure
TYPE cl_abap_structdescr=>component_table.
METHODS setup.
METHODS all_values_as_constant FOR TESTING.
METHODS all_constants_in_domain FOR TESTING.
ENDCLASS.

CLASS ltcl_constant_pattern IMPLEMENTATION.


METHOD setup.
CALL FUNCTION 'DD_DOMVALUES_GET'
EXPORTING
domname = 'MESSAGE_SEVERITY'
TABLES
dd07v_tab = domain_values
EXCEPTIONS
OTHERS = 1.

ASSERT sy-subrc = 0.

DATA: lo_struct_descr TYPE REF TO cl_abap_structdescr.


lo_struct_descr ?= cl_abap_TYPEdescr=>describe_by_DATA(
message_severity=>message_severity ).
components_of_const_structure =
lo_struct_descr->get_components( ).
ENDMETHOD.

METHOD all_constants_in_domain.
LOOP AT components_of_const_structure INTO DATA(component).
ASSIGN COMPONENT component-name
OF STRUCTURE message_severity=>message_severity
TO field-symbol(<value>).
ASSERT sy-subrc = 0.
IF NOT line_exists( domain_values[ domvalue_l = <value> ] ).

156 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


6.2 Constants

cl_abap_unit_assert=>fail(
|Component { component-name } not found in domain fix values|).
ENDIF.
ENDLOOP.
ENDMETHOD.

METHOD all_values_as_constant.
DATA value_found TYPE abap_bool.
LOOP AT domain_values INTO DATA(domain_value).
CLEAR value_found.
LOOP AT components_of_const_structure INTO DATA(component).
ASSIGN COMPONENT component-name
OF STRUCTURE message_severity=>message_severity
TO field-symbol(<value>).
ASSERT sy-subrc = 0.
IF domain_value-domvalue_l = <value>.
value_found = abap_true.
EXIT.
ENDIF.
ENDLOOP.
IF value_found = abap_false.
cl_abap_unit_assert=>fail( |Domainvalue
{ domain_value-domvalue_l } not available
as constant| ).
ENDIF.
ENDLOOP.
ENDMETHOD.

ENDCLASS.

Listing 6.14 Unit Test for Constant Pattern Enumeration Class

The object pattern is defined as a class with the addition CREATE PRIVATE FINAL, which
ensures that only within the class can instances be created. The values for the constants
are not defined as constants but rather as read-only CLASS-DATA, typed as a reference to
the same class. During class construction, instances are created for every constant
value that should be exposed. Thus, the parameter can be typed as reference to the
class, and only those instances that were created during class construction can be
passed during runtime. Overall, with this construct, even without a real enumeration
class, type safety can be ensured, and the code can be made more robust.
Additionally, once the class is written, the usage of an interface, constant, or object pat-
tern is identical. The reader of the code is not forced to differentiate between patterns,
which are all read the same since the values are always addressed statically. An example
for the object pattern in the context of message severity is shown in Listing 6.15.

Personal Copy for Andrei Arabolea, [email protected] 157


6 Variables and Literals

CLASS message_severity DEFINITION PUBLIC CREATE PRIVATE FINAL.


PUBLIC SECTION.
CLASS-DATA:
warning TYPE REF TO message_severity READ-ONLY,
error TYPE REF TO message_severity READ-ONLY.

DATA value TYPE symsgty READ-ONLY.

CLASS-METHODS class_constructor.
METHODS constructor IMPORTING severity TYPE severity.
ENDCLASS.

CLASS message_severity IMPLEMENTATION.


METHOD class_constructor.
warning = NEW message_severity( 'W' ).
error = NEW message_severity( 'E' ).
ENDMETHOD.

METHOD constructor.
me->value = value.
ENDMETHOD.
ENDCLASS.

Listing 6.15 Enumeration Class in Object Pattern

Both patterns allow you to enhance the class with additional functionality. This func-
tionality is not only for convenience, but it also breaks dependencies. How basic meth-
ods like to_string, is_valid, contains, and equals are implemented should be specified
by the enumeration class itself, not by every consumer again. In addition to bugs that
might be introduced in the consumers’ code, the consuming code must be adapted if
the set of available values is changed. If the class handles more than one enumeration,
that is, is a collection implementing these methods, understanding the code would be
difficult since different types must be handled or different methods must be intro-
duced, which ignores the guideline to separate development objects.
Unfortunately, code is often written under time pressure, and thus, the described
object pattern may seem to generate a lot of overhead compared to the interface and
constant pattern, especially compared to the collection pattern where different types
of constants are bundled into one development object. Clean code strives for optimiza-
tion of understandability and readability, not in reducing the time required to write
code. Additionally, the effort of writing this type of enumeration class can reduce later
maintenance effort because of its enhanced type safety and unit tests.

158 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


6.2 Constants

6.2.3 Constant Grouping


In some environments, you might not be able to or want to create a discrete develop-
ment object for every type that is used. In many environments, legacy constant inter-
faces were created based on a business context and not on the technical layer.
Whatever the situation in a given environment, using constants will improve your code
significantly. Another significant improvement can be achieved even when constants
are already in use. A pattern common in constant interfaces or classes that have grown
over time is that there is a long list of constants. Even though all the constants are used,
the reader will find it hard to get an overview and the context of their values, as shown
in Listing 6.16.

INTERFACE if_message_constants PUBLIC.


CONSTANT warning TYPE msgty VALUE ‘W’.
CONSTANT error TYPE msgty VALUE ‘E’.
CONSTANT save_failed TYPE msgid VALUE ‘006’.
CONSTANT save_successfull TYPE msgno VALUE ‘001’.
CONSTANT system TYPE msgty VALUE ‘S’.
ENDINTERFACE.

Listing 6.16 Anti-Pattern: Ungrouped Constant Interface

Setting aside the recommendation to use abstract classes instead of interfaces, room for
improvement still exists. The constants are missing from the structure, and thus what
the constant is used for is hard to understand, even though additional information is
available from the name and the type. Using constant groupings, like nested type defini-
tions, can give your constants a structure and enable clear guidance.
Introducing a constant grouping for the interface gives more context to your constants
if the name for the group is well defined. In the example shown in Listing 6.17, the group
name severity directly gives the constants more meaning. Additionally, when refer-
enced, the group name is visible, and every reference benefits from the context.

INTERFACE if_message_constants PUBLIC.


CONSTANTS:
BEGIN OF severity,
warning TYPE msgty VALUE ‘W’.
error TYPE msgty VALUE ‘E’.
system TYPE msgty VALUE ‘S’.
END OF severity.
CONSTANTS:
BEGIN OF msgid_v1,
save_successfull TYPE msgno VALUE ‘001’.
save_failed TYPE msgno VALUE ‘006’.
END OF msgid_v1.
ENDINTERFACE.

Listing 6.17 Structured Constants in Interface

Personal Copy for Andrei Arabolea, [email protected] 159


6 Variables and Literals

Moreover, a reader’s understanding of the interface is increased since now the reader
can clearly see that two types of constants are currently maintained. One group of con-
stants defines message severity, and the other group contains message numbers from
the message class V1. Overall, even if you can only group together constants (without
redesigning the scope of the objects themselves), the maintainability and usage of your
code can be improved significantly.

6.3 Strings
In the previous section, we described the handling of constant values in the context of
clean code. Another constant value in programming code are strings that are con-
structed in the source code itself. Two types of strings exist: one is constant (that is,
defines a specific situation in a logger) and the other constant is constructed. We’ll
describe dynamic constructions of strings in Section 6.3.2, but first, let’s look at string
literals.

6.3.1 String Literals


Having a string containing text in code can be useful but should not be used for texts
presented to end users. For these texts, better solutions involve messages that offer
translation options that are not available for source code. For analysis purposes, mes-
sages can be added to a logger, as shown in Listing 6.18. These texts could be technically
relevant for translation, but the potential user group might be quite limited (likely only
those familiar with the code), so this type of text can be handled directly within the
source code.
Strings are often declared in the way shown in Listing 6.18.

"Anti-Pattern
DATA logger_message TYPE string.
logger_string = 'Log: Some Text'.

Listing 6.18 Anti-Pattern: String Declaration with Conversion

As discussed earlier, these types of declarations can and should be compressed into a
single line. However, in this example, the inline declaration might cause trouble since
technically the single quotation mark (') defines a character type and not a string. If the
declaration is compressed into one line as follows, the result would be a character type,
which could lead to type conflicts or unnecessary conversions:

DATA(logger_message) = ' Log: Some Text'. "Type is char

In this example, for a static text that we intend to use in a logger, two ways exist for
declaring the string. If the string won’t be changed, nor has a variable part, the string
can be declared as a constant to emphasize its static intention, as follows:

160 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


6.4 Booleans

CONSTANT logger_message TYPE string VALUE 'Log: Some Text'.


DATA(logger_message) = 'Log: Some Text'. "Type -> String.

Otherwise, if not be declared as a constant, the string can still be declared inline using
backticks (``). A string between two pipes (|Log: Some Text|) will also produce a variable
of type string. However, this case is an anti-pattern because the string would be pro-
duced only during runtime because variable parts are intended, which generally has
terrible execution performance and should be avoided when possible.
Both approaches can declare a string in a single line when the actual content is fixed
and not variable.

6.3.2 String Building


In the previous section, we concentrated on the declaration of static strings. However,
strings often consist of a static part and parts that are based on variable values. A well-
known use case is message raising. The message itself is stable, but situational details
must be added to increase the value of the message. As recommended earlier, strings
are declared inline with backticks (``). This declaration of strings has an addition that
allows you to concatenate variable values to the beginning or to the end of the string.
With the operator &&, a variable value will be concatenated with the previous and/or fol-
lowing string. For this case, the strings must be closed with a matching backtick. The
following statement is syntactically correct and brings the intended result of a com-
bined string; however, the code is not easy to read and obscures where the string ends
and the variable begins:

DATA(message) = `Received an unexpected HTTP ` && status_code &&


` with message ` && text.

Using the string interpolation with the pipe operators (||) increases readability.
Between the two pipes, everything is the text unless escaped by curly brackets ({}). This
style emphasizes readability and clear structure, as follows:

DATA(message) = |Received HTTP code { status_code } with message { text }|.

Additionally, pipes can also be used inline, such as the text that should be printed if a
unit test assertion fails. In these cases, you don’t need to create a new variable contain-
ing the result because the result can be assembled anonymously as a string.

6.4 Booleans
Boolean values are one of the most basic parts of a programming language as they rep-
resent a simple two-state value. Many conditions are based on this logic to make deci-
sions about executing code or not. Even though these values seem so commonplace,
several details must be considered, which we’ll discuss in this section.

Personal Copy for Andrei Arabolea, [email protected] 161


6 Variables and Literals

6.4.1 When to Use Booleans


Unlike practically all other programming languages, ABAP lacks a built-in Boolean type.
To overcome this issue, the commonly used type abap_bool was introduced, which is
technically only a character field with a length of 1. The type is accompanied by three
constant values that can be used for easier usage, even though comparisons with all
char1 fields are possible.
Technically, the type is a simple char1 field that can carry all different character values.
In other programming languages, an actual Boolean type has type safety to only con-
tain two values. In ABAP, all characters can be stored in an abap_bool type. Additionally,
not only the type abap_bool was added to the pool, but also the following constants for
three states were created:
쐍 abap_false
Is represented by a space, which is also the initial value for a char1 field. Thus, if a
variable is not initialized, this value will be false.
쐍 abap_true
The true value is represented by an uppercase X.
쐍 abap_undefined
The type abap_bool, which is atypical for Boolean types, offers a third option to
explicitly mention an unknown state. The value of the constant is ‘-‘.

Providing the third option has its advantages when an explicit false value needs to be
maintained rather than being the initial state. Only in a limited problem area will using
the unknown state make any sense at all. One of these problems is shown in Listing
6.19. A method should only be executed in case the functionality is enabled. If the
enablement check is costly in itself, it does make sense to only determine it once. In
this case, abap_undefined can be utilized to only determine once and store the result in
the variable function_is_enabled.

METHOD regularly_called.
IF function_is_enabled = abap_undefined.
function_is_enabled = is_function_enabled( ).
ENDIF.
IF function_is_enabled = abap_true.
do_something( ).
ENDIF.
ENDMETHOD.

Listing 6.19 Usage of abap_undefined

This construct only works if the variable was previously set to the abap_undefined value.
Since this step must be coded, erroneous behavior may arise in cases when the instance
is reused when not intended or not reused when it should be.

162 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


6.4 Booleans

Don’t Use abap_undefined


The use of abap_undefined is not trivial, as the initial value of a Boolean variable is a
space, which is also the term for false. The undefined state must be explicitly set once
and reset when necessary. Additionally, having an undefined value is highly uncom-
mon. To improve understanding of the code, it’s discouraged to use abap_undefined in
clean ABAP.

In contrast, the constants abap_true and abap_false should be used whenever possible,
and you should omit the actual values X and space. Even though most developers will
know these values, it is clearer when using the constants, especially abap_false, which
should be prioritized to IS INITIAL. All these different usages may obscure the presence
of a Boolean expression at hand.
Overall, the use of Boolean variables and expressions should be a conscious decision.
Even though a Boolean value seems to be useful for all situations, you should use Bool-
eans with care. When determining a state, do not use a Boolean value to represent it,
because often states are not as clearly differentiated as a Boolean value indicates. From
a different viewpoint, the same situation could be better depicted by an enumeration.
Thus, our recommendation is to choose Booleans carefully. Additionally, Boolean val-
ues serving as parameters may indicate that the method does more than one thing, and
you should split this method into smaller methods so that each method has a clear pur-
pose, as described in Chapter 4.

6.4.2 XSDBOOL for Inline Decisions


Boolean variables can be directly assigned with the corresponding constants we
described in the previous section. As shown in Listing 6.20, a simple check and its
resulting assignment can produce a lot of code that is unnecessary for a simple assign-
ment.

IF messages IS INITIAL.
has_entries = abap_false.
ELSE.
has_entries = abap_true.
ENDIF.

Listing 6.20 Assigning a Boolean Variable

Instead of this lengthy code, we recommend using XSDBOOL, as shown in the following
example:

DATA(has_entries) = XSDBOOL( messages IS NOT INITIAL ).

Personal Copy for Andrei Arabolea, [email protected] 163


6 Variables and Literals

The result of this method directly produces an abap_bool variable and avoids the type
conversions necessary with the alternative methods BOOLC and BOOLX. BOOLX returns a
byte chain of type xstring as a result and is one of the bit functions that aren’t usable
for regular logical expressions. BOOLC returns the same values as XSDBOOL, but as a char-
acter rather than as a string. Thus, when comparing to the constants abap_true and
abap_false, a type conversion is executed, which ignores space values. However, BOOLC
returns a space if the logical expression is false, which is ignored during conversion. If
one of the constants is compared to the result of BOOLC, a syntax warning is raised. In
addition to these points, the naming indicates that the result is a Boolean variable and
not a different type as the X and the C indicate. (Note that the XSD prefix is misleading.
The function does not require any XML Schema context and is valid throughout ABAP.)
Another alternative is using COND to assign the true value if the WHEN condition is true.
The false value will be assigned automatically as the initial value for the parameter, and
if the condition does not return a result, this initial value is used. Additionally, the type
abap_bool must be explicitly mentioned to ensure the correct typing, which is not nec-
essary with XSDBOOL. An example using COND is as follows:

DATA(has_entries) = COND abap_bool( WHEN messages IS NOT INITIAL


THEN abap_true ).

Comparing the readability of both approaches, we recommend using XSDBOOL to


increase readability and reduce the amount of unnecessary characters.

6.5 Regular Expressions


Regular expressions are a common way to describe checks and are supported by sev-
eral programming languages and other rule engines. Since they are commonly used,
regular expressions can be exchanged between modules, and expressions can be easily
reused. However, regular expressions, even though they follow their own syntax, are
often hard to read in program code. Let’s walk through simple regular expressions and
basic checks, before moving on to more complex regular expressions.

6.5.1 Simple Regular Expressions


ABAP allows you to use regular expressions at several locations throughout the source
code even though doing so is not common because they are hard to understand
quickly and some additional functions, like matches, are necessary. ABAP keywords or
functions that can work with regular expressions are available in the ABAP section of
the SAP Help Portal.
Regular expressions have the advantage of using a common syntax across different
programming environments and therefore can be shared between environments.

164 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


6.5 Regular Expressions

However, when executing simple checks, avoiding regular expressions improves the
source code. Since regular expressions can be used generically, even easy checks may
look complex, as shown in the following example:

IF input IS NOT INITIAL.


" IF matches( val = input regex = '.+' ).

The keyword IS NOT INITIAL clearly indicates what the check does, but the regular
expression must be processed first to understand it.
Clean code primarily focuses on the code itself and not directly on performance, which
of course can’t be ignored completely. Regular expressions are not ideal in either
aspect, especially for simpler checks. For regular expressions, an execution tree must
be built by parsing the expression during runtime with a simple LOOP AT using a tempo-
rary variable, which can execute quickly and uses a smaller memory footprint.

6.5.2 Basic Checks


Especially in generic coding parts, checks are necessary for ensuring that the objects
represented as strings are consistent. For some simple checks, a regular expression can
ensure that the provided class name only consists of allowed characters, as shown in
the following example. This check only determines if the name is valid, not if the class
is available.

DATA(is_valid) = matches( val = class_name


pattern = '[A-Z][A-Z0-9_/]{0,29}' ).

This rather simple check is easy to understand in its regular expression form. However,
this kind of basic check is already available as a function module or class, and might be
more sophisticated than a simple regular expression check, such as the code shown in
Listing 6.21.

DATA(is_valid) = xsdbool(
matches( val = class_name
regex = '[A-Z][A-Z0-9_/]{0,29}' ) ).

Listing 6.21 Class Name Check

Even though this check is rather simple and understandable, the regular expression
breaks the don’t-repeat-yourself principle since checks are already available for this
purpose (Martin, 2014). Additionally, checks that are made available by the ABAP Basis
team are more likely to include specialties that may not be relevant in the first round,
but still can be quite important.
When using regular expressions, you may reimplement checks several times due to
their simplicity at first glance and their ease of use. But you should avoid regular

Personal Copy for Andrei Arabolea, [email protected] 165


6 Variables and Literals

expressions in clean code, even when the check is quite simple. In another context, or
at a later stage, this check might be insufficient and must then be adapted at all places.
Finding all of these places can be quite tedious and error prone.

6.5.3 Complex Regular Expressions


Regular expressions can easily become complex when chained together or if they han-
dle more than one check. The syntax allows this type of chaining, and, in some other
context (not an advanced programming language), regular expressions might need to
be complex. Thus, the syntax for regular expressions also allows this complexity in
ABAP despite the availability of alternatives that can be understood more quickly.
Nevertheless, in some situations, a complex expression is a better solution than avoid-
ing the expression, which might impact the code consistency. Even in these cases,
often the expression can be improved by splitting it up into its component parts, as
shown in Listing 6.22. With this approach, you can give subexpressions names to
describe the goal or the content of the expression. A set of smaller expressions can be
understood more easily than one large expression.

CONSTANTS class_name TYPE string VALUE `CL\_.*`.


CONSTANTS interface_name TYPE string VALUE `IF\_.*`.
DATA(object_name) = |{ class_name }\|{ interface_name }|.

Listing 6.22 Regular Expression Stored as Constant

Expression Too Complex


A syntactically correct expression can be too complex for execution and would result in
a CX_SY_REGEX_TOO_COMPLEX exception. For example, the expression FIND REGEX '.*X.*'
IN text. may already be too complex. As the execution depends on the length of the
content in the text variable, there might be some cases where it works and some cases
where the text gets too long and then an exception is raised.

6.6 REDUCE
The keyword REDUCE is one of the most powerful keywords in ABAP and can sometimes
even replace a method with a single statement. Due to its power, its proper use is diffi-
cult, often coming down to how and where to leverage the potential of the keyword. As
described for other construction statements, our goal is not just code that produces the
expected result, but also code that is easily understandable for others. This second task
often fails due to its complexity.
The purpose of REDUCE is to reduce an internal table into a single variable (e.g., a sum
or a count), which already demonstrates that REDUCE is not a trivial operator. The full

166 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


6.6 REDUCE

complexity of REDUCE goes beyond the basic parts of the statement explained in this
section. For more details, visit the ABAP section in the SAP Help Portal documenta-
tion.
Like other operations, the resulting type is either given or determined when using the
# character. The determination of the type has several steps depending on where the
operator is used. If used in an inline declaration, which our discussion focuses on, the
type is derived from the first variable that is declared after the keyword INIT. In total,
REDUCE consists of the following four parts:
쐍 LET
The only optional part, this part allows you to create variables (or rather, constants)
that can be used throughout the expression but cannot be changed.
쐍 INIT
Helper variables can be declared and can be used and changed within the expres-
sion. As in the LET case, the variables cannot be used outside of the expression.
쐍 FOR
At least one for the table iteration must be used, but multiple are possible. Both
types of iterations, conditional or table, are available.
쐍 NEXT
The statements after this keyword are executed for every iteration done in the last
FOR from the previous part.

With these four parts alone, a complex construct can be built that is very hard to under-
stand. Furthermore, the operator can also be used inline and combined with other
operators. If all these capabilities are used, the code would be only understandable after
a long period of time. Thus, if the resulting coding is not trivial, we recommend com-
bining REDUCE with other operations for cleaner code.
Nevertheless, if the operation is rather short and the resulting variable is named so pre-
cisely that what is done is clear, REDUCE can be an exceptionally good tool. Naming in
REDUCE operations can be arbitrary compared with the naming guidelines given in other
chapters of this book. There, names need to be precise to make the code clean. In con-
trast, variables used in the LET or INIT part are better if variable names are short or even
just single characters. This recommendation is comparable to the iteration counter i in
other programming languages, as shown in Listing 6.23.

DATA(sum) = REDUCE #(
SUM = 0
FOR i = 1 UNTIL i > lines( items )
NEXT sum = sum + items[ i ]-amount ).

Listing 6.23 Iteration Using REDUCE with Short Names

In other languages, short variable names work because, on the one hand, using a single
character is already common and, on the other hand, the intention is clear. For REDUCE,

Personal Copy for Andrei Arabolea, [email protected] 167


6 Variables and Literals

short variables are better so that the code is even shorter and more precise, as shown in
Listing 6.24, as long as the short names do not hinder understanding, like SUM or i. Table
operations that need better names to be understood should not be implemented using
the REDUCE operator. Of course, if you have a short REDUCE statement, long names can be
used, but as a rule of thumb, they should be avoided.

DATA(net_amount) = REDUCE #(
INIT amount TYPE netwr
FOR item IN sales_order-items
NEXT amount = amount + item-net_amount ).

Listing 6.24 Reduce for Inline Calculation

The power of REDUCE has been used already in some code, usually inline and combined
with other statements. The main concern with this approach is the difficulty in under-
standing the subpart, which is the limitation of REDUCE. However, in existing coding,
this situation can be improved by some refactoring. One possible approach is to carve
out the REDUCE part and store its result in a meaningfully named variable and use this
variable in the code afterwards. Breaking complex statements into smaller parts will
make your code much cleaner. Moreover, if the REDUCE part already holds so much logic
and takes a lot of space, you could extract this part into its method with a precise name.
The task of refactoring can be extended by extracting single parts from the REDUCE block
into cleaner coding. In the end, you have the same outcome, but the code is much eas-
ier to understand. Anyone who must debug the code later will be thankful for the
clearer split because even enormous REDUCE statements are executed as a single state-
ment and thus cannot be debugged properly.

6.7 Summary
A big difference exists between writing code and reading it. Readable code can begin
with the variables you declare, in how they are declared, and in how they are used. In
this chapter, our perspective is to choose variables consciously. A good part of the per-
ceived complexity and steep learning curves that come with working with unfamiliar
code comes down to the variables that are used. Often, using more thoughtfully chosen
variables tremendously increase the cleanliness of the code. We covered the following
key guidelines in this chapter:
쐍 Declare variables inline whenever possible.
쐍 Declare additional variables if it increases readability.
쐍 Declare variables prior to the IF branch, if it’s used in more than one branch.
쐍 Only chain declarations of variables if they belong together.
쐍 Use field symbols as a looping variable.

168 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


6.7 Summary

쐍 Don’t use literals within the code; instead, replace them with constants.
쐍 Organize constants in an enumeration class per type.
쐍 Use abap_bool as the Boolean type.
쐍 Use abap_true and abap_false for comparisons.
쐍 Use XSDBOOL for Boolean variables and inline decisions.
쐍 Only use regular expressions when necessary and keep complexity low.
쐍 Use REDUCE only when the statement is still easy to understand.

In ABAP, a lot of operations are performed on table-like structures, and the language is
powerful in this regard. Therefore, the next chapter will introduce the clean approach
to handling internal tables.

Personal Copy for Andrei Arabolea, [email protected] 169


© 2025 by Rheinwerk Publishing Inc., Boston (MA)
Chapter 7
Internal Tables
In this chapter, you’ll learn key basic clean coding guidelines and best
practices to follow when dealing with internal tables in ABAP. You’ll
learn how to properly declare internal tables and the usage of non-
redundant, optimal, and efficient code. You’ll also learn how to insert
and retrieve table entries as well as perform any manipulation on them.

Internal tables are a fundamental, even indispensable, programming construct in


ABAP. These tables store variable data of a fixed row type in working memory during
runtime. The most important use of internal tables is to locally store and manipulate
the data from a database table within a program.
Each row of an internal table can be any data type, structured or unstructured. The row
type of an internal table forms the basis for its table type.
Since internal tables can sometimes involve processing huge chunks of data, writing
optimal code and avoiding superfluous statements is crucial while working with them.
In this chapter, we’ll guide you on some essential clean coding principles to follow
when dealing with internal tables in your programs. We’ll start by showing you how to
properly declare internal tables with the right table category in Section 7.1 and by avoid-
ing default keys in Section 7.2. We’ll also help you identify and avoid unnecessary state-
ments during the insertion of data into and the retrieval of data from internal tables
from Section 7.3 to Section 7.7. At the end of this chapter, you’ll learn about block pro-
cessing in Section 7.8 and how to determine the number of lines in an internal table in
Section 7.9.

7.1 Using the Right Table Category


Depending on the most common type of row access for an internal table, the right table
category must be defined during declaration. This definition not only helps you iden-
tify the purpose of a table, but also reduces the time required for accessing its entries,
thus improving the overall performance of the program.
The three table categories defined in ABAP are discussed in the following sections.

Personal Copy for Andrei Arabolea, [email protected] 171


7 Internal Tables

7.1.1 Standard Tables


Standard tables are typically used when individual entries can be accessed via their
index. The index of a table entry provides the fastest possible way to access an entry.
A sample declaration of a standard table is as follows:

DATA employees TYPE STANDARD TABLE OF employee.

In order to access a table entry by its index, we use the INDEX keyword in ABAP. For
example:

READ TABLE employees REFERENCE INTO employee INDEX 4.

Accessing a table row by index requires constant time, regardless of the number of
entries in the table. However, as the number of table entries increases, the cost of creat-
ing and maintaining the structure of indexes becomes more expensive. Therefore,
standard tables must only be used for the following cases:
쐍 Small tables
Tables with less entries, where indexing is beneficial and produces less overhead.
쐍 Array-like tables
Tables where the order of entries does not matter or where entries can be processed
in exactly the order in which they were inserted/appended.

Another way to access the rows of a standard table would be by their primary keys. In
the following example, the field id is the primary key of the employees table and can be
accessed using the WITH TABLE KEY clause in ABAP:

READ TABLE employees REFERENCE INTO employee WITH TABLE KEY id = 'E101'.

This access is an iterative process in the ABAP runtime, which means that the cost of
accessing the rows increases linearly with the number of entries. Thus, even in this
case, standard tables should be considered only for small tables or array-like tables.
If sorted access to standard tables is required, then entries can be sorted either in
ascending or descending order by one or more columns using a SORT statement. In
sorted standard tables, BINARY SEARCH can be used with READ TABLE to improve the effi-
ciency of the access. For example:

READ TABLE employees REFERENCE INTO employee WITH KEY id = 'E101' BINARY SEARCH.

7.1.2 Sorted Tables


Sorted tables should be used for larger tables, whose entries always need to be sorted,
right from the time of its creation. The insertion of rows into such tables takes place in
accordance with the sort order defined by their primary table key. A sample declaration
of sorted tables is as follows:

172 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


7.2 Avoiding DEFAULT KEY

DATA employees TYPE SORTED TABLE OF employee WITH UNIQUE KEY id.

The benefits of a sorted table are most apparent when entries must be read, modified,
or processed, often using their full or partial keys in a certain order. In this case, the
insertion, modification, or deletion of entries only requires finding the right index and
doesn’t require the adjustment of the indices for the rest of the entries of the table.
Sorted tables improve the overall efficiency of a program typically when a large num-
ber of row accesses is expected. This efficiency stems from the fact that binary search is
automatically carried out during accesses. Thus, the cost of key accesses increases log-
arithmically with an increase in the number of entries.

7.1.3 Hashed Tables


Hashed tables should be used only for large tables. The entries in these tables are filled
in a single step, never modified, and are often read using their key. A sample declara-
tion of hashed tables is as follows:

DATA employees TYPE HASHED TABLE OF employee WITH UNIQUE KEY id.

Since a hashed table creates a hash map of the keys of its entries, entries cannot be
accessed using an index; instead, they can be accessed by their keys. For example:

READ TABLE employees REFERENCE INTO employee WITH TABLE KEY id = 'E107'.

Hashed tables are similar to database tables, and their keys are always unique. There-
fore, hashed tables are suitable for creating internal tables that can be used like data-
base tables. Their inherent memory and processing overhead make hashed tables
viable and useful only for storing large amounts of data and multiple read accesses. A
hashed table is not worthwhile for smaller tables, whose entries can be accessed easily
by the index.
Each change to the table’s content requires an expensive recalculation of the hash, so
these tables must not be modified often, never if possible. However, these tables can be
extremely efficient since the cost of each key access is always constant and indepen-
dent of the number of table entries.

7.2 Avoiding DEFAULT KEY


Now that you’ve learned when to use the different table categories, let’s take a deeper
look at another clean code principle to follow when declaring an internal table: avoid-
ing the usage of DEFAULT KEY.
Quite often, an internal table is declared in the following way:

DATA employees TYPE STANDARD TABLE OF employee WITH DEFAULT KEY.

Personal Copy for Andrei Arabolea, [email protected] 173


7 Internal Tables

Default keys are often only added to get newer functional statements working, for
example, when you need to use the internal table as an array, which does not depend on
key values. In fact, the keys themselves are usually superfluous and a waste of resources.
Default keys can even lead to obscure mistakes because they ignore numeric data types.
SORT and DELETE ADJACENT DUPLICATES statements, without explicitly mentioning the
field(s), will resort to the primary key of the internal table. In these cases, the usage of
DEFAULT KEY can lead to unexpected results if the table has any numeric fields as the com-
ponents of its key. This warning is also true for READ TABLE … BINARY SEARCH.
Therefore, the usage of DEFAULT KEY must be avoided. A better approach would be one of
the following options:
쐍 Explicitly specify the components of the key, for example, with the following state-
ment:

DATA employees TYPE STANDARD TABLE OF employee WITH NON-UNIQUE KEY id cost_
center.

쐍 Use EMPTY KEY if no key is required, for example, with the following statement:

DATA employees TYPE STANDARD TABLE OF employee WITH EMPTY KEY.

SORT on Tables with EMPTY KEY


A SORT statement on an internal table with EMPTY KEY does not work. Warnings will be
issued during a syntax check if the key’s emptiness can be determined statically (i.e.,
before runtime/execution).

7.3 INSERT INTO TABLE and APPEND TO


So far, you’ve learned some essential clean coding principles to follow when declaring
internal tables. Now, let’s delve further into how to apply clean coding specifically to
performing various operations on these internal tables.
A common operation on internal tables is the insertion of new entries into them. Two
statements can be used for inserting new rows into an internal table: APPEND and INSERT.
Let’s look at these statements in more detail.

7.3.1 APPEND Statement


The APPEND statement appends one or more rows to an internal index table, which
inserts new rows at the end of the internal table, with respect to the primary table
index. You can use the APPEND statement in the following ways:
쐍 To insert a single row at the end of an internal table, use the following statement:

APPEND new_employee TO employees.

174 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


7.3 INSERT INTO TABLE and APPEND TO

쐍 To insert multiple rows of an internal table, for instance, new_employees at the end of
another internal table (employees), use the following statement:

APPEND ROWS OF new_employees TO employees.

As mentioned earlier, the index of the internal table is crucial for the APPEND statement.
As a result, the append behavior for each table category (defined earlier in Section 7.1)
can be described as follows:
쐍 Standard tables
Rows are appended directly, without checking the content of the internal table for
duplicate entries.
쐍 Sorted tables
Rows are appended only if the following conditions are met:
– The new rows match the sort order defined. If the row being appended would
destroy the sort order of sorted tables, a non-handleable exception is raised.
– The new rows do not create duplicate entries, that is, the primary table key is
unique.
쐍 Hashed tables
Rows are not appended. The APPEND statement is not applicable and doesn’t work for
hashed tables since the indexes of its entries aren’t accessible.

As a result, a lot of rework may be required if the table category must be refactored at
some later point, perhaps due to a change in performance requirements.

7.3.2 INSERT Statement


The INSERT statement adds one or more rows to an internal table at any specified posi-
tion. If no position is mentioned, then the new row is inserted at the end (for standard
tables) or at the right position (for sorted and hashed tables). The position can be spec-
ified using either the primary table key or a table index in the following way:
쐍 To insert a single row to an internal table, use the following statement:

INSERT new_employee INTO employees [INDEX 18].

쐍 To insert multiple rows of an internal table, for instance, new_employees to another


internal table (employees), use the following statement:

INSERT LINES OF new_employees INTO employees [INDEX 18].

The insertion of new rows is executed successfully only after validating all existing
unique table keys, including the primary table key and multiple unique secondary
table keys, if present. The system handles any duplicate entries by setting system fields,
such as sy-subrc, or by raising appropriate exceptions.
An INSERT statement works seamlessly on internal tables of all table categories, thus
requiring less time and effort for any likely rework. Therefore, avoid using the APPEND
statement and use the INSERT statement instead.

Personal Copy for Andrei Arabolea, [email protected] 175


7 Internal Tables

7.4 Verifying the Existence of a Row


After inserting entries into an internal table, another frequently performed operation
is the retrieval of its contents. In this section and the following sections, we’ll dig deeply
into some best practices to keep in mind while retrieving and manipulating internal
table entries.
Often in our programs, we need to verify whether a row with certain values in its fields
exists in an internal table before trying to retrieve it or trying to perform any opera-
tions on the row (such as MODIFY, DELETE, etc.). In these cases, you have three options,
which we’ll discuss in detail next.

7.4.1 READ TABLE


We can check for the existence of a table entry using the statement READ TABLE … WITH KEY
on the column and its value. If the statement executes successfully, then the system
field sy-subrc will be set to zero. By checking this condition, you can determine
whether such an entry exists in the table. For example, Listing 7.1 shows how you can
use READ TABLE to check for the existence of an entry with a specific condition, in our
example, for any employee with the first name “Jason”.

READ TABLE employees TRANSPORTING NO FIELDS WITH KEY first_name = 'Jason'.


IF sy-subrc = 0.
" Do Something
ENDIF.

Listing 7.1 Using READ TABLE to Check for the Existence of a Row

7.4.2 LOOP AT
You can also use a LOOP AT statement for this purpose by checking the condition using
the WHERE clause by specifying the column and its value. If the WHERE condition is satis-
fied, then the control is transferred inside the loop. After this loop is processed, you’ll
check for the value of sy-subrc. If the value is zero, the loop has been entered. If the
value is non-zero, it hasn’t. For example, Listing 7.2 shows how you can use LOOP AT to
check for the existence of a row with a specified condition.

LOOP AT employees REFERENCE INTO employee WHERE first_name = 'Jason'.


ENDLOOP.
IF sy-subrc = 0.
" Do Something
ENDIF.

Listing 7.2 Using LOOP AT to Check for the Existence of a Row

176 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


7.5 Retrieving Table Contents

7.4.3 LINE_EXISTS
Neither of the ways shown in Listing 7.1 and Listing 7.2 clearly identify the purpose of
the statement, which is checking for the existence of a row in the internal table. To
make the purpose of the line clearer, you can use the predefined ABAP function LINE_
EXISTS instead. The predicate function LINE_EXISTS checks whether the row of an inter-
nal table specified in a table expression exists and returns abap_true if found (abap_
false otherwise) and can be used in the following ways:
쐍 To check the existence of a row by its one (and only one) key:

IF LINE_EXISTS( employees[ key = 'E101' ] ).

쐍 To check the existence of a row by any of its column names:

IF LINE_EXISTS( employees[ first_name = 'Jason' ] ).

An appropriate example use case would be reading a table of messages. If a single


error message arises, you may not want to proceed further. In this case, you won’t
want to go over the whole table, reading for all the error messages. Instead, you can
skip the execution of the code block that’s not required by using the LINE_EXISTS func-
tion on the messages table. An example of this is shown in Listing 7.3.

IF LINE_EXISTS( messages[ msg_type = 'E' ] ).


RETURN.
ENDIF.

Listing 7.3 Using LINE_EXISTS to Check for Errors in a Table of Messages

As a result, this function expresses the intent of the statement more clearly and is also
concise, which is why we prefer LINE_EXISTS over READ TABLE or LOOP AT.

7.5 Retrieving Table Contents


One common operation performed on internal tables is the retrieval of its contents.
Single or multiple rows of an internal table can be read, based on the specific needs of
the program. Some common objectives for retrieving table data include the following:
쐍 Performing other operations on a table row that has been read, such as MODIFY,
DELETE, etc.
쐍 Using the values of the READ TABLE entry for processing something else, like assigning
another variable based on the value of a particular field, etc.

In most cases, we’re reading a single row, most often by its keys or sometimes by its
index. To this end, you can use either a LOOP AT statement or a READ TABLE statement.
Let’s analyze both options in detail next and explore best practices in the upcoming
subsections.

Personal Copy for Andrei Arabolea, [email protected] 177


7 Internal Tables

7.5.1 LOOP AT
You can retrieve one or more rows of an internal table using the LOOP AT … WHERE state-
ment. This statement runs iteratively over an internal table and executes the block of
statements defined between LOOP AT and its corresponding ENDLOOP. This statement
sequentially reads each row of the specified internal table that meet the criteria speci-
fied in the optional WHERE condition.
The example shown in Listing 7.4 demonstrates a simple LOOP AT statement that reads
each employee entry from the table employees whose location is “London,” one after
another, by specifying the condition in the WHERE clause.

LOOP AT employees REFERENCE INTO employee WHERE location = 'London'.


" Do Something
ENDLOOP.

Listing 7.4 Reading All Rows Matching a Condition Using LOOP AT Iteratively

Alternatively, as shown in Listing 7.5, you could use a nested IF statement inside a LOOP
AT statement. We’ll discuss this further in Section 7.6.

LOOP AT employees REFERENCE INTO employee.


IF employee->location = 'London'.
" Do Something
ENDIF.
ENDLOOP.

Listing 7.5 Reading All Rows Matching a Condition using LOOP AT and NESTED IF Iteratively

In the examples shown in Listing 7.4 and Listing 7.5, the loop runs iteratively over all
the table entries that match the given criteria. So, the LOOP AT statement is suitable only
when the following conditions are true:
1. Multiple table entries may exist that match the specified WHERE or IF condition.
2. We need to read all table entries that match the specified WHERE or IF condition.

7.5.2 READ TABLE


A READ TABLE statement reads a single row from an internal table. The condition that
specifies which row should be retrieved must be provided through one of the following
values:
쐍 A table key
쐍 A free condition, using a WITH KEY clause
쐍 An index

The row’s contents are read into the variable specified after the keyword INTO.

178 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


7.6 LOOP AT WHERE and Nested IF

For example, READ TABLE … WITH KEY can read an employee entry from the table employees
whose field location has a value “London.”
If such an entry exists, then the system field sy-subrc is set to 0, as shown in Listing 7.6.

READ TABLE employees REFERENCE INTO employee WITH KEY location = 'London'.
IF sy-subrc = 0.
" Do something
ENDIF.

Listing 7.6 Reading a Single Row Matching a Condition Using READ TABLE

If multiple rows match the specific condition (i.e., if the condition to identify the row to
be read is not specified uniquely), then the first suitable row is read.
Therefore, using a READ TABLE statement is most appropriate when the following condi-
tions are true:
쐍 Only a single row must be read, and that row can be uniquely identified by either a
table key, a free condition, or its index.
쐍 Even if multiple rows match the given condition, reading only the first one would fit
our purpose. This situation is rare, and quite often, the predicate function LINE_
EXISTS (explained in Section 7.4.3) can be used instead.

A LOOP AT statement also works in both cases. But these statements are redundant and
longer and do not express the aim of the statement clearly. Thus, in these cases, we rec-
ommend favoring READ TABLE over LOOP AT.

7.6 LOOP AT WHERE and Nested IF


As mentioned in Section 7.5.1, a LOOP AT statement is used when multiple rows of an
internal table match the specified condition and all of them need to be read iteratively.
In these scenarios, the condition for retrieval can be specified in two ways. Let’s go over
both options in detail and explore best practices next.

7.6.1 Nested IF
One way to specify the condition is inside the LOOP AT … ENDLOOP statement, using an IF
statement.
For example, as shown in Listing 7.7, the LOOP AT statement reads the reference of all
entries from the table employees whose location is “Sydney” into employee, by specify-
ing this condition in an IF statement.

Personal Copy for Andrei Arabolea, [email protected] 179


7 Internal Tables

LOOP AT employees REFERENCE INTO employee.


IF employee->location = 'Sydney'.
" Do Something
ENDIF.
ENDLOOP.

Listing 7.7 Reading All Rows Matching a Condition Using LOOP AT and Nested IF

One major drawback of this approach is that the loop is executed and iterates over
every row in the internal table. The condition is checked only after retrieving the entry.
As a result, this programming practice is redundant and lengthy. More importantly,
nested ifs are also inefficient in terms of performance because of the following reasons:
쐍 More entries of the internal table must be processed.
쐍 Unnecessary lines of code are executed.

7.6.2 LOOP AT … WHERE


The better alternative is to specify the condition for data retrieval in the WHERE clause of
the LOOP AT statement.
Considering the example shown in Listing 7.7, the condition on the field location can
now be provided in the WHERE clause of the LOOP AT statement, as shown in Listing 7.8.

LOOP AT employees REFERENCE INTO employee WHERE location = 'Sydney'.


" Do Something
ENDLOOP.

Listing 7.8 Reading All Rows Matching a Condition Using LOOP AT and WHERE

Now, only the entries that match the specified criteria are read from the internal table,
thus improving performance. The code is also more concise and clearly conveys the
intent that the statements within the loop are executed only in accordance with the
specified condition. For all these reasons, we prefer LOOP AT … WHERE over nested IF state-
ments.

7.7 Identifying Unnecessary Table Reads


By now, you’ve learned how to identify necessary statements while performing table
reads. Of course, coding such superfluous statements is not an optimal practice and
can result in performance issues that require a lot of time and effort for optimization
and rework.
When we normally write programs, we may fall into habits that result in redundant
lines of code in many ways.

180 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


7.8 Block Processing of Table Rows and Single Row Operations

For example, let’s say we want to read a row in the internal table employees with the con-
dition id = 'E106'. We’ll first check whether such an entry exists, using the predicate
function LINE_EXISTS (as discussed in Section 7.4.3) and raise an exception. Then, we’ll
read the row into employee, as shown in Listing 7.9.

IF NOT LINE_EXISTS( employees[ id = 'E106' ] ).


RAISE EXCEPTION NEW my_data_not_found( ).
ENDIF.
employee = employees[ id = 'E106' ].

Listing 7.9 Checking the Existence of a Row and Then Reading It

This approach clutters the program and slows down the main control flow with a dou-
ble read. Instead, if you expect a row to exist, then execute a read just once and react to
the exception raised if it fails. This approach is shown in Listing 7.10.

TRY.
DATA(row) = employees[ id = 'E106' ].
CATCH cx_sy_itab_line_not_found.
RAISE EXCEPTION NEW my_data_not_found( ).
ENDTRY.

Listing 7.10 Reading a Row and Then Reacting to the Exception If It Fails

Besides being a performance improvement, this approach is a special variant of the


more general best practice “focus on the happy path or error handling, but not both,”
which we discussed in Chapter 4, Section 4.3.4.

7.8 Block Processing of Table Rows and Single Row Operations


Let’s now consider a use case where we have an internal table old_employees, from
which entries indexed from 10 to 25 must be inserted into another internal table called
employees. One way to achieve this goal would be to use a WHILE … ENDWHILE and READ
TABLE statement, as shown in Listing 7.11.

index = 10.
WHILE index <= 25.
READ TABLE old_employees INDEX index INTO employee.
INSERT employee INTO employees.
index = index + 1.
ENDWHILE.

Listing 7.11 Inserting Multiple Consecutive Rows One after Another

Personal Copy for Andrei Arabolea, [email protected] 181


7 Internal Tables

One significant way to improve the performance of a program is to process a complete


block of consecutive rows in one go. In other words, if an entire area of rows in an inter-
nal table can be processed at once, rather than each row being processed individually,
you can use block operations instead.
Block operations can be achieved with the additions of FROM and TO clauses to the
INSERT, APPEND, LOOP, and DELETE statements according to your requirements.
So, the example shown in Listing 7.11 can be modified in the following way:

INSERT LINES OF old_employees FROM 10 TO 25 INTO employees.

Consider another example where one manager Helen is managing 16 employees, and
you need to reassign half of her employees to a new manager, Michael. The optimal
way to go about this reassignment of a block of rows is shown in Listing 7.12.

LOOP AT employees ASSIGNING <employee> FROM 9 TO 16 WHERE manager = ‘Helen’.


<employee>-manager = ‘Michael’.
ENDLOOP.

Listing 7.12 Reading Multiple Consecutive Rows of a Table Using LOOP AT

In conclusion, single row operations must be avoided unless required, since they slow
down the program’s control flow with unnecessary statements, condition checks, and
initializations.

7.9 DESCRIBE TABLE and Table Function LINES


Many times, especially in assertions, we need to determine the number of rows cur-
rently present in an internal table. We’ll discuss two ways you can get this information
next.

7.9.1 DESCRIBE TABLE


The ABAP statement DESCRIBE TABLE with the optional clause LINES is one way to deter-
mine the current number of table rows in an internal table. In the example shown in
Listing 7.13, the field no_of_employees of generic ABAP type i contains the number of
rows in the table employees.

DESCRIBE TABLE employees LINES no_of_employees.


IF no_of_employees = 0.
" Do Something
ENDIF.

Listing 7.13 Row Count Using DESCRIBE TABLE

182 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


7.10 Summary

7.9.2 LINES
The other way to ascertain the current number of rows in an internal table is the table
function LINES. The code shown earlier in Listing 7.13 could then be modified into the
code shown in Listing 7.14.

IF LINES( employees ) = 0.
" Do Something
ENDIF.

Listing 7.14 Row Count Using Table Function LINES

Notice how the table function LINES better articulates the intention of the code and is
also briefer. Therefore, we prefer using the table function LINES over using DESCRIBE
TABLE statements.

7.10 Summary
In this chapter, we introduced you to some basic clean coding practices to follow when
dealing with internal tables in your code to ensure that your code is optimal, efficient,
and maintainable, including the following best practices:
쐍 Use the right table category while declaring internal tables.
쐍 Avoid using DEFAULT KEY during declaration.
쐍 Prefer INSERT INTO TABLE over APPEND TO while inserting entries into internal tables.
쐍 Prefer LINE_EXISTS over READ TABLE or LOOP AT to check the existence of a certain table
entry.
쐍 Use READ TABLE or LOOP AT based on whether reading all the entries of a table matching
the given criteria is absolutely necessary.
쐍 Prefer LOOP AT WHERE over nested IF while iterating over internal table entries and
checking certain conditions.
쐍 Avoid unnecessary and repetitive table reads.
쐍 If possible, prefer block processing of table rows over single row operations.
쐍 Prefer table function LINES over DESCRIBE TABLE because the table function is more
concise and expresses the intent better.

In the next chapter, you’ll learn some best practices to structure control flows in your
programs.

Personal Copy for Andrei Arabolea, [email protected] 183


© 2025 by Rheinwerk Publishing Inc., Boston (MA)
Chapter 8
Control Flow
Writing code is about making decisions and deriving the next action
from these decisions. This chapter focuses on how decisions and their
flow can be optimized in a clean code fashion to improve the transpar-
ency of your code so that readers understand the path that was taken
during development.

Programs in ABAP are written to solve complex problems that arise in the context of
business processes. Many situations must be handled, and even small details can grow
in importance and become business critical. Additionally, if the software is not
designed to cope with unforeseen events and fails at a critical point, a business can
come to a complete stop. To avoid error situations, good and clean code is key, but it’s
not possible to reduce the number of decisions and cases that need to be covered, since
this is defined by the business. The software must handle real-world situations.
In this chapter, we’ll cover how you can improve your code in terms of control flow to
make the necessary complexity understandable. First, in Section 8.1, we’ll start with the
keyword IF, which is the most rudimentary way to make a decision, not only in ABAP.
We’ll show you how IF should be used when writing clean code, especially when nest-
ing decisions, in Section 8.2. Then, in Section 8.3, we’ll cover conditions in general and
show you how crafting a condition properly can tremendously improve your code
even though conditions carry a lot of the complexity being addressed. This section is
not only relevant for IF branches; these principles can be applied generally whenever
something must be written conditionally. When several situations are covered by the
same code, the situation can be categorized with the keyword CASE. Covering every sin-
gle possibility only with IF statements clutters the code. You’ll need to be careful when
using CASE statements, however, which we’ll cover in Section 8.4. Note that reusing the
same CASE statements is an indicator that your code needs refactoring.
Finally, in Section 8.5, we’ll explain the reasoning behind the usage of pseudo loops,
which are executed exactly one time, and describe why pseudo loops should be
avoided or, if already used, should be refactored to clean up the code.

8.1 IFs
One of the most basic parts of a programming language is the ability to execute code
only if a certain condition is met. As in many other languages, the keyword IF is

Personal Copy for Andrei Arabolea, [email protected] 185


8 Control Flow

followed by a condition. If the condition is true, the code that follows the IF is executed
until an ELSE or ENDIF statement or an explicit earlier exit of the branch is reached. The
statement itself does not make the code complex to understand; the usage of the state-
ment is what adds complexity.
In this section, we’ll discuss IF branches and how to keep your IF statements under-
standable.

8.1.1 IF Branches
In its most basic form, an IF statement has two branches: one that is executed if the
condition is true and one branch executed if the condition is false. However, this con-
struct can be reduced further. If no action is specified to respond in a case where the
condition is false, an empty ELSE branch does not add value to the code, and therefore,
an empty ELSE branch should be deleted. Some conditions are constructed in such a
way that the executed code is written only in the ELSE branch, and the IF branch is
empty. The general rule that only branches should be introduced that have coding in
them also applies. For this example, the condition should be changed so that the cod-
ing can be relocated into the actual IF branch and the ELSE branch can be deleted.
In some situations, an IF statement can be replaced by the keyword CHECK followed by
a condition. If the condition is true, the execution continues, but if false, the execution
is skipped. Placed directly in a method, a failed check will directly exit the method, skip-
ping all other code within the method. This type of control flow was explained in detail
in Chapter 4, Section 4.3.5.
Whether the decision is to exit the method with either the IF branch or the check key-
word, the result of the method is mostly unclear for the caller. The calling method can-
not react properly on an unclear result. As shown in Listing 8.1, the expectation of the
caller of the check_is_authorized method is that an exception should be raised if the
user lacks the necessary authorization. If the importing document_type is INITIAL, the
AUTHORITY-CHECK method is not executed, and no exception is raised. Thus, the result is
unclear for the caller. CHECK statements especially tend to return unclear states. An early
exit with a RETURN statement can be more efficient, for example, when the needed result
was calculated earlier and can be retrieved from a buffer, thus skipping a new calcula-
tion.

METHOD check_is_authorized.
CHECK document_type IS NOT INITIAL.
AUTHORITY-CHECK object 'ORDER'
ID 'TYPE' field document_type.
IF sy-subrc <> 0.
RAISE EXCEPTION NEW cx_unauthorized.
ELSE.

186 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


8.1 IFs

is_authorized = abap_true.
ENDIF.
ENDMETHOD.

Listing 8.1 CHECK Leading to Unclear Result

Typical methods where CHECK statements are used are methods making a check. If the
check fails, an exception is raised. Otherwise, no return value is expected, and thus the
direct exit of the method creates an expected result.
An example of an unclear situation after a CHECK statement is a method that is return-
ing the sum of two given parameters. First, within the method, the runtime checks that
both parameters are provided using the check method. When implemented using just
a CHECK statement, the result is not well defined. In ABAP, returning parameters are
always initialized. Thus, the result after a CHECK is not null as in other languages, but a 0.
Now, the caller of the method cannot interpret the result correctly because not all 0 val-
ues indicate an error situation because 0 is a value within the possible space of correct
results. In the end, the result of the method is unclear, somehow comparable to return-
ing null in other languages, which is also hard to interpret or can even lead to unrecov-
erable situations. The issue could be easily resolved when situations that prevent
successful execution are treated as an error and react accordingly by throwing an
exception.
A CHECK statement is an easy way to ensure that boundary conditions are fulfilled, but
the result that is created by this harsh exit must be considered. The caller of the method
should have a clear signature and must rely on the error being treated as such.
In some cases, the complete IF block is replaceable by the COND statement. The COND
statement is not limited to producing these variables as outputs; it can produce results
of different types and, with these results, can replace IF blocks that only perform differ-
ent assignments based on the result of a conditional expression, as shown in Listing
8.2. With every compression into a single statement, consider whether you can save
space without losing additional information. Otherwise, the saved space is not worth it.
Since ABAP has a rather lengthy syntax, you should adopt techniques to reduce the
amount of code extensively.

DATA(is_authorized) = COND #(
WHEN authroized_document_type = abap_true AND
authroized_organization = abap_true
THEN authorized
ELSE unauthorized ).

Listing 8.2 COND Replacing IF Block

Although these inline statements often make maintaining the code more difficult, an
inline assignment is rather uncritical. For many IF blocks that handle an assignment,

Personal Copy for Andrei Arabolea, [email protected] 187


8 Control Flow

the assignment is not the only thing happening within the branch. Sometimes, prior to
the actual assignment, a field symbol must be assigned. These two-step assignments
can be handled within a single COND statement as shown in Listing 8.3.

DATA(businesspartner) = COND #(
WHEN type = employee
THEN
LET <id> = employees[ employee_id = employee_id ]-businesspartner
IN <id>
WHEN type = contractor
THEN
LET <id> = contractors[ contractor_id = contractor_id ]-businesspartner
IN <id> ).

Listing 8.3 COND with Inline Field Symbol Assignment

Due the concentration to simple assignments, complexity is controlled by the amount


of cases that are covered.

8.1.2 Keep It Understandable


The underlying message of this whole book is that clean code is about understanding
code easily. The IF statement has a rather simple syntax and is sometimes overused
when cleaner solutions are available. Keep in mind that every nested IF block creates
new complexity that reduces how easily a method can be understood.
Be conscious about the structure created by the branches you create. In this case, the
structure describes the number of IF statements that are written in a method, but their
only relation is the method they are written in. Moreover, the nesting depth is
described separately in the next section and is not described with this structure.
Even though IF statements on the same level that are not nested have a separate scope,
following the logic of code is more difficult. Listing 8.4 shows how only a few IF state-
ments make the code hard to follow, even though not much logic is introduced. With
every IF, a new possible execution path is created that the reader needs to understand
to understand the complete method. Keep in mind that a method should only do one
thing. Methods that contain so many possible results are hard to follow and are proba-
bly doing more than one thing. With every additional possibility within a method, the
complexity, and the unit testing required to achieve acceptable coverage, increases. It's
important to make it as easy as possible to write tests; otherwise, another person edit-
ing the method might not add the necessary test.

METHOD calculate_tax_amount.
IF country = 'US' AND state = 'NY'.
tax_amount = special_tax_ny( ).

188 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


8.1 IFs

ELSEIF country = 'US'.


tax_amount = us_normal_tax( ).
ENDIF.
IF tax_type = reduced.
DATA(deductible_amount) = get_deduction( ).
tax_amount = tax_amount – deductible_amount.
ENDIF.
ENDMETHOD.

Listing 8.4 IFs with Separate Scopes

The easy way to enhance a method with a condition-based additional functionality is


to add the new functionality and craft an IF around it so that the functionality is only
executed when necessary. After some of these iterations, the method may increase in
scope, and only seldom will its name be adapted accordingly. Be sure you check the sur-
rounding code carefully before adding another IF block. The time spent for analysis
and subsequent refactoring is time well invested. Functionality that is simply added
into another method without working it into the whole increases the technical debt of
the adapted code. Finding the time to remove technical debt is even harder than find-
ing the time to follow the clean approach in the first place. By introducing new techni-
cal debt, the overall situation deteriorates, and your code is less likely to work properly.
However, sometimes, adding a new method call within a new IF branch is the best solu-
tion. In several discussions and code reviews, a question would arise about where to
put the IF for the new functionality. Should the IF branch be located around the new
method call to make clear that the method is not executed every time? Or, is it better to
locate the IF within the method itself and make a check for an early exit of the method?
Unfortunately, no easy answer for this question exists because the situation is differ-
ent from case to case. The question that you must answer is where the check belongs. If
the method depends on a condition that is checked, the check should be located within
the method as a criterion for an early exit or even an error message, depending on
severity. However, in the other case, where the method is working fine and does not
depend on the check, locating the check within the method would reduce the reusabil-
ity of the method and add unnecessary complexity. In this case, the condition should
be located within the calling method because the check only has relevance to this
method.
In general, you can enhance understandability by reducing the amount of different
cases handled in a single method without additional context. This reduction enables
the reader to selectively go deeper into the logic. Since the only indication that a case is
available is the name of the called method, this name must be very precise and unam-
biguous. To get a feel of whether the complexity is still acceptable, the unit tests can be
checked as they serve as documentation as well. If the setup of the test method requires
many necessary steps, the method itself might already have too much complexity.

Personal Copy for Andrei Arabolea, [email protected] 189


8 Control Flow

8.2 Nesting Depth


In the previous section, we covered what to look for when writing IF branches in paral-
lel, but most complexity comes from branches that are too deep and too complex. The
complexity of the condition checked within the IF branch might be high, but so too is
how many other IF statements are located within an IF branch. Like parallel branches,
depth creates additional complexity. Otherwise, you could refactor two nested
branches into a single condition, which would reduce the depth of nesting and increase
the readability of the code.
The decision tree that is built by complex nested branches can often be taken apart
using submethods to bring the subtree into its own method and add additional docu-
mentation via the name of the method. The code now has additional information, and
the reader is more likely to follow the code. Usually, the purpose of documenting meth-
ods is to help identify whether a reader needs to dive more deeply into this method.
Additionally, a decision tree that is decomposed into subtrees can be tested with less
effort because the combinations that need to be tested do not increase exponentially.
Robert C. Martin proposed in his book, Clean Code, to have only one single line in every
branch (Martin, 2014). Following this guidance, the nesting depth would always be one,
and the amount of method names that can be used for documentation purposes
increases. However, this ideal situation is not suitable for or achievable in every project.
In trivial cases, the additional method does not add additional value. Nonetheless,
keeping nesting depth low is important. Generally, a nesting depth with 3 levels is
already hard to understand and already has at least 8 different combinations that need
to be tested. (And we’re only considering a simple scenario where each nested IF state-
ment only has an IF and an ELSE branch, and no additional ELSEIF combinations.) This
complexity, also called cyclomatic complexity, is briefly explained in Chapter 14,
Section 14.4.2.

8.3 Conditions
Until now, our only topic was how to structure and work with IF branches cleanly, not
about the conditions themselves and the underlying decisions prominent within the
IF block. Without proper well-defined decisions, the program logic will not work as
intended. Decisions are rarely trivial in themselves, usually have intrinsic complexity,
and become even more complex based on the context. Since these decisions are crucial
for the code, we’ve dedicated this section to the crafting of conditions.

8.3.1 Try to Make Conditions Positive


The business world is complex and handles a broad set of processes. Often, a business
event only needs some parts of a software to be executed, and other parts of the soft-
ware are not relevant. Therefore, business software also tends to be complex. One part

190 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


8.3 Conditions

of this complexity is the amount of decisions that must be taken and the definition of
the process flow. More and more processes should be automated, and decisions should
be made by the code. Again, this goal increases the amount of conditions that are pres-
ent within the code. Improving the style of the conditions (that is, the way they can be
received) can have a significant impact on the maintainability of your code and can
reduce the number of unintended execution paths.
The first recommendation is to formulate conditions in a positive way. A positive condi-
tion can be processed more quickly by readers. With a positive formulation, the scope of
the condition is clearly defined. In contrast, with a negative formulation, only the
excluded scope is defined, and the relevant part is not explicitly written out and must be
inferred. Many programmers, however, are accustomed to the negative form already,
and the process of gathering the relevant scope might usually be fast. Still, a negative
formulation is an error-prone approach that requires experience on the part of the
reader. The example shown in Listing 8.5 is a condition formulated in a negative way.

IF is_relevant <> abap_false.


"Do something.
ENDIF.

Listing 8.5 Anti-Pattern: Negative Condition

Especially in combination with Boolean variables, negatively formulated conditions


are harder to understand. In this example, the negative form represents a double nega-
tion. The same logical result with a positive formulation is shown in Listing 8.6.

IF is_relevant = abap_true.
“Do something
ENDIF.

Listing 8.6 Pattern: Positive Condition

Comparing both examples, the second version is easier to understand. The condition
appears more precise, even though the result is the same.
Positive conditions can be achieved easily when using Boolean values. Especially in
ABAP, making comparisons in a positive way is recommended since, while a rare case,
an unknown value could be used as well, and a negative comparison might result in
unintended results.
Other conditions may not be so easy to change into the positive form. A condition that
is not directly transferable to a positive form may be deconstructed for easier compar-
ison. Still, in some situations, a negative comparison is unavoidable. One common
example is working with the system field sy-subrc. Only if the value is 0 can you be sure
that the last statement executed successfully, and a lot of keywords indicate issues or
special situation with a change in the value. The result is that the check for an errone-
ous situation will include a negative comparison, as shown in Listing 8.7.

Personal Copy for Andrei Arabolea, [email protected] 191


8 Control Flow

IF sy-subrc <> 0.
“Do Error Handling
ENDIF.

Listing 8.7 Negative Comparison for sy-subrc

Even though the condition is negative, the argument that the code is harder to under-
stand is not valid in this case. Checking the sy-subrc value is such a basic component in
ABAP programs that every developer should be familiar with the handling of this field
as though part of the syntax. Therefore, no reason exists to change this check for some-
thing better.
The sy-subrc field is an exception to the general rule that you should phrase conditions
in a positive way. The example shown in Listing 8.8 shows a negative comparison that
is harder to understand.

IF document_category <> 'C' AND document_category <> 'G'.


calculate_billing_amount( ).
ENDIF.

Listing 8.8 Anti-Pattern: Negative Condition

The condition itself is not yet complex, but due to the not equal comparison, the list of
document categories that are in scope for the branch is rather long. Instead, you should
formulate the values that are relevant for the condition rather than stating which val-
ues are irrelevant. Additionally, confusion is created when the logical result of the con-
dition must be true to execute the first branch because a negative phrase gets a positive
result. In spoken languages, these double negations are also not easy to process, but at
this point, the double negation is necessary to exclude two values from the set of doc-
ument categories, since the billing amount calculation is relevant for all others. A pos-
itive condition would need to include all other possible document categories, which is
not practical.
Rather than changing the condition within the IF in an unusable manner, the condi-
tion can be enhanced with further context, as shown in Listing 8.9. The decision itself is
extracted from the IF part and checked separately.

DATA(category_is_billing_relevant) = xsdbool(
document_category <> ‘C’ AND
document_category <> ‘G’ ).

IF category_is_billing_relevant = abap_true.
calculate_billing_amount( ).
ENDIF.

Listing 8.9 Pattern: Extract Condition for Additional Context

192 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


8.3 Conditions

The additional context is solely provided by the name of the variable that stores the
result from the condition. In this case, the name category_is_billing_relevant indi-
cates that only some categories are relevant for billing and the relevance of the cate-
gory is stored in the variable. The condition that is checked within the IF block is trivial
now. The runtime only checks the result of the new variable, and if true, the billing
amount is calculated. With just the introduction of a new variable, the control flow is
easier to follow, and the reader has more information to decide which path is most rel-
evant. Additionally, the new variable can be reused within the method if necessary.
The previous example can be further enhanced by introducing an enumeration class
for the document categories instead of using literals as described in Chapter 6, Section
6.2.2. Furthermore, the information that the document category is relevant for billing
might be relevant at several places. Conditions should be reused as much as possible
rather than duplicating the condition at several points throughout the code. Rather
simple conditions like those used in this section tend to be rewritten several times,
which is a violation of the don’t-repeat-yourself principle. Whenever a new category is
introduced that is also not billing relevant, different methods need adaptation. To
avoid these situations, reusable conditions should be extracted into their own method.
In most cases, the resulting methods are only a few lines of code, but the overall code
quality benefits, and the calling method may not need to introduce a new variable
because the method name gives enough context so that it might be used directly
within the IF block and the calling code gets cleaner.

8.3.2 IS NOT or NOT IS


ABAP is a programming language that allows some keywords a broad range of syn-
taxes. One example is the IS NOT and NOT IS, which are identical for the compiler and
therefore have identical results. The developer can decide which phrase to use.
The keyword NOT is comparable to the exclamation mark in front of logical expressions
in other programming languages (NOT is inverting the result of the expression that fol-
lows). The syntax in ABAP has more similarities with the English language compared to
Java where the English word “not” is used for inversions.
Listing 8.10 shows an example where the IS INITIAL expression is inverted by a leading
NOT rather than using IS NOT INITIAL.

IF NOT writer IS INITIAL.


writer->write( |The result is: { result }| ) .
ENDIF.

Listing 8.10 Anti-Pattern: Using NOT IS INITIAL

Personal Copy for Andrei Arabolea, [email protected] 193


8 Control Flow

Using the leading NOT in this case may lead to misunderstanding the code since the NOT
can easily be overlooked, which changes the logic completely. Another approach is
shown in Listing 8.11.

IF writer IS NOT INITIAL.


writer->write( |The result is: { result }| ) .
ENDIF.

Listing 8.11 Pattern: Using IS NOT INITIAL

The trailing NOT is harder to overlook. Additionally, the whole expression is more
aligned with the English language. Using a syntax that resembles natural language
means the brain does not acknowledge that the written words are in a programming
context. Therefore, misunderstandings can be prevented by choosing a syntax closer
to natural language, and the processing as natural language does not harm the under-
standing in this case.
The possibilities for inverting the IS stand only as substitutes for other keywords as
well. More examples are available than can be described in this section, but the recom-
mendations apply analogously. Every time more than one possibility exists, the option
you choose should have the least chance of being misunderstood. You might avoid
some expressions completely by applying general concepts that are also available in
other programming languages and that might not share the same patterns as the
English language.
A more general usage of NOT is in conditions that aren’t checking against the initial
value of a variable but rather want to invert one or more parts of the expression. Any
logical expression can be inverted by a leading NOT resulting again in several possibili-
ties to express a comparison. You can add NOT instead of using the not equals operator,
as shown in Listing 8.12.

IF NOT counter = 1.
“do something.
ENDIF.

Listing 8.12 Negated Equals

Indeed, in some cases, using NOT is the best option to keep the expression understand-
able, but you should not use NOT when better options exist, as in the converted example
shown in Listing 8.13. Here, the unequal sign <> is used instead of NOT counter = 1.

IF counter <> 1.
“do something.
ENDIF.

Listing 8.13 Using <> Instead of NOT

194 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


8.3 Conditions

After reading Section 8.3.1, the first example may come to mind, because the compari-
son of the counter is phrased in a positive way. However, the overall expression only
works when the counter is not equal to a specific value. Converting the expression in
such a way to fulfill the recommendation leads to a less readable expression that you
should avoid. Conditions should be written as precisely and as cleanly as possible to
avoid errors. The example shown in Listing 8.12 may be precise but does not make the
code better and therefore should be avoided.

8.3.3 Complex Conditions


Up to this point, our conditions have been rather simple, but these simple conditions
qualified as good examples to introduce clean code recommendations for conditions.
Not all decisions or execution paths can be derived by such simple conditions. Good
design and clean coding can reduce the necessity to construct complex conditions, but
cases that require complex conditions will always exist. Unfortunately, if complexity
rises, the chance of error and the effort required for proper unit testing also increase.
The problem with understanding complex conditions is that a range of inputs must be
condensed into a single true or false value to decide if the branch should be executed
or not. With every additional input that is required, the number of possibilities
increases as well.
Within ABAP, you can condense complex expressions into a form that is represented
by a single statement, which then also can be written inline. Although inline conditions
reduce the number of lines, these lines can be hard to follow, especially in complex
expressions. At this point, we don’t have a precise definition to determine which
expressions are complex or not. The goal for the optimization of the code, however, is
the understandability of the expression. Moreover, two dimensions can be considered
to reduce this goal: the number of different values or inputs that come into the expres-
sion and the number of operations necessary to complete the expression. The actual
values for these two dimensions can define complexity, without a clear threshold. For
instance, an expression with 10 different operations and only 1 input can be simple or
complex.
A solid indicator for the complexity is the amount of time necessary to follow the
expression and the implicit knowledge that is necessary to understand it. The implicit
knowledge required should be reduced to zero by providing additional context and
using enumeration classes.
The example shown in Listing 8.14 illustrates how just four lines of a condition can
become quite complex and hard to maintain.

IF document_category EQ 'C'
AND ( payment_terms = 'Z' OR today < cond_date )
OR customer->has_extended_condition( ) = abap_true

Personal Copy for Andrei Arabolea, [email protected] 195


8 Control Flow

AND today < extended_date.


billing_amount = billing_amount * '0.98'.
ENDIF.

Listing 8.14 Anti-Pattern: Complex Condition

The best way to clean up the condition is to deconstruct the condition into its parts and
give each part additional context by naming. Additionally, by making each condition
available as its own method, the ability to reuse these conditions is increased. This
deconstruction should be done recursively, until the expression is again simple. Not all
layers of deconstruction need their own methods, however, since adding additional
context using additional variables and their names might be sufficient. The result of
such a deconstruction is shown in Listing 8.15.

METHOD calculate_billing_amount.
IF is_paid_early( ) = abap_true .
billing_amount = deduct_ paymnt_discount( billing_amount ).
ENDIF.
ENDMETHOD.
METHOD is_paid_early.
applicable = xsdbool(
eaerly_payment_applicable( ) OR
is_extended_discount_applicable( ) ).
ENDMETHOD.

Listing 8.15 Deconstructed Complex Condition

The result is a set of methods that return Boolean values, which is easier to handle in an
expression since only two possible values exist. Due to the additional context added by
the method names, the expression is easier to process for the reader. Moreover, the
maintenance effort for each method is decreased since the amount of time spent on
searching for the part of the expression that does not work is reduced. Previously, you
could not debug individual parts of a condition as a condition was executed as single
statement. With the deconstruction, every method is entered separately, and the error
can be identified more easily.

8.4 CASE
The CASE statement provides the ability to handle different situations in a clean and or-
ganized way. Regarding structuring, the coding case has a clear advantage over a set of
IF statements because a CASE statement reduces the amount of necessary checks and
generates well-structured code. However, CASE must be handled with care, otherwise the
well-structured method contradicts a well-structure architecture. We’ll walk through
various usages of CASE in the following sections.

196 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


8.4 CASE

8.4.1 CASE or IF
The syntax of CASE has two main advantages for clean code over IF statements. While a
CASE statement can always be replaced by a set of IF blocks, the opposite is not possible,
because a CASE statement only checks a single variable for specific values, whereas in an
IF statement, any kind of condition and combined conditions can be checked. As a
result, whenever possible and useful, the CASE statement should be preferred. By
design, a CASE statement creates a structure within the method that is easy to follow
since every handled case is introduced by a new WHEN block. All comparisons that are
made have an implicit positive phrasing since the only operation is a comparison for
equality. Thus, you won’t have inverted or other negative forms, which makes it easier
to follow the control flow.
CASE statements work especially well with enumerations or similar value sets. They
contain a well-defined set of possible cases that define the possible WHEN blocks. Addi-
tionally, when the enumeration is extended or a new situation is added to the scope,
the CASE can be easily enhanced by adding a new WHEN block. Since this approach is min-
imally invasive to the other cases, the chance of introducing an error is quite low.
Even though the CASE statement provides a good structure, these statements tend to
have more logic within the method than identifying the relevant case and delegating
further processing to another method. This scenario contradicts the single-responsibil-
ity principle because the method does have more than one reason to change. Changes
must be made when another case is added and when the processing of one of the cov-
ered cases changes. Listing 8.16 shows an example in which the single-responsibility
principle has not been considered.

CASE vehicle_type.
WHEN 'TRAIN'.
duration = waiting + distance / speed + stops * wait_at_stop.
WHEN 'PLANE'.
duration = waiting * 2 + ( distance / speed ).
WHEN 'CAR'.
duration = distance / speed.
ENDCASE.

Listing 8.16 Anti-Pattern: Case with Further Processing

In this example, whenever a new vehicle type is added to the scope, the method must
be changed. Additionally, when a change is made to the calculation of the duration (e.g.,
to include slowing down prior to a stop), the method must be changed as well. Thus,
more than one reason exists for the change. In summary, a method should hardly do
more than the case itself. To consider this principle, each WHEN block should only dele-
gate to another method that implements the calculation, as shown in Listing 8.17.

Personal Copy for Andrei Arabolea, [email protected] 197


8 Control Flow

CASE vehicle_type.
WHEN 'TRAIN'.
duration = calculate_train_duration( ).
WHEN 'PLANE'.
duration = calculate_plane_duration( ).
WHEN 'CAR'.
duration = calculate_car_duration( ).
ENDCASE.

Listing 8.17 Case Considering the Single-Responsibility Principle

With the last change, the method is only identifying the relevant cases and delegates
the processing accordingly. However, in this example, all our calculations are rather
simple; based on the decision processing, the code can get quite complex. Moreover,
testing the case method separately from the actual calculation is easier since there
would be fewer cases to consider.

8.4.2 CASE or SWITCH


Sometimes, a CASE block is only used to assign a specific field value to a more generic
one, perhaps because the type is not relevant anymore or to bring a single value to the
user. For this purpose, the SWITCH operation was introduced, which can function as an
inline assignment, as shown in Listing 8.18.

DATA(id_to_display) = SWITCH string( id_type


WHEN 'EMPLOYEE' THEN partner-employee_id
WHEN 'EXT_WORK' THEN partner-assignment_id
WHEN 'CUSTOMER' THEN partner-customer_id
ELSE THROW unknown_type( ).

Listing 8.18 SWITCH for Inline Assignment

Like other operators, SWITCH can also be used inline, for example, for method parame-
ters that allow the result of the SWITCH to be changed. The same rules apply as for other
operators. Using the inline options whenever possible might seem convenient at first.
Unfortunately, a non-trivial assignment for a parameter can result in code that is hard
to follow and maintain.
The previous assignment is not complex and only covers three possible values for the
id_type. Still, the statement uses several lines and, when embedded in another method
call, is hard to follow. SWITCH can save space and reduce the number of statements when
they do not add value. However, you can easily overuse this operator, and in general,
you should not use SWITCH within another functional call. Instead, the result should be
stored in its own variable, thus enabling you to introduce an additional name.

198 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


8.4 CASE

However, since SWITCH provides a short way to write CASE statements, programmers
tend to use SWITCH more than once. For the reasons mentioned in the previous section,
reusing the same CASE or SWITCH statements has some drawbacks that you’ll need to
consider. An enumeration that is used in a SWITCH can change, and all users will need to
be changed. Therefore, the footprint of SWITCH should be reduced so that a change in the
enumeration results in a minimum amount of changes in other methods. Extracting
the operator within its own method that focuses on identifying the current case and
returning the right value results in a better separation, and a change within the enu-
meration only results in a change in the single switch where the enumeration is encap-
sulated.
In the end, the same decision must be made: whether having a single method with
SWITCH or hiding the different cases in a factory method and creating its own class
makes the most sense, which we’ll discuss next.

8.4.3 Multiple Uses of the Same CASE


CASE has the advantage of creating a well-structured method that can be easily followed
and often directly reacts to different situations. Unfortunately, due to these advan-
tages, the same CASE is often reused several times. The example shown in Listing 8.17
identifies a vehicle type for which the duration should be calculated in an easy way. It
is unlikely that the differentiation based on the type is only relevant for this calculation
and not reused in any other method.
Since CASE makes identifying the case so easy, it is often reused several times, thus vio-
lating the don’t-repeat-yourself principle. This principle is particularly important to
follow because code changes. If copied, rarely are those copies adapted accordingly, and
inconsistent behaviors are created.
The repetition of CASE must not be completely identical. Sometimes, only some cases
are repeated at another place, which should also be avoided. In an object-oriented envi-
ronment, the right approach to avoid repetition is polymorphism (see Chapter 3, Sec-
tion 3.1). Robert C. Martin even suggested hiding CASE statements in the factory method
of the class and never repeating it (Martin, 2014).
The adapted example would relocate the CASE into the factory method, as shown in List-
ing 8.19, which hides the different types in the factory itself. Thus, the conditional is
replaced by polymorphism.

CASE vehicle_type.
WHEN 'TRAIN'.
instance = new train( ).
WHEN 'PLANE'.
instance = new plane( ).

Personal Copy for Andrei Arabolea, [email protected] 199


8 Control Flow

WHEN 'CAR'.
instance = new car( ).
ENDCASE.

Listing 8.19 CASE in the Factory Method

Every subclass of the vehicle class implements the calculate_duration method as


required. This can also be done for every other method that has a different behavior
other than the upper vehicle class, which contains all coding that is independent from
the vehicle type. Hiding the CASE within the factory method helps us follow both the
single-responsibility principle and the recommendation to not repeat yourself.

8.5 Do 1 Times
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 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, which are mainly
explained in Chapter 4. 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.

8.5.1 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 previ-
ously in the method. The code in between has a few reasons to be exited early or com-
pletely 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 contin-
ues with the next one. Since there is no next iteration in a one-time loop, the execution
continues after the ENDO.
Listing 8.20 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.

200 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


8.5 Do 1 Times

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 ).

Listing 8.20 DO 1 TIMES to Avoid Multiple IFs

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.
As mentioned earlier, 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, cre-
ating 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.

8.5.2 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.

Personal Copy for Andrei Arabolea, [email protected] 201


8 Control Flow

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 in Listing 8.20. 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 in
Listing 8.21, 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.

Listing 8.21 Intermediate Refactoring Result

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 fur-
ther 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 in Listing 8.22, 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 ).

Listing 8.22 Final Refactoring Result

Since the pseudo loop was introduced to serve as a detached part to facilitate easy con-
trol flow, this part can also be extracted into its own method. One disadvantage of the

202 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


8.6 Summary

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 situa-
tions might be as straightforward as the example used in this section. The biggest dif-
ference 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.

8.6 Summary
Control flow is a key component in software written to handle complex business pro-
cesses. A variety of situations from everyday life may be covered as well, which means
possibly handling a lot of edge cases and exceptions. In this chapter, we explained how
you can handle complex requirements with a clean approach to control flow, which is
achieved via the following recommendations discussed in this chapter:
쐍 Phrase conditions precisely and positively.
쐍 Decompose conditions if they are too complex.
쐍 Consider extracting a condition into its own method.
쐍 Nesting depth should not exceed two layers.
쐍 Prefer IS NOT over NOT IS.
쐍 Use CASE instead of IF whenever possible.
쐍 In most cases, CASE can better understood than SWTCH.
쐍 Don’t repeat the same CASE; avoid it (for example, by polymorphism).
쐍 DO 1 TIMES is an indicator that refactoring is needed.

In some cases, the approaches we discussed in this chapter to control flows and condi-
tions are not enough to make your code easy to read. A common way to give readers
the missing piece of information is to add comments in the code to provide additional
information. The rules for comments are covered in the next chapter, where you’ll see
how comments work in a clean code environment.

Personal Copy for Andrei Arabolea, [email protected] 203


© 2025 by Rheinwerk Publishing Inc., Boston (MA)
Chapter 9
Comments
Writing code is about making decisions and deriving the best next action
from this series of decisions. In this chapter, we’ll focus on how your deci-
sions and their flow can be optimized with clean code principles in mind
to improve the transparency of your code and help readers see the path
that was taken during development.

Comments do not change a program’s logic. You can use comments to include addi-
tional information in the code, such as the reasoning behind a line of code or special
meanings in the code. A well-placed comment can increase code quality a great deal,
but writing such a good comment is also difficult.
This chapter focuses first, in Section 9.1, on the importance of expressing yourself
through code in a clean code environment. After an overview of comment placement
and usage in Section 9.2, we’ll turn to comments that are better avoided in Section 9.3.
Then, we’ll look briefly at the use of organizational comments that serve as embedded
to-do items in the code in Section 9.4 before looking at some special comment types in
Section 9.5. Overall, this chapter encourages the reduction of the number of comments,
rather than increasing them, while increasing the value of each comment.

9.1 Express Yourself in Code


The goal of clean code is to write code that is so precise that it can be followed and
understood quickly and without additional resources. While reading clean code, the
names of variables and methods should provide so much detail that, for each line, what
is processed in that line is clear.
In comparison, comments are parts of the code that do not provide any functionality
that is intended by the code. Comments simply provide additional information that is
not bound to the behavior and help you understand the code. A comment placed in
code can overcome an otherwise steep learning curve and does not create syntax
issues when the code changes. While these are notable benefits, this is also the reason
why comments often become outdated relatively quickly. The code described within a
comment can be changed in a way that makes the comment obsolete or even com-
pletely wrong.

Personal Copy for Andrei Arabolea, [email protected] 205


9 Comments

Therefore, comments are often outdated and no longer have their original expected
value. Even worse, outdated comments can create misunderstandings and increase the
effort required to work with the code.
Clean code tries to reduce the need for comments by having code that only does one
thing and by adopting names for methods and variables that will not create confusion.
If a variable or method name creates the urge within the developer to write a comment,
the name is not good enough yet. The missing information should be expressed
directly by the code and not within the comment. Code that needs comments to be
understandable should be refactored to avoid needing comments altogether as much
as possible.
Listing 9.1 shows an example of a method that fixes the overflow of a date calculation.
However, the code is flooded with comments that try to make the code understand-
able. The sheer number of comments makes following the method difficult.

" correct e.g., 29.02. in non-leap years


" as well as result of a date calculation would be
" something like e.g., the 31.06.
" that example has to be corrected to 30.06.
METHOD fix_day_overflow.
DO 3 TIMES.
"31 - 28 = 3 => this correction is required not more than 3 times
lv_dummy = cv_date.
" lv_dummy is 0 if the date value is a not existing date -
" ABAP specific implementation
IF ( lv_dummy EQ 0 ).
cv_date+6(2) = cv_date+6(2) - 1.
" subtract 1 day from the given date
ELSE.
" date exists => no correction required
RETURN.
ENDIF.
ENDDO.
ENDMETHOD.

Listing 9.1 Anti-Pattern: Heavy Usage of Comments

The naming of the variables may need additional comments to explain the variables’
purposes in addition to the code itself. In this extreme example, each line of code needs
its own comment to make sense to the reader. In these cases, the refactoring of large
methods could require some iterations to reach a satisfactory point, where only a few
or ideally no comments are necessary, as shown in Listing 9.2.

METHOD correct_day_to_last_in_month.
WHILE is_invalid( date ).
reduce_day_by_one( CHANGING date = date ).

206 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


9.2 Comment Placement and Usage

ENDWHILE.
ENDMETHOD.

METHOD is_invalid.
DATA zero_if_invalid TYPE i.
zero_if_invalid = date.
result = xsdbool( zero_if_invalid = 0 ).
ENDMETHOD.

METHOD reduce_day_by_one.
date+6(2) = date+6(2) - 1.
ENDMETHOD.

Listing 9.2 Pattern: Refactored Code without Comments

After the refactoring, several methods are used, and their names do not need addi-
tional comments to be understood. The use of small methods supports this readability
by making the method itself extremely easy to understand.
Refactoring a larger method into smaller methods that are each more easily under-
standable will have an impact on the runtime. Each new method call increases the run-
time by a small amount of time, but in many cases, the added runtime is insignificant.
Nevertheless, performance also needs to be addressed in clean code. However, perfor-
mance is not an excuse to have unclean code. Since in most cases the added runtime
does not affect the overall perception of performance, the recommended approach is
to refactor complex code first. If the performance impact is too significant, a perfor-
mance measurement should be done. The measurement shows areas in which perfor-
mance improvements are necessary and where effort could be well spent. In many
cases, working on the results of the actual measurement has brought more improve-
ments, and the code is cleaner and faster.

9.2 Comment Placement and Usage


Comments can have a substantial impact on the understandability of some methods. In
the previous section, we recommended refactoring code until most comments can be
removed. Nevertheless, some comments cannot be not efficiently expressed as code.
Good comments do not explain what is done in the code because this is better
expressed by the code itself. Comments should explain the why, not the what. Explain-
ing the why in a comment provides insights into the reasoning behind the coding,
which can help readers understand your code better.
The table read shown in Listing 9.3 is clear, but after a statement like READ TABLE, usu-
ally the sy-subrc is evaluated to avoid terminations. A reader of this small section can

Personal Copy for Andrei Arabolea, [email protected] 207


9 Comments

conclude that there is a bug and the code needs adaption. But in this specific case,
there was an earlier check that ensured that the read will be successful.

READ TABLE items ASSIGNING FIELD-SYMBOL(<item>)


WITH KEY sales_order_id = sales_order
item_id = item.
DATA(tax_amount) = <item>-tax_amount.

Listing 9.3 READ Table Missing an Important Comment

Adding the comment “Cannot fail due to earlier check” states the reason for leaving
out the check and documents the conscious decision taken at this point during devel-
opment. Another developer working with the code will not need to spend time deter-
mining whether a bug needs to be addressed.
In other examples, a comment can explain statements that seem to be placed incor-
rectly, but a reason for its placement exists. If the reason is not obvious by the name of
the method or by patterns, the reason should be explained in a comment.
In ABAP, you can introduce a comment in one of two ways. To mark a complete line as
a comment, the first character in this line must be an asterisk (*). The first character
includes all spaces. Thus, the indentation for the comment must be started after the
introduction, and the result is a strange looking indentation. When this comment is
copied, often the character is not put into the first position and the comment must be
corrected. Comments created with a double quotation mark (") are nicely indented
with the statement to which the comment belongs. Additionally, such comments can
be easily copied. Therefore, comments should always be introduced with ".
The previous example including the missing comment is show in Listing 9.4.

"Cannot fail due to earlier check


READ TABLE items ASSIGNING FIELD-SYMBOL(<item>)
WITH KEY sales_order_id = sales_order
item_id = item.
DATA(tax_amount) = <item>-tax_amount.

Listing 9.4 Read Table with Well Written Comment

9.3 Comments to Avoid


As described in this chapter, writing good comments that keep value over time is diffi-
cult. Unfortunately, many comments that were common practice previously should be
avoided today.
As a programming language, ABAP is already several decades old. Over time, this lan-
guage has improved, adapting to new requirements and developing tremendously.
Together with the language, the development environment has changed significantly,
with more and more features being introduced through the decades.

208 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


9.3 Comments to Avoid

In older programs, especially in function modules, often a large comment is present


that contains the signature of the function module, like the example shown in Listing
9.5. At first, this comment was helpful since no other information for the developer
would be available during development of the function module without switching
back and forth between views.

FUNCTION calculate_price
IMPORTING
document_number TYPE document_number
include_taxes TYPE abap_bool
configuration TYPE configuration
EXPORTING
net_amount TYPE net_amount
gross_amount TYPE gross_amount
TABLES
conditions LIKE conditions OPTIONAL.

*FUNCTION calculate_price
* IMPORTING
* document_number TYPE document_number
* include_taxes TYPE abap_bool
* configuration TYPE configuration
* EXPORTING
* net_amount TYPE net_amount
* gross_amount TYPE gross_amount
* TABLES
* conditions LIKE conditions OPTIONAL.

Listing 9.5 Comment Repeating Function Signature

In today’s environment, this is not needed anymore as the signature of a function


module or method can be displayed directly in the source code. In methods, no historic
reason existed for repeating the signature, and these comments should not only be
avoided but also directly deleted.
Another common comment in older, and especially long, programs and subroutines is
a detailed explanation of the purpose of the whole program. From a clean code perspec-
tive, these comments are already longer than what a typical method should be. If the
method needs such a long explanation, the method should be split further into smaller
methods with better names. The issue with these long explanations is that they often
do not grow with the code, as we discussed in Section 9.1. Initially, the comment
explained what the code does, but no guarantee exists that the comment was updated
when the code was enhanced or changed. Thus, the information is not complete at best
and, at worst, is wrong or obsolete.

Personal Copy for Andrei Arabolea, [email protected] 209


9 Comments

The best way to reduce the need for these comments and to remove them is refactoring
your code so that the information is expressed directly in the code. You thus might
spend more time improving code that has already been running for quite some time.
You must decide if this effort is well spent on that code. If you can already see that the
code will need to be changed or enhanced, refactoring can be useful. However, if the
code is not planned to be touched, the effort might be better spent on code that is being
worked on already. New coding should not be written with these types of comments,
but instead, the new code should directly embody clean code principles and thus avoid
the need for comments altogether.
Other comments commonly found at the start of big programs is a description of the
underlying design of the component. This description belongs in a separate design
document, not within the code as a comment. Big text blocks before the actual code
begins are rarely read and generally indicate that the code itself requires further
improvement to remove the need for the textual description.
In big functions that have grown over time, new parts are added by introducing IF
branches that try to avoid changing how the code works and add new functionalities
without touching the existing code. After some iterations, the code becomes hard to
read and understand as so many IFs are opened and closed at some point. Instead of
refactoring the code into an understandable state, comments might have been used to
support the reader in some way. Listing 9.6 shows a short example how a chain of
ENDIFs is commented with end-of comments like " end-of z = y. These comments, unfor-
tunately, do not make the code less complex. They improve readability slightly, but
these comments are only focused on the symptoms, not on the real reason the code is
hard to understand. Even though it gets easier to see which area is closed, it is not easier
to understand the context to identify the part where new coding fits best. If the code is
structured in small methods that do not even need closing comments, it is easier to
find the correct place. Finding code that is still not understandable, even though some-
one already spent some time to make end-of comments, is a clear indicator that the
code should be refactored.

METHOD does_alot.
"some code

IF a = b.

IF z = y.

ENDIF. "end-of z = y
ENDIF. "end-of a = b
ENDMETHOD

Listing 9.6 Example for ENDIF Comments

210 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


9.4 FIXME, TODO, and XXX Comments

Instead of refactoring the code, sometimes big methods are structured into different
sections only via comments like begin of item handling. The developer already has iden-
tified that a new section is beginning and has given a name already. Unfortunately, no
new method was created to handle the item in a submethod having a clear scope and a
clear signature. These comments can be easily avoided by creating a new method and
calling it at the place where the comment should go.
Several times throughout this chapter, we mentioned the need for refactoring. Often,
refactoring starts with identifying problematic code that is kept as a comment, rather
than deleted, and replacing it with new, hopefully improved code. After testing the new
code and making sure it improves in an area that should be improved, the old code is
not relevant anymore. Unfortunately, a comment persists when not explicitly deleted.
If you come across commented code that is not currently locked in a transport request
(that is, is being worked on), commented code should be deleted directly. The version
history can restore the deleted part if ever needed. However, in many places, code may
have been commented on a long time ago and never deleted. A developer coming
across such a comment cannot know if the comment is still needed or not. Therefore,
just delete the comment directly to avoid any confusion.
The last recommendation in this section is to avoid copying message texts into a com-
ment when raising the message. No benefit is gained with this kind of comment. If the
text is needed, the text can be looked up directly in the message class. As with all other
comments, no one will remember to update all occurrences of the comment once the
text is changed.

9.4 FIXME, TODO, and XXX Comments


Up to this point, we’ve emphasized how comments should be used to explain the rea-
son for a statement to another developer, so that the code is easier to follow. Another
type of comment is also addressed to developers, but these comments contain infor-
mation on what needs to be done with this code, not what has already been done. These
instructions, given via comments, are used during development.
To identify these comments and distinguish them from normal comments, a prefix is
used. In this section, three of these comments are described that have shown to be
helpful. In all cases, you should leave your initials or the sy-uname with the comments,
so that if questions arise from the instruction, the author can be contacted. Comments
are not a place for communicating and discussing the instruction. Sometimes, conver-
sations between different developers exist within a comment, which just confuses
everyone else. Communication should be conducted with the appropriate tools
instead. Therefore, leave some details of how you can be contacted for discussion.

Personal Copy for Andrei Arabolea, [email protected] 211


9 Comments

Additionally, these comments are meant to be used within the team that owns and is
working on the code. Suggestions or bug reports should be addressed in a different and
appropriate way when these requests come from outside the team.
The three prefixes commonly used during development are as follows:
쐍 FIXME
This prefix reports an issue in the code that is not severe enough to justify a formal
bug report or an incident. If the issue is minor, Robert C. Martin suggests following
the Boy Scout Rule: leaving the code better than you found it by correcting the issue
directly.
쐍 TODO
A comment with this prefix marks a place in the code where something is missing
and should be added/changed. When working with this prefix, you must understand
that the change should be done soon. Otherwise, the comment might get lost, and
the change is never introduced. Unfortunately, no formal to-do list functionality
scans your code for these comments.
쐍 XXX
This third prefix marks code that does what it should, but room exists for improve-
ment; for instance, to extract another method to add more context or to improve
performance.

All these prefixes only work if they are picked up and worked on. Otherwise, these com-
ments have the same behaviors as other comments. If not completed or deleted at
some point, these comments become obsolete immediately as leftovers that might not
be relevant anymore. Additionally, you’ll need time to work on minor improvements
that are not formally tracked. If not reliable, comments should not be written, and
instead issues should be added to a formal backlog. In this way, you can plan and work
on issues.
Besides these special comments that a team might adopt, other kinds of comments are
built into the ABAP language, which we’ll cover next.

9.5 Special Comments: ABAP Doc, Pragmas, and Pseudo Comments


In ABAP, some comments are special and can influence the result of checks or provide
further documentation to developers. These special comments must follow a syntax to
be recognized as, for example, documentation. If these comments do not follow the
syntax, they are interpreted as normal comments and do not influence ABAP Test
Cockpit checks.
ABAP Doc comments are comments that provide additional information for other
developers, which are particularly useful for public APIs. Listing 9.7 shows a method
that is documented with an ABAP Doc comment. The text that is written after the ABAP

212 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


9.5 Special Comments: ABAP Doc, Pragmas, and Pseudo Comments

Doc introduction “!” is shown to developers in the code completion view. All parame-
ters of the method can get their own explanation. The parameter name is stated after
the @ sign, and the explanatory text is provided after a pipe.

"! Calculates the total net amount of the complete sales order
"! and returns the found pricing procedure
"! @sales_
order | ID of a sales order for which the net amount should be calculated
"! @pricing_
procedure | ID of the pricing procedure that was determined during calculation
METHODS calculate_total_net_amount
IMPORTING
sales_order type sales_order_id
RETURNING
value(pricing_procedure) TYPE pricing_procedure.

Listing 9.7 Method Documented with ABAP Doc

The name of the parameter in the ABAP Doc is validated against the actual parameters,
and if the name is not valid, a warning is raised. The content of the document should
provide guidance on how the interface should be used and possibly additional infor-
mation that is helpful for the consumer.
Comments written as ABAP Docs have the same issues as all other comments. When
the interface is changed, or the behavior is adapted, the ABAP Doc is outdated and must
be adapted. Therefore, these comments should only be written for external interfaces
and not for internal methods. Interfaces should stay rather stable, and thus, the docu-
ment is more likely to remain up to date.
Other special parts of ABAP code that are not executable are pragmas and pseudo com-
ments. A pragma is not executable code; rather, it’s a directive to the syntax check and
the extended program check. There are several different pragmas available that are
introduced with a leading ##, as shown in the following example where the pragma
needed indicates that the variable is necessary even though there isn’t static usage of it.
The syntax of pragmas requires that the pragma is stated prior to the closing. In most
cases, the pseudo comments are obsolete and have a pragma replacement. Whenever
possible, pragmas should be preferred.

DATA component_name TYPE fieldname ##needed.

Pragmas are used to suppress (and thus consciously ignore) some checks and warnings.
Some warnings are raised to give further information that the code might have room
for improvement. For example, a warning is raised when an opportunity exists to join
an internal table with a database table in a single SELECT statement. This functionality is
only possible with some databases. To make the developer aware, a warning is raised.
However, Listing 9.8 shows that this warning can be suppressed by a pragma to indicate

Personal Copy for Andrei Arabolea, [email protected] 213


9 Comments

that the warning was received and a decision was made to work with the functionality
as is.

DATA(open_order_ids) = customer->get_open_orders( ).
SELECT * FROM orders AS o INNER JOIN @open_order_ids AS p
ON o~order = p~order INTO @data(open_orders) ##db_feature_mode[itabs_in_
from_clause].

Listing 9.8 Pragma Usage

9.6 Summary
Proper comments can greatly improve the quality of your code, but they can be chal-
lenging to write. A comment needs the same conscious thought process and continu-
ous improvement as the code itself. A comment that is not precisely written can create
more confusion than help. Overall, a guiding principle is to express yourself in the
code, not through the comments. The following clean ABAP recommendations for
comments were discussed in this chapter:
쐍 Express yourself in code, not in comments.
쐍 Explain the why, not the what, in a comment.
쐍 Use " to introduce a comment.
쐍 Place a comment in front of the affected statement.
쐍 Delete commented code directly.
쐍 Use ABAP Docs only for public APIs.

In general, in addition to being written well, code must be formatted well to be under-
stood easily. The next chapter will focus on correct formatting rules in a clean ABAP
environment.

214 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Chapter 10
Formatting
In this chapter, you’ll learn why formatting is a prerequisite to good pro-
gramming. You’ll learn some simple and fundamental formatting tech-
niques to make your ABAP code more readable and easier to maintain.
We’ll also provide insights on why getting into the habit of good code
formatting in your day-to-day ABAP programming is so necessary.

A true hallmark of a professional programmer is the legibility, or readability, of their


code. The readability of a piece of code is a measured by how easily a fellow program-
mer can read and understand what that code does, which is especially important
because, in most situations, several people are working on the same software or proj-
ect. Having readable and easily understandable code makes everyone’s job easier. For-
matting plays a significant role in the readability of code in all programming lan-
guages, including ABAP.
Formatting doesn’t just mean making the code “look neat.” Formatting also includes
choosing meaningful names for variables, classes, methods, functions, and other ABAP
programming elements. It involves adding a crisp and clear description to these
classes, methods, method parameters, etc. Good formatting also includes maintaining
proper comments, especially when what a bit of code does isn’t clear at first glance, as
discussed in Chapter 9. All these best practices, and many other clean code guidelines
mentioned in this book, make the work of a programmer easy because they signifi-
cantly enhance the readability of code. This readability, in turn, helps others easily
understanding the purpose of the code. Also, the more readable the code is, the easier
you’ll find identifying and squashing potential bugs in earlier stages of development.
Of course, one could argue that formatting is not necessary for a short piece of code,
say 100 lines or less. A cursory glance would usually tell you what it does. However, as
the code grows over time into several hundred or even a few thousand lines, which is
common, the code will inevitably become harder to read and more time consuming to
work with—even if that code was written by you just a few weeks ago! So, developing
the habit of writing well-formatted code from the beginning is imperative, even if the
code has just 10 lines or only does the simplest of things.
Several important aspects of readable code we’ve described so far have already been
covered in other, more focused chapters dedicated to these topics. In this chapter, we’ll
describe good practices to follow to make the code “look neat” in Section 10.1 through

Personal Copy for Andrei Arabolea, [email protected] 215


10 Formatting

Section 10.10. We’ll also describe the formatting of method parameters in great detail
in Section 10.11. Of course, you’re entitled to your own opinions after reading this chap-
ter, and some of you might disagree with some of these practices. In these cases, we
suggest that you discuss with your teammates and reach a consensus about what prac-
tices to follow because your teammates are the ones who will read your code. With
these goals in mind, let’s delve more deeply into several good formatting practices.

10.1 Consistency in Coding Style


One way to ensure well-formatted code in a project is if all its code is formatted in the
same way. This consistency in format ensures that each programmer working on the
project will require less time to read and understand the code since each programmer
has been writing the code in the same way. Consistency in style also makes maintain-
ability easy even when one team member is on a vacation or isn’t part of the team any-
more. In these cases, the other team members can understand the code much more
quickly than had each programmer used his or her own personal coding style. When an
outsider looks at the software, the code shouldn’t seem to be have been written by a
team of disagreeing contributors. Inconsistency just adds to the overall complexity
when trying to understand the intent of the code.
Many times, one project reuses the code written for another project, perhaps not
owned by you or your team. For new features or sometimes bugs, you might need to
modify the reused foreign code. In these scenarios, adhering to the project’s existing
formatting rules, rather than insisting on your own individual coding style, is critical.
In many scenarios, projects are handed over to other teams, for any number of reasons
such as the unavailability of resources, lack of funding, and so on. Sometimes, teams
may have to maintain, edit, and even develop new features within legacy code. In these
cases, we recommend that you look closely at the practices for handling legacy code we
discussed in Chapter 1, Section 1.3.

10.2 Optimizing for Reading


In many Hollywood movies, you might see scenes where coders are hacking away at
their computers (usually on a black background with green text), writing impressive
code within seconds, and doing some serious damage to the villains. As much as we’d
like to think we’re indeed that awesome, in reality, these stereotypes are serious mis-
representations of our community of coders. In truth, programmers spend most of
their day looking at code, reading it, hopefully understanding it, but usually frustrated
by it. They spend significantly less time actually writing code. Therefore, when writing
code, that code must be optimized for reading, not for writing.

216 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


10.3 The Pretty Printer

For example, you should write code neatly, as shown in Listing 10.1.

DATA:
a TYPE b,
c TYPE d,
e TYPE f.

Listing 10.1 Neat Code Example

And avoid writing code in messy ways, as shown in Listing 10.2.

DATA:
a TYPE b
,c TYPE d
,e TYPE f.

Listing 10.2 Messy Code Example

Both of these code fragments will run perfectly fine, in exactly the same way, and have
the same result because code is interpreted and executed by the machine. But code is
read, understood, and maintained by humans and therefore should be written in a
human-readable format. All the sections in this chapter are dedicated to formatting
your code in such a way that your code is optimized for reading.

10.3 The Pretty Printer


All lines of code in a software program should have a uniform appearance. For exam-
ple, the indentation and uppercase or lowercase for specific parts of the code should be
consistent. Every member of the team adhering to a coherent formatting style is vital.
The code may look messy if one programmer prefers uppercase for keywords and
another prefers lowercase. Each method or program would look different from one
another, and the uniformity of the software is lost. Thus, some common settings must
be decided upon, and strictly adhered to, by each contributor of the team.
Each programmer must maintain these agreed-upon settings by navigating to Menu 폷
Utilities 폷 Settings 폷 ABAP Editor 폷 Pretty Printer, as shown in Figure 10.1.
Under the Pretty Printer tab, you can specify whether indentation is required for
enclosed lines of code within a code block, such as in LOOP or IF statements, or whether
these lines should be displayed at the position in which the code was written, regardless
of the hierarchy. You can also specify whether all the characters should be uppercase or
lowercase, whether only keywords should be in uppercase or lowercase, and more.
After maintaining these pretty printer settings, your code can be beautified easily
by using the keyboard shortcut (Shift)+(F1) in Transaction SE80, Transaction
SE24, or ABAP Development Tools (ADT). In ADT, you could also use the shortcut
(Ctrl)+(Shift)+(F).

Personal Copy for Andrei Arabolea, [email protected] 217


10 Formatting

Figure 10.1 Maintaining Pretty Printer Settings

Once these pretty printer settings are agreed upon and followed by the team, then all
the code in the software will be consistent, and for readers, your code will be easy on
the eyes.
When working with foreign code or legacy code, as mentioned in Section 10.1, if some
part of the code is modified and beautified, then you might consider applying the
pretty printer only for new or modified code, rather than the entire codebase, to avoid
huge change lists and transport dependencies. Pretty printing an entire development
object should be undertaken in a separate transport request or note.

10.4 Number of Statements Per Line


Consider the code fragments shown in Listing 10.3 and Listing 10.4.

READ TABLE employees REFERENCE INTO employee WITH KEY location =


'London'. IF sy-subrc = 0. employee-region = 'UK'. ENDIF.

Listing 10.3 All Statements on One Line

READ TABLE employees REFERENCE INTO employee WITH KEY location = 'London'.
IF sy-subrc = 0.
employee-region = 'UK'.
ENDIF.

Listing 10.4 One Statement per Line

The blocks of code shown in Listing 10.3 and Listing 10.4 will execute in exactly the
same way because the machine doesn’t care how the code looks. The system only cares
about the syntactical and runtime correctness of the code. Both these code fragments
are correct, but the human readable format is the code shown in Listing 10.4. A key to
this readability is that the maximum number of statements per line is 1, not more, even
if the statement is short.

218 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


10.5 Line Length

10.5 Line Length


Consider the layout of text in a newspaper or a magazine article as an example. The
page is usually divided into several columns which each contain article content. One
reason for this column format is that text content on shorter lines is easier to read than
text on full-width lines. Imagine needing to keep moving your eyes to the right to read
the entire width of a newspaper! Your code editor’s window might not be as wide as a
newspaper, but this principle still holds true. The human eye is more comfortable read-
ing code when the lines are not too wide.
As a programmer, you’ll also appreciate narrower lines of code when debugging or
comparing two sources next to each other. A general rule of thumb is that, on an aver-
age laptop screen with text of average font size, if you must scroll horizontally, then
you’ve gone too far. If we had to mention the reasonable line length, we would suggest
a maximum of 120 characters. While you may often hear a recommendation for a max-
imum of 100 characters, 120 characters seems to work a little better for ABAP because
of the general verbosity of the language. For example, if your code block has an IF state-
ment with multiple long clauses that would take up the entire width of the screen (or
more), you should consider splitting the lines at each clause so that the statement is
more readable and easier to debug.
In ADT, you can configure the print margin to 120 characters, which can then be visual-
ized in the code editor as a vertical line. This configuration can be maintained by navi-
gating to Menu 폷 Window 폷 Preferences 폷 General 폷 Editors 폷 Text Editors. Select the Show
Print Margin checkbox and enter a number of characters in the Print Margin Column
field, as shown in Figure 10.2.

Figure 10.2 Configuring Print Margin in ADT

Personal Copy for Andrei Arabolea, [email protected] 219


10 Formatting

10.6 Condensing the Code


Blank spaces are essential to making code more readable. For example, when assigning
a value to a variable, a good practice is to maintain whitespaces to both the left and
right of the = operator. In fact, in ABAP, whitespace must surround assignment opera-
tors, and you must pad parentheses with spaces during method calls and brackets spec-
ifying table indices, as illustrated in the following code:

count = lines( my_table ).


row = my_table[ 3 ].

However, too many whitespaces and unnecessary blanks can also make your code less
comprehensible and prone to bugs. So, avoid writing the following code:

DATA(result) = calculate( items = list ) .

We recommend writing the same bit of code by removing the pointless additional
whitespaces, as in the following code:

DATA(result) = calculate( items = list ).

Notice how this statement is more succinct. Condensing the code by removing super-
fluous whitespaces is a simple way to improve the readability of your code.

10.7 Blank Lines


In the context of code readability, lines of code that are tightly related to each other by
logic should be vertically dense (i.e., grouped together; Martin, 2014), as shown in Listing
10.5.

READ TABLE my_table REFERENCE INTO row.


IF sy-subrc = 0.
do_this( ).
then_that( ).
ENDIF.

Listing 10.5 Logically Related Statements, Densely Placed

If two statements do different things, then adding a single line break between the state-
ments makes sense, as shown in Listing 10.6.

DATA(result) = do_something( ).

DATA(value) = do_something_else( ).

IF result = value.
do_this( ).

220 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


10.8 Alignment of Assignment Statements

then_that( ).
ENDIF.

Listing 10.6 Logically Different Statements, Placed with a Single Line Break among Them

However, no reason justifies adding multiple blank lines between statements of your
code, as shown in Listing 10.7.

DATA(result) = do_something( ).

DATA(value) = do_something_else( ).

IF result = value.

do_this( ).

then_that( ).

ENDIF.

Listing 10.7 Statements with Superfluous Blank Lines between Them

In fact, the urge to add multiple separating blank lines may indicate that your method
does more than one thing, as described in Chapter 4, Section 4.3.1.

10.8 Alignment of Assignment Statements


When multiple assignment statements are placed one after the other, the vertical align-
ment of assignment operators should only be performed if you need to emphasize that
the statements are somehow related to one another and belong together. Otherwise,
the attention is not on the intent of the statement.
For example, as shown in Listing 10.8, with vertical alignment, the focus is on the fact
that name, id, and location are the fields of the same structure employee, and therefore,
aligning the assignment operator = with one another makes sense.

employee-id = '4711'.
employee-name = 'Denise'.
employee-location = 'Los Angeles'.

Listing 10.8 Aligned Assignment Statements

Personal Copy for Andrei Arabolea, [email protected] 221


10 Formatting

In fact, a better way to represent these assignments is shown in Listing 10.9, where the
assignments for all the fields in the structure are performed in a single executable
statement.

employee = VALUE #( id = '4711'


name = 'Denise'
location = 'Los Angeles' ).

Listing 10.9 Aligned Assignments in a Single Statement

However, when assigning variables that don’t relate to one another, then you should
leave them unaligned, as shown in Listing 10.10.

customizing_reader = cust_obj_model_reader=>get_instance( ).
hdb_access = hdbr_access=>s_get_instance( ).
table = my_util_class=>get_table_data( ).

Listing 10.10 Unaligned Assignments for Unrelated Statements

10.9 Alignment of Variable Declarations


As with assignment statements, the alignment of variable declarations can also distract
the reader from the declaration of the variable at hand. A variable and its type go hand-
in-hand and should therefore be declared in close proximity, as shown in Listing 10.11.

DATA name TYPE seoclsname.


DATA reader TYPE REF TO db_reader.
DATA hdb_access TYPE REF TO hdbr_access.

Listing 10.11 Unaligned Variable Declarations

Aligning the variable declarations vertically draws attention away from the assignment
of the variable to its type and suggests that the variables form one vertical group, while
their types form another, as shown in Listing 10.12.

DATA name TYPE seoclsname.


DATA reader TYPE REF TO db_reader.
DATA hdb_access TYPE REF TO hdbr_access.

Listing 10.12 Aligned Variable Declarations

In addition to obscuring the actual intent of the code, vertical alignment also results in
more editing overhead, requiring you to adjust the indentations in all the variable dec-
larations whenever the length of the longest variable name changes or a new long vari-
able is declared.

222 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


10.11 Formatting Method Parameters

10.10 Placement of Closing Brackets


While calling methods with multiple parameters or assigning a structure with values
for multiple fields, we recommend adding the closing brackets at the end of the last
line, instead of placing it on a completely new line created just for that closing bracket.
In fact, this recommendation holds true in all cases where sets of brackets are used.
For example, Listing 10.13 shows how adding the closing bracket on a new line is visu-
ally distracting and not a good practice.

modify->update( node = if_alert_c=>node-item


key = item->key
data = item
changed_fields = changed_fields
).
employee = VALUE #( id = changed_fields-id
name = changed_fields-fullname
location = changed_fields-location
).

Listing 10.13 Closing Brackets on a New Lines

Instead, a more visually pleasing way to close bracketed code is to add the closing
bracket at the end of the concluding line, as shown in Listing 10.14.

modify->update( node = if_alert_c=>node-item


key = item->key
data = item
changed_fields = changed_fields ).

employee = VALUE #( id = changed_fields-id


name = changed_fields-fullname
location = changed_fields-location ).

Listing 10.14 Closing Brackets Placed on the Last Line

10.11 Formatting Method Parameters


One of the most frequently encountered statements in object-oriented ABAP is the
definition of a method and its respective calls. In fact, in a well modularized codebase,
methods are an integral part of achieving the objective of doing one thing as described
in Chapter 4, Section 4.3.1. Thus, formatting methods and their calls is imperative in
making code more readable. Up to this point, we’ve only discussed some rudimentary
ways to format code within methods. In this section, we’ll delve into formatting
method calls.

Personal Copy for Andrei Arabolea, [email protected] 223


10 Formatting

10.11.1 Single Parameter Calls


Let’s start our discussion of formatting method parameters by examining what should
be done while calling methods with only a single parameter. For more succinct and
readable code, a single parameter method call should be kept on a single line:

DATA(unique_list1) = remove_duplicates( list ).


DATA(unique_list2) = remove_duplicates( CHANGING list = list ).

Method calls with just one parameter should not be needlessly extended across multi-
ple lines, as shown in Listing 10.15.

DATA(unique_list1) = remove_duplicates(
list ).
DATA(unique_list2) = remove_duplicates(
CHANGING
list = list ).

Listing 10.15 Single Parameter Calls over Multiple Lines

10.11.2 Line Break Multiple Parameters


If a method call contains more than one parameter, then the de facto standard is keep-
ing each of these parameters and their values on a line of their own, for example, in
Listing 10.16.

DATA(sum) = add_three_numbers( value_1 = 5


value_2 = 6
value_3 = 7 ).

Listing 10.16 Multi-Parameter Method Calls

This style keeps the code readable even though more lines are required. Cramming all
the parameters on a single line would obscure where one parameter ends and the next
one begins, for example, in the following code:

DATA(sum) = add_three_numbers( value_1 = 5 value_2 = 6 value_3 = 7 ).

Notice how this code is messy and unreadable, and thus, adding more than one param-
eter on one line should be avoided.

10.11.3 Placement of Parameters


The parameter of a method and its argument (i.e., its value) are closely bound together.
Therefore, these elements should be placed near each other and on a single line.
If the parameters and their values are short, then the first parameter can be placed on
the same line as the method name, as shown in the following code:

224 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


10.11 Formatting Method Parameters

DATA(sum) = add_two_numbers( value_1 = 5


value_2 = 6 ).

If the parameter name or the value passed is too long, then you can break the parame-
ter into the next line, as shown in Listing 10.17.

DATA(sum) = add_two_numbers(
value_1 = 42 + round_down( 19 * step_size )
value_2 = VALUE #( ( `Calculation failed` ) ) ).

Listing 10.17 Long Method Calls

However, separating the parameter from its value is a poor practice. The example
shown in Listing 10.18 demonstrates how illegible the code can become if parameters
and their values are separated.

DATA(sum) = add_two_numbers(
value_1 =
42 + round_down( 19 * step_size )
value_2 =
VALUE #( ( `Calculation failed` ) ) ).

Listing 10.18 Separating Method Parameters and Their Values

10.11.4 Indenting Parameters


As discussed in Section 10.11.3, if the method parameter name or the value passed is too
long, then a good practice would be to break the parameters into the new line. How-
ever, the parameters should be kept tightly coupled with the method name. A good way
to ensure this coupling is to indent the parameters below the method name. An exam-
ple of this style is shown in Listing 10.19, which clearly indicates that value_1 and value_
2 are parameters and not some random assignment statements.

DATA(sum) = add_two_numbers(
value_1 = 42 + round_down( 19 * step_size )
value_2 = round_up( input DIV 7 ) ).

Listing 10.19 Indenting Parameters below the Method Name

Aligning the parameters elsewhere can obscure what element a parameter belongs to,
as shown in Listing 10.20.

DATA(sum) = add_two_numbers(
value_1 = 5
value_2 = 6 ).

Listing 10.20 Aligning Call Parameters Elsewhere

Personal Copy for Andrei Arabolea, [email protected] 225


10 Formatting

However, if the method parameter name might change in future, perhaps becoming
long, then this alignment would be the best way forward so that the formatting of other
parameters is not messed up in the future.

10.11.5 Vertical Alignment of Parameter Values


Although the vertical alignment of variable declarations and assignments is discour-
aged, vertical alignment is actually recommended for parameters and their values for
the following reasons:
쐍 Even though a method parameter and the value passed to it are closely coupled,
these elements are not individual statements by themselves; they are parts of a big-
ger statement: the method call.
쐍 Ragged alignment obscures where a parameter ends and its value begins, as shown
in Listing 10.21.

modify->update( node = if_alert_c=>node-item


key = item->key
data = item
changed_fields = changed_fields ).

Listing 10.21 Unaligned Parameter Names and Values

However, by vertically aligning the parameter names with their values, as shown in
Listing 10.22, notice how the call is more readable and parameter names are more dis-
tinct from their values and from each other.

modify->update( node = if_alert_c=>node-item


key = item->key
data = item
changed_fields = changed_fields ).

Listing 10.22 Aligned Parameter Names and Values

10.11.6 Breaking Calls to a New Line


If a method name gets too long and becomes hard to read, then readability can be
improved by breaking the method name from its return parameter, which is placed on
a new line, as shown in Listing 10.23.

DATA(some_super_long_param_name) =
if_some_annoying_interface~add_two_numbers_in_a_long_name(
value_1 = 5
value_2 = 6 ).

Listing 10.23 Long Method Name

226 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


10.12 Summary

10.11.7 Indents and Using the Tab Key


If a method call contains keywords, such as IMPORTING, EXPORTING, CHANGING, and so on, a
good practice is to indent the keywords by 2 spaces and indent the method parameters
by 4 spaces from the method name, as shown in Listing 10.24.

DATA(sum) = add_two_numbers(
EXPORTING
value_1 = 5
value_2 = 6
CHANGING
errors = errors ).

Listing 10.24 Indenting Keywords and Parameters

If the method call only contains parameters, then it’s a good practice to indent these
lines with 2 spaces, as shown in Listing 10.25.

DATA(sum) = add_two_numbers(
value_1 = 5
value_2 = 6 ).

Listing 10.25 Indenting Parameters

We recommend pressing the (Tab) key to indent, although the (Tab) key may add an
extra space. This situation usually occurs when an odd number of characters precedes
the current cursor location in the line with the method name.

10.11.8 Indentation of Inline Declarations


If the value of any method parameter must be declared inline, then follow the same
rules as for formatting a method parameter. This recommendation applies to the inline
declaration of structures, tables, or even objects. An example is shown in Listing 10.26.

DATA(result) = merge_structures(
a = VALUE #( field_1 = 'X'
field_2 = 'A' )
b = NEW structure_type( field_3 = 'C'
field_4 = 'D' ) ).

Listing 10.26 Formatting Inline Declarations in Method Calls

10.12 Summary
In this chapter, you learned some elementary ways to implement good, consistent for-
matting in your ABAP programming, such as the following best practices:

Personal Copy for Andrei Arabolea, [email protected] 227


10 Formatting

쐍 Maintain consistency in the coding style of a project.


쐍 Optimize code for reading, not for writing.
쐍 Use common pretty printer team settings and beautify your code before activation.
쐍 Only have one statement per line.
쐍 Stick to a reasonable line length, preferably 120 characters.
쐍 Condense your code and remove superfluous whitespaces.
쐍 Add only a single blank line if necessary, to ensure the logical separation of code.
쐍 Align assignments of properties and values only if they belong to the same element
(within a structure, table, or object).
쐍 Do not vertically align variable declarations.
쐍 Add closing brackets at the end of the concluding line, not in a separate line on their
own.
쐍 Keep a single parameter method call on one line with its parameter.
쐍 Keep the parameter and its value together in the case of multiparameter method
calls.
쐍 Place parameters of a method call close to the method name.
쐍 Indent call parameters with the method name when parameters are placed in the
line below.
쐍 Align parameters and their values while calling methods.
쐍 Break a method call to a new line if the method name is too long.
쐍 Maintain a constant indentation of 2 spaces for keywords and 4 for parameters in a
method call.
쐍 Align inline declarations of parameter values the same way as for a method call.

At this point, you should have a good idea why developing good code formatting habits
is so essential. You won’t work on a piece of code forever. One day, eventually, your
code could be maintained by another programmer. A little extra effort on your part will
make life way easier for the people who’ll debug or consume your code. This person
could be you, your colleagues, your successors, or anyone who just wants to look at
your code. It’s never too late to start adopting these practices. You can start right now!
In the next chapter, you’ll learn about the clean code guidelines to follow while han-
dling errors.

228 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Chapter 11
Error Handling
In every process or program, unforeseen situations or even errors can
arise. Highly used program code must especially be resilient to error and
should not exit unexpectedly. Handling errors should be a crucial consid-
eration during development.

Feature development is typically focused on implementing a new feature and its


behavior. Error handling should be treated with the same care as the feature code, as
error handling covers critical situations that, if not handled correctly, result in incon-
sistencies. Therefore, not only the process should be tested automatically, but also
error handling should be similarly automated. Ideally, the code should be so robust
that manual testing cannot break it.
This chapter focuses on error handling within ABAP. We’ll first cover message handling
in general in Section 11.1 and commonly used return codes in Section 11.2. Then, in Sec-
tion 11.3, we’ll introduce you to exceptions, which in ABAP have multiple meanings.
Most of this chapter will cover the usage of class-based exceptions, showing you how to
throw these exceptions and what to consider in Section 11.4, including how to handle
foreign exceptions.

11.1 Messages
Messages are an integrated part of the ABAP language. Especially in older programs
written as SAP GUI applications, messages are heavily used. When a MESSAGE statement
is executed, the runtime displays the text of the messages directly on the screen.
Depending on the type the message was raised with, the position on the screen could
deviate between a messages row in the status bar at the bottom of the screen, a popup
window, or a termination message. Messages can be raised with different types that are
identified by an uppercase character. The possible values and their message types are
defined in the following way:
쐍 A
The message is displayed, and the program is terminated and aborted.
쐍 E
Indicates an error in the processing. If the process is not executed in the background,
without any UI available, the current screen cannot be exited, and input fields are
ready for inputs to correct the error.

Personal Copy for Andrei Arabolea, [email protected] 229


11 Error Handling

쐍 I
An information message displayed in an additional popup window.
쐍 S
A status message displayed with a green checkmark, also known as a success mes-
sage.
쐍 W
Displays the message as a warning.
쐍 X
Message of this type exits the current program with a short dump.

In general, messages in ABAP are organized in messages classes, which are sets of indi-
vidual messages. Message classes do not necessarily share the same context, and one
class can consist of messages from multiple contexts. However, the message class is
assigned to a package and is a transportable object in itself. We recommend using a
message class for one context only. The message class’s name is often also referenced
as message ID or just ID. Within a message class, a message is assigned a number
between 000 and 999. The numbers don’t need to fill up consecutively, and thus gaps
can provide the opportunity for additional structuring within the message class. For
example, the message class for messages in the context of a sales order could use the
message numbers from 000 to 099 for general messages, while the number range
from 100 to 199 could be for messages that can be assigned to the root node, and the
range from 200 to 299, for the item node. Another way to organize messages is creating
a new message class for each context.
The messages identifiable by message number within the message class must have a
short text up to 73 characters. Additionally, if the message is not marked as self-
explanatory like the first message in the example shown in Figure 11.1, a long text can
be provided to explain the reason for the message and how to handle it.

Figure 11.1 SAP GUI Message Maintenance

This level of detail is probably not possible in the short text alone. However, the short
text is the text that is used most of the time and therefore should be written as precisely

230 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


11.1 Messages

as possible. Like naming conventions, making the short text better is time well spent.
Both the short text and the long text can be translated into different languages, which
is performed by the MESSAGE statement during execution.
A message not only consists of static texts; it can also be enhanced by variable values.
A maximum of four variables can be added to a text, and each value can have up to 50
characters. Listing 11.1 shows an example with two variables. Variables in general are
added to the text by the numbers 1 through 4 escaped by an ampersand (&).

Material &1 cannot be used in item &2.

Listing 11.1 Sample Message Text with Two Escaped Variables

The MESSAGE statement was introduced at a time when the language was primarily used
for SAP GUI applications. Therefore, using this statement in applications was conve-
nient because the message that is raised is shown directly on the screen. Today, the
MESSAGE statement is still in use, especially in older applications, but is less convenient
now because it has direct integration to the SAP GUI processor and does not work in a
unit test environment. Thus, writing a unit test for code that raises messages directly is
quite difficult. To enable the code for unit testing, the statement itself must be
extracted into a different class that can be exchanged using dependency injection (see
Chapter 3, Section 3.3.2).
This issue with the MESSAGE statement is not only present with unit tests. When the first
programs were wrapped by a function module that can be called remotely or in the
background, the immediate display of messages was not useful. To avoid reimplement-
ing all functionalities, the CALL FUNCTION statement was enhanced by a fixed addition to
the EXCEPTIONS part of the statement. If the addition error_message is used, messages
won’t interact with SAP GUI. Instead, they fill out the typical fields in the sy structure,
which are msgid, msgno, msgty, msgv1, msgv2, msgv3, and msgv4. These fields contain all nec-
essary information to process the raised message. How many of the four variable fields
are taken into account is defined in the message itself. If the message doesn’t have any
placeholders, the value in these fields are not relevant. If the function module raises an
exception in addition to the message, the sy-subrc is set to a specified value, which can
be used for the error handling after the function is processed, as shown in Listing 11.2.

CALL FUNCTION 'PROCESS_ITEM_CHECKS'


EXPORTING
item = item
EXCEPTIONS
error_messages = 1
others = 2.
IF sy-subrc EQ 1.
message_processor->raise_message(
id = sy-msgid

Personal Copy for Andrei Arabolea, [email protected] 231


11 Error Handling

number = sy-msgno
type = sy-msgty ).
ENDIF.

Listing 11.2 Call Function Using error_messages

Which value is assigned to the sy-subrc is defined by the caller of the function module.
For each exception, the caller can define a numeric value so that the different excep-
tions can be differentiated in the error handling.
Up to this point, our discussion regarding messages was not specific to the clean code
principles in ABAP. Clean code does not change the use of messages nor their types.
However, the original usage of the MESSAGE statement is reduced because error handling
is more focused towards exceptions. Message classes are still used for organizing mes-
sages and the MESSAGE statement is still relevant but is mostly used to fill in system
fields. For end user-facing SAP GUI development, the MESSAGE statement is still valid but
might get results from a reusable component.
To work with messages cleanly, they must be easy to find, for instance, in the where-
used list. Unfortunately, if a message is not used in conjunction with its respective
statement, the where-used list will not find all occurrences. If, for example, a message
is added to an exception or a list of raised messages, the message ID and number are
interpreted as characters and numbers. One way to avoid missing messages in the
where-used list is to use the message statement. However, instead of raising the mes-
sage directly to the UI, the addition INTO can be used to transfer the resulting message
text into a dummy variable just to fill in the system fields. The variable is often called
dummy because it’s never used. The system fields can then be used to add a message to
a message container. Another way to avoid the missing link is by organizing message
numbers for a message class in an enumeration class (see Chapter 6, Section 6.2.2). At
all instances where the message is raised, the reference to the enumeration class must
be used. With this approach, the where-used list on the constant finds all occurrences.
Additionally, the name of the constant could add more information than just a 3-digit
number, which makes the code easier to understand when read by someone else.

11.2 Return Codes


Functions and methods can have multiple IMPORTING and EXPORTING parameters in
ABAP. Some EXPORTING parameters are not designed to the return values calculated
during processing, but rather will only indicate whether processing has executed cor-
rectly. These special EXPORTING or RETURNING parameters are called return codes. We’ll
examine how return codes relate to exceptions in the following sections, as well as how
to handle failures.

232 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


11.2 Return Codes

11.2.1 Exceptions versus Return Codes


There are different ways to indicate that a function has executed correctly. Of course,
these indicators can have different causes, but what’s important is that the calling code
is informed to act accordingly. ABAP itself uses a kind of return code for many state-
ments. For example, when the READ TABLE statement fails to find an entry based on the
given key, no error is raised. Instead, the field sy-subrc is set to a specific value based on
the situation that occurred. For a record that was not found, the value 4 is typically
used. The exact values must be extracted from the documentation. Working with sy-
subrc is common for ABAP developers and is critical for writing robust code.
Function modules are not ABAP statements but can be developed to execute some
logic. As in other coding, during the execution of the module, errors can appear. To
inform the caller about the situation, function modules allow you to raise exceptions.
These exceptions share the same names as objects in other languages but are not based
on the same concept. In ABAP, we must distinguish between classic exceptions and
class-based exceptions. In ABAP, classic exceptions can be used in combination with
more than just function modules and are the focus of this section. Classic exceptions
are defined in the signature of a function module to indicate different error situations.
For the caller of the function module they act as a return code, because they are con-
verted into numeric values. On the other hand, class-based exceptions are more com-
mon today and are more like exceptions in other languages. Section 11.3.2 will cover
class-based exceptions in more detail.
ABAP exceptions can be raised by function modules and methods. However, the usage
of classic exceptions in methods is uncommon because class-based exceptions had
already been introduced, so until development with methods became common, there
was no need to fall back to the classic exceptions. Both options coexist for compatibil-
ity reasons but should not be used together. ABAP exceptions are defined within the
object that uses them and cannot be reused. Additionally, exceptions do not provide
more functionality than having a specific name and the ability to add more informa-
tion in a long text similar to the message explained in Section 11.1.
If a function module uses exceptions, they must be handled by stating their name fol-
lowed by a numerical value. This value is used to fill in the sy-subrc field, which can be
then used to determine which exception was raised. More generically, the predefined
exception others is available for all classic exceptions, and the value assigned to OTHERS
is used for every exception that is not explicitly mentioned. The program will be termi-
nated if a raised exception cannot be caught explicitly or by others. Thus, all function
module calls should have the generic block to avoid terminations. An example func-
tion call is shown in Listing 11.3.

METHOD get_address.
CALL FUNCTION 'ADDR_GET'
EXPORTING

Personal Copy for Andrei Arabolea, [email protected] 233


11 Error Handling

address_number = address_number
IMPORTING
address_value = address_value
EXCEPTIONS
number_not_found = 1
others = 2.
IF sy-subrc = 1.
RAISE EXCEPTION NEW not_found( ).
ELSEIF sy-subrc <> 0.
RAISE EXCEPTION NEW undefined_error( ).
ENDIF.

Listing 11.3 Call Function with Others

Classic exceptions are a type of return code where the value of the return code is
defined by the caller, which then can group some exceptions if necessary, but they
need to be categorized as return codes.
We recommend using, wherever possible, class-based exceptions instead of classic
ABAP exceptions or return codes since class-based exceptions are harder to ignore and
make the processing more robust.

11.2.2 Handling Failures


As described in the previous section, one way to indicate incorrect processing is intro-
ducing a return code. To react to the different outcomes of the function, the return
code must be analyzed by the calling code, thus introducing additional dependencies
between the two functions. Differentiating on a more granular level means introduc-
ing more dependencies. A return code can have multiple values, each indicating differ-
ent situations, like processing being skipped as not relevant or indicating that
processing has failed. The failed indicator could even have different values to model
different errors to enable the caller to react based on a specific error. In both cases, the
result of the method is the return code and possibly other EXPORTING parameters that
are not filled.
In any case, there’s no check to confirm that the return code has been analyzed and
checked if the program needs to exit due to an error. Errors that are only shown in a
return code are easy to ignore, and errors can slip through without being handled.
Additionally, if the set of possible return codes changes or is increased, the calling
methods must be adapted since they have also a dependency on the return codes and
must adapt accordingly.
Again, the previously mentioned sy-subrc field, which is a kind of return code, has a
special behavior. Even though the runtime doesn’t check whether the return code of a
function module is syntactically correct, an extended check indicates that the sy-subrc

234 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


11.2 Return Codes

field is not checked. The example shown in Listing 11.4 ignores all raised exceptions. In
any case, correct or failed processing, the same code is executed.

CALL FUNCTION 'RAISE_EVENT'


EXPORTING
event_name = 'Order_created'
object_id = order_id
EXCEPTIONS
others = 0.

Listing 11.4 Call Function Ignoring All Exceptions

Some measures can be taken to prevent the sy-subrc return code from being missed.
For example, the extended code check, which raises warnings if the sy-subrc is not eval-
uated after a function call.
Not only built-in statements and function modules work with return codes. Methods are
technically also capable of raising classic exceptions, although the same problem exists.
Fortunately, there are only a few occasions for classic exceptions in object-oriented
ABAP, as we discussed previously. Note, however, that some methods are designed to
use a RETURNING parameter as a return code, which does not follow the recommendation
to use class-based exceptions. Class-based exceptions are the best way to indicate pro-
cessing errors.
Many function modules are still relevant in new coding. If these modules fulfill their
requirements, reimplementing them using class-based exceptions is not a good invest-
ment. Thus, the value that lies within the modules must be used while increasing their
quality and the safeguarding measures like unit tests. Listing 11.5 shows a simple exam-
ple of a method that only contains the call function, providing the IMPORTING parame-
ters of the function call. If the function module raises an exception, the method
evaluates the sy-subrc and raises a class-based exception. The conversion of the classic
exception to a class-based exception forces the caller to react.

METHOD raise_event.
CALL FUNCTION 'RAISE_EVENT'
EXPORTING
event_name = event_name
object_id = object_id
EXCEPTIONS
others = 1.
IF sy-subrc <> 0.
RAISE EXCEPTION NEW cx_raise_event( ).
ENDIF.
ENDMETHOD.

Listing 11.5 Wrapping a Function Module

Personal Copy for Andrei Arabolea, [email protected] 235


11 Error Handling

Wrapping function modules in methods reduces the risk of mishandling an error that
might occur. Additionally, the method should be part of an interface so that the calling
method can replace the productive implementation with a mock implementation to
break dependencies. Many function modules have enormous signatures since they do
more than one thing. When wrapping a function module, its various capabilities can be
broken down into different methods, hiding the complexity of the function module
behind clear methods. Splitting one module into more than one method enables callers
to follow clean code principles and use well-named methods with clear scopes. How-
ever, function modules require a deeper understanding to correctly define their scopes.

11.3 Exceptions
As the term indicates, exceptions describe situations that are not expected during nor-
mal processing. However, exceptions are particularly important as ways to communi-
cate an unexpected situation and forcing the caller to react. This section provides
details on what exceptions are designed for and how they should be defined. We’ll dis-
cuss several options when defining exceptions, and each option will be described in
detail in this section.

11.3.1 Exceptions Are for Errors


In the ABAP language, the term “exception” is unfortunately ambiguous since so-called
“classic exceptions” are different from “class-based exceptions,” as we discussed in Sec-
tion 11.2.1. However, since development has moved towards the use of classes in all
areas where technically feasible, the term “exception” is definite and means class-
based exceptions.
This section focuses on how exceptions fit into the flow of your code. The next section
specifically covers class-based exceptions, whereas some aspects we discuss in this sec-
tion can be applied to classic exceptions as well. Nevertheless, our main focus in this
chapter is to teach you how to work with class-based exceptions.
Even though both types of exceptions differ significantly, they have a common pur-
pose: covering error cases. An error case, in the context of this book, is a situation that
is not part of the normal processing of a function or method. Therefore, an error is not
only a failed calculation but can also refer to situations where not all the necessary
information was provided or the input was wrong. All these situations force the current
processing to be interrupted. This information should be made transparent to the
caller. Based on the type of error, the caller might be able to proceed or again need to
inform their caller.
By definition, exceptions of any kind are designed to cover situations that interrupt
processing and excludes using exceptions for different purposes. Therefore, using a
concept designed for errors to cover normal processing is not recommended. From an

236 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


11.3 Exceptions

outside perspective, it’s not transparent what consequences result from an exception.
A component, for example, can have an internal state to store that an error has
occurred and that some cleanup needs to be performed before processing can resume.
If this component is involved multiple times, unforeseen side effects may occur when
an error is forced. Using exceptions in the normal process flow, as in the example
shown in Listing 11.6, can also be confusing for the reader.

TRY.
read_order_from_db( order ).
CATCH INTO DATA(not_found_exception).
persist_new_order( order ).
ENDTRY.

Listing 11.6 Anti-Pattern: Exceptions Used in a Normal Flow

Since exceptions are normally not part of the flow, the first assumption is that an error
was raised when something went wrong, even though this is not the case. Listing 11.7
shows what happens when we remove the unnecessary exception, which leads to
increased readability and performance since the runtime did not need to create an
exception instance.

IF order_not_persisted( order ).
persist_new_order( order ).
ENDIF.

Listing 11.7 Pattern: Check Instead of Forcing Errors

Catching an exception should not be part of the regular process flow and should be
used for errors exclusively. Error handling and working with exceptions is a crucial part
of software development and should be limited to the area for which this capability is
designed.

11.3.2 Class-Based Exceptions


After our more general explanations regarding exceptions, this section focuses on
class-based exceptions in more detail. In contrast to the classic exceptions, class-based
exceptions are similar to the well-known exceptions used in other object-oriented lan-
guages, like Java.
In general, an exception is a class that has the root exception class cx_root as its super-
class. Inheriting from the abstract class provides some common features that are nec-
essary for efficient exception handling. In Section 11.1, messages were introduced as a
crucial part of the language, which continues in the implementation of the exception
superclass. The exception root class provides reusable methods to convert messages
into language-dependent text without any further development effort. Besides one

Personal Copy for Andrei Arabolea, [email protected] 237


11 Error Handling

root class, exceptions in ABAP have three possible types, which are defined, via inheri-
tance, from one of the following defining classes:
쐍 CX_STATIC_CHECK
쐍 CX_NO_CHECK
쐍 CX_DYNAMIC_CHECK

The characteristics of and the differences between the three types are explained in
detail in their corresponding sections that follow. Since these exceptions are abstract,
more specific exceptions must be written to raise them. Additionally, you should have
explicit exceptions specific to the context to add some perspective and reusable meth-
ods to help in analyzing errors or provide common conversions.
Class-based exceptions can only be raised by methods. In contrast to methods, which
can throw classical and class-based exceptions, function modules are limited to the use
of classic exceptions. However, whenever possible, using class-based exceptions is
preferable.
A special exception in ABAP is the area of T100 messages. In general, messages are an
integral part and are already defined for several situations, most of them being also
messages for error situations. When using exceptions, there must be a way to map
messages to exceptions. Instead of defining an exception for every message, messages
can be added to an exception. In this way, the development object message class, which
contains the message text, can be converted to an exception containing the message
text. Not every exception needs to be a T100 exception, since messages are not relevant
for all exceptions. To enable an exception for this feature, the exception class must
have the interface IF_T100_MESSAGE. Adding this interface enables the Texts tab in the
Class Builder. An example of class cx_root is shown in Figure 11.2.

Figure 11.2 Exception with Enabled Texts Tab

New public constants are generated that represent a text by grouping the message
class, the message numbers, and the placeholders together in one constant structure.
The placeholders for these exceptions are mapped to public attributes that are also
generated into the exception constructor and can be set when creating an instance.
During retrieval of the exception’s text, the values from the instance attributes are
taken and added to the message text.

238 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


11.3 Exceptions

In the development environment, ABAP Development Tools (ADT), another approach


is available for working with text IDs. To add a new constant structure, place your cur-
sor into the public section and type “textid” followed by the code completion by press-
ing (CTRL)+(SPACE). The template textIdExceptionClass generates a constant structure
containing all attributes that are necessary for reference, as shown in Figure 11.3.

Figure 11.3 Code Completion for textid

The generated values for the different attributes must now be replaced with the correct
values. MSGID and MSGNO must be set according to the message that you want to raise. For
the four attributes representing the variables in the message, you are expected to name
a member attribute of the exception class or leave it blank. The member attribute that is
assigned to one of the variables should be defined as public and read only, as follows:

DATA order_id TYPE order_id READ-ONLY.

To fill in the value of this attribute, the constructor must be enhanced. This change
should not be done manually! In ADT, a quick fix is available. Place your cursor on the
constructor and press (CTRL)+(1). Then, select Re-generate constructor, as shown in
Figure 11.4.

Figure 11.4 Regenerating a Constructor in ADT

Personal Copy for Andrei Arabolea, [email protected] 239


11 Error Handling

The attribute is automatically added to the signature of the constructor, and the attri-
bute name can be used as an attribute in a text ID. The final form of the class after cre-
ating a new text ID, adding an attribute, and regenerating the constructor is shown in
Listing 11.8.

CLASS cx_order DEFINITION PUBLIC


INHERITING FROM cx_static_check FINAL CREATE PUBLIC .
PUBLIC SECTION.
INTERFACES if_t100_dyn_msg .
INTERFACES if_t100_message .
CONSTANTS:
BEGIN OF cx_example,
msgid TYPE symsgid VALUE 'ORDER',
msgno TYPE symsgno VALUE '003',
attr1 TYPE scx_attrname VALUE 'order_id',
attr2 TYPE scx_attrname VALUE '',
attr3 TYPE scx_attrname VALUE '',
attr4 TYPE scx_attrname VALUE '',
END OF cx_example.
DATA order_id TYPE vbeln READ-ONLY.
METHODS constructor
IMPORTING
textid LIKE if_t100_message=>t100key OPTIONAL
previous LIKE previous OPTIONAL
order_id TYPE vbeln OPTIONAL.
PROTECTED SECTION.
PRIVATE SECTION.
ENDCLASS.

CLASS cx_example IMPLEMENTATION.


METHOD constructor ##ADT_SUPPRESS_GENERATION.
super->constructor( previous = previous ).

me->order_id = order_id.

CLEAR me->textid.
IF textid is initial.
if_t100_message~t100key = cx_example.
ELSE.
if_t100_message~t100key = textid.
ENDIF.

ENDMETHOD.
ENDCLASS.

Listing 11.8 Exception Class with textid

240 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


11.3 Exceptions

One exception can also have multiple texts from different message classes. As a result,
you can design exceptions that are more tailored to the clean structure while still en-
couraging the reuse of messages. The generated constant structures are then used as ref-
erences when creating a new exception instance. It is not necessary to know which mes-
sage class or number is assigned to raise it. However, if there are multiple texts created
within the same exception, there is a high probability that several attributes are neces-
sary to cover all possible placeholders. By increasing the number of attributes, the IM-
PORTING parameters of the constructor are also increased. The advantage in abstracting
the message behind a constant text ID is lost because the caller needs to know which at-
tributes are relevant, thus increasing dependencies.
Exceptions working with T100 text IDs must be designed carefully. Otherwise, the excep-
tion will be overloaded and cannot be used cleanly because too many dependencies are
created. Handling an exception must be as easy as possible to encourage better error
handling without cluttering the code with only error handling.
While T100 messages are designed for new exceptions specializing on a few messages
that share the same variables, the interface IF_T100_DYN_MSG can be added to excep-
tions to improve their compatibility with messages. The interface is designed to work es-
pecially well with classic exceptions raised by function modules or methods. Listing 11.9
shows how a classic exception from a function module can be converted into a class-
based exception by using the MESSAGE addition to the RAISE EXCEPTION statement. The sys-
tem structure sy has 5 fields for message handling. The field msgty stores the type with
which the message was raised. The message ID and number are stored in the fields msgid
and msgno. Additionally, the fields msgv1, msgv2, msgv3, and msgv4 store the attributes that
were added when the message was raised. The fields are always filled in when the MESSAGE
statement is executed, even when the result is stored into a variable that is never used.

METHOD read_address.
CALL FUNCTION 'ADDRESS_GET'
EXPORTING
address_id = address_id
IMPORTING
complete_address = complete_address
EXCEPTIONS
address_invalid = 1
error_message = 2.
IF sy-subrc <> 0.
RAISE EXCEPTION TYPE cx_function_call
MESSAGE ID sy-msgid
TYPE sy-msgty
NUMBER sy-msgno
with sy-msgv1 sy-msgv2 sy-msgv3 sy-msgv4.
ENDIF.
ENDMETHOD.

Listing 11.9 Convert Function Module Exception to T100 Dynamic Exception

Personal Copy for Andrei Arabolea, [email protected] 241


11 Error Handling

In contrast to T100 exceptions, the dynamic interface reduces the overhead to define
each message that should be raised statically. A class-based exception must be tailored
to the callers’ needs and not based on internal details or components. With T100 excep-
tions, the caller can identify a specific error message by comparing the text ID within
the exception to the constant values. Referencing the text ID introduces a new depen-
dency to a specific exception class. Therefore, T100 messages should be not used in
external interfaces, only internally to reduce dependencies.
In the end, defining a set of exceptions that enables the caller to handle error situa-
tions, while following a clean style of code, is vital. In the next three sections, the three
general exception types are explained in detail.

11.3.3 CX_STATIC_CHECK
The first type of exception is checked statically at design time by the syntax check. An
exception class is relevant for the static syntax check when it inherits the class CX_STAT-
IC_CHECK. If the method has identified a situation where it is necessary to throw an
exception, the exception must be declared in the method’s signature. It doesn’t make
sense to first raising an exception and then directly catch it in the same method. Thus,
a method that raises a statically checked method must declare it in its signature. Other-
wise, the syntax check will raise a warning message. A method that is calling another
method that has a statically checked exception in its signature can catch the exception
to handle it and break the propagation chain. Breaking the propagation chain exempts
the method from declaring the exception in its own signature. If the exception should
not be handled directly and instead be propagated to the next level, the method signa-
ture must declare that exception, as shown in Listing 11.10.
However, when the exception is not handled or propagated, the syntax check raises a
warning since the exception was defined in a way that cannot be ignored. Even if the
calling method checks any error situation beforehand, the exception must be handled.

CLASS cx_invalid_user_name DEFINITION


INHERITING FROM cx_static_check.

METHODS read_by_user name


IMPORTING
user_name TYPE string
RAISING
cx_invalid_username.

Listing 11.10 Method Signature Raising Static Exception

Code that contains syntax warnings can be activated and might run as expected.
However, if the code is not prepared to handle an expected exception, the program
will likely terminate at some point in time. As written in the documentation, a syntax

242 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


11.3 Exceptions

error resulting in not activatable code was deemed too harsh for unhandled exceptions.
With a warning instead, code can be developed quickly, for instance, in a proof of con-
cept (POC) where handling all possible exceptional situation is not a high priority. Pre-
venting this type of code from being transported into delivery or productive
environments can be achieved by using the ABAP Test Cockpit. The ABAP Test Cockpit
typically treats missing exception handling as an error with very high priority, thus pre-
venting the transporting of this code.

11.3.4 CX_NO_CHECK
The second way to define an exception is to inherit from the CX_NO_CHECK class. Excep-
tions that inherit from this class have a different behavior than the statically checked
class. From a design-time perspective, exceptions of this type could not be propagated
by adding it to the method signature. This feature was added with SAP NetWeaver
Application Server for ABAP (SAP NetWeaver AS for ABAP) 7.78/7.55. Therefore, the call-
ing method can be informed about the possibility to raise this type of exception but
not handle it and not raise syntax warnings. In contrast to the static check exceptions,
the developer is not prompted by a warning to catch the exception.
The result of this missing prompt is that, in most cases, the code does not need to be
prepared to handle the exception at runtime. However, when the called method iden-
tifies a situation from which it cannot recover, it will throw the exception. Typically,
the calling method can either handle the exception by itself by catch or propagating
the exception at design time. Propagating this type of exception is impossible as this
exception cannot be added to the signature, and preparing a CATCH block for an excep-
tion that is not part of the signature is both unlikely and unnecessary. Thus, raising a
no-check exception results in a termination in most cases unless you include a generic
CATCH block for cx_root that handles all exceptions.
There are situations where it does make sense to terminate further processing because
the caller will not be able to recover. For example, if there is not enough memory avail-
able to process further, a no-check exception makes sense. Hopefully, the situation that
not enough memory is available is rare. Adding a statically checked exception to the
signature forces every consumer to handle the exception in one way or the other, and
the code of all consumers is thus cluttered unnecessarily. The consumer is not able to
provide further memory anyway, and the program is terminated. In these cases, no-
check exceptions remove useless code. In case the exception is raised as resumable, the
resumability is preserved for no-check exceptions in contrast to static or dynamic
exception. Resumability in general is explained in Section 11.4.2.
However, in some unique cases, raising a no-check exception may not be as critical as
the developer would expect. For example, while running a unit test, a dependency that
can’t be resolved might be tolerable. Therefore, no-check exceptions can be caught,
avoiding the runtime termination.

Personal Copy for Andrei Arabolea, [email protected] 243


11 Error Handling

A no-check exception should be a conscious decision towards most consumers not


handling the exception. Therefore, no-check exceptions should only be used for unre-
coverable situations, not for situations where a knowing consumer has a possibility of
recovering. Still, there’s another area where no-check exceptions can be used as inter-
nal exceptions within a component. This approach is explained in detail in Section
11.4.1.

11.3.5 CX_DYNAMIC_CHECK
The third option for defining an exception is inheritance from the exception class CX_
DYNAMIC_CHECK. This type is in some ways a combination of the previous two options. On
one hand, the exception must be added to the method’s signature to be able to throw
it, which is the same behavior with statically checked exceptions. On the other hand, no
warning from the syntax check is raised if the calling code is not propagating or han-
dling the exception.
Again, the missing syntax warning can reduce the amount of code necessary to call the
method. As the exception is added to the signature, the caller can be prepared either by
handling the exception directly or propagating the exception. This was the main differ-
ence to no-check exceptions since the caller could recognize the potential of an excep-
tion by inspecting the signature and then can decide whether handling the exception
make sense. The syntax check did not force the handling of the exception.
System exceptions make use of this type regularly. If these exceptions would be stati-
cally checked, ABAP code would be flooded with exception handling for system excep-
tions, which would make it hard to find the actual coding. However, in some situations,
handling the exception might be appropriate. Most of these exceptions are avoidable
by checks confirming that prerequisites for the execution are met. For instance, the
exception CX_SY_REF_IS_INITIAL can easily be avoided by checking that an importing
reference variable is not initial; forcing everyone to take care of this exception is not
feasible. The exception CX_SY_DYN_CALL_ILLEGAL_METHOD is an example where catching
an exception and continuing the execution, as shown in Listing 11.11, might make
sense.

METHOD call_plugin.
TRY.
CALL METHOD (class_name)=>(method_name).
CATCH cx_sy_dyn_call_illegal_method.
set_not_relevant( ).
ENDTRY.
ENDMETHOD.

Listing 11.11 Anti-Pattern: Catch System Exceptions

244 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


11.4 Raising and Catching

If plugins and their classes are not available in a system, this scenario may still be valid,
and the program should not be terminated. However, a better approach is shown in
Listing 11.12, which checks first if the plugin is deployed and only then calls the method
dynamically.

METHOD call_plugin.
IF plugin_is_relevant( ).
CALL METHOD (class_name)=>(method_name).
ENDIF.
ENDMETHOD.

Listing 11.12 Check First Instead of Exception

The system exceptions make use of dynamically checked exceptions because it makes
sense in their case. Additionally, you can handle these exceptions, but system excep-
tions mostly represent severe issues and should not be part of normal execution. Fur-
thermore, nearly all system exceptions can be caught by just handling CX_DYNAMIC_
CHECK; however, we don’t recommend this approach, which can let severe errors slip
through that should terminate the execution. Additionally, severe development issues
might be obscured. Error situations should be treated as such.

11.4 Raising and Catching


When working with class-based exceptions, designing how the exceptions are struc-
tured is only one aspect to consider. Two other aspects of exceptions are throwing and
catching them. An exception is thrown when the code detects a situation where further
processing is not possible. An exception is then raised, and the caller must deal with it.
The caller either takes care of catching the exception and handling the situation or is
passing the exception to its own caller. This section describes how to work with excep-
tions either when throwing or catching them.

11.4.1 Exception Superclass


The main decision for exceptions is the definition of the correct superclass. As
described in the previous section, the fundamental behavior and the reaction of the
runtime is defined by inheritance. The goal is to have a set of exceptions that enables
the caller to react to exceptions properly, while not forcing them to clutter the code
with exception handling that may not be relevant in their context.
Additionally, while defining exceptions should be taken seriously, this is only one part
of writing quality code. The focus still lies on productive code that should not be taken
over by error handling. For this purpose, the ideal exception would be defined in the
raising part of a method definition and then travel upwards through the call stack until

Personal Copy for Andrei Arabolea, [email protected] 245


11 Error Handling

the exception is handled at some point without every method in between adding the
exception explicitly to the signature. Robert C. Martin advocates for the use of the
unchecked exceptions in Java so as to not force several layers to adapt their signatures
if exceptions must be handled at a different level (Martin, 2014).
In ABAP, you can now add no-check exceptions to the signature as well. Statically
checked exceptions require that the exception be added to the signature and are either
caught or propagated by the calling methods. If the syntax warning is ignored, still the
exception triggers a CX_SY_NO_HANDLER exception in the upper layer if the original
exception inherited from CX_DYNAMIC_CHECK. Dynamically checked exceptions only
avoid the syntax warning; the system exception is still triggered if the exception is not
caught. From a naming perspective, exceptions of the no-check type in ABAP resemble
unchecked exceptions in Java.
Which exception type should be used depends on the ABAP version you’re using. If you
have a version lower than SAP NetWeaver AS for ABAP 7.78/7.55, no-check exceptions
are limited. You cannot add exceptions to a method signature, and thus, you’ll miss an
important feature, since the caller of a method will not expect exceptions.
None of the available exception types match the description of an ideal situation.
Therefore, no single exception should be used for every type of error. However, in most
cases that arise during application development, statically checked exceptions are
commonly used. The effort to delegate an exception by adding it to the signature is
seen as more acceptable than being surprised by no-check exceptions and their side
effects. These exceptions are typically used for manageable exceptions, for instance,
when an end user enters an incorrect input, resulting in an error that the user can cor-
rect.
In an environment with SAP NetWeaver AS for ABAP 7.78/7.55 or higher, the recom-
mendation changes to using CX_NO_CHECK as the exception superclass. The important
step is adding the component-specific subclass to the method signature for the meth-
ods that raise it. The consumer is informed that the exception might appear but is not
forced to handle it directly. As soon as the exception is exposed in the signature, ADT
offers a quick fix to surround the method call with a TRY – CATCH block or add it to the
raising part of the signature. The third option possible with no-check exceptions is to
do nothing and let a higher layer handle the exception. In contrast to the other excep-
tion types, when a no-check exception is raised, it does not require immediate han-
dling. The exception can be passed through each layer until some layer catches it or the
entry point is reached, and the execution is terminated. For no-check exceptions, the
direct caller does not need to handle the exception, but someone must, to avoid a ter-
mination. This scenario reduces the effort and noise required for restating an excep-
tion in each raising part. However, you must ensure that the exception is caught in
some layer to avoid terminations and to perform proper error handling.

246 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


11.4 Raising and Catching

Dynamically checked exceptions are best used for exceptions that must be raised for
avoidable error situations. “Avoidable” in this case means that the caller can control
whether an exception is raised and whether it is necessary to catch the exception. Since
these situations are quite rare, dynamically typed exceptions are seldom used.
Within an application or a large project, creating your own abstract implementations
for the general classes CX_STATIC_CHECK and CX_NO_CHECK makes sense. These classes can
then be commonly used across applications and provide additional features and reus-
able methods. Preferably, the superclass for each component is abstract so that it can-
not be raised directly but can be used to catch every possible component-specific
exception, instead of individually catching every possible exception unnecessarily.

11.4.2 Raising Exceptions


Raising an exception is an important aspect in development since exceptions and the
situations in which they are raised define how the consumer needs to react. Therefore,
you must choose the type of exception and the type of message that is most suitable
for the consumer.
Adding a message to an exception influences the syntax and definition of the excep-
tion. In general, class-based exceptions are raised with the keyword RAISE EXCEPTION.
The shorter version RAISE is reserved for classic exceptions exclusively. The preferred
way of using the keyword is with the addition NEW, as shown in Listing 11.13, which is
shorter than using the keyword CREATE OBJECT and more aligned with the class-based
syntax.

METHOD save.
IF status = inconsistent.
RAISE EXCEPTION NEW cx_business_error(
reason = co_inconsistent ).
ENDIF.
ENDMETHOD.

Listing 11.13 Raise Exception New

Exceptions can be raised with the addition MESSAGE to directly fill in the relevant T100
fields of the exceptions. This addition can be helpful when the exception is relevant for
an end user or if the exception is raised in a function module wrapper to convert the
ABAP exceptions and the corresponding sy fields into an exception. The usage of the
addition is shown in Listing 11.14. The keyword addition directly converts the parame-
ters to the attributes of the interface IF_T100_DYN_MSG. Therefore, the exception
must implement the interface.

Personal Copy for Andrei Arabolea, [email protected] 247


11 Error Handling

METHOD save.
IF status = inconsistent.
RAISE EXCEPTION TYPE cx_business_error
MESSAGE ID 'MSGID'
NUMBER 123
TYPE 'E'
WITH co_inconsistent.
ENDIF.
ENDMETHOD.

Listing 11.14 Raise Exception Type with Message

Another option for raising exceptions is available in combination with the conditional
expressions COND and SWITCH. The addition THROW can be used to raise an inline exception
if one condition should result in an error. Also, the addition MESSAGE can be used to add
appropriate messages. However, since inline conditional expressions only have a lim-
ited scope in which they make sense, throwing an exception inline has only limited use
cases. Otherwise, too much logic will be packed into a single statement that is hard to
understand. In a situation where adding an inline exception to the statement makes
sense, you should consider refactoring the statement into multiple statements to fos-
ter clean code.
For all these options for raising an exception, the addition RESUMABLE can be used to
indicate that the processing can be resumed after the raising statement. The resume
ability is not an attribute of the exception type, but of the single instance. An exception
class can be used as both a resumable and a not resumable exception. The ability to
resume processing must be restated with every re-raise or propagation. However, only
a few situations exist where resuming processing is feasible. One example is inbound
processing in a migration scenario. Before data that should be migrated is imported
into the system, the data is checked. If the data isn’t consistent, a resumable exception
can be raised and the data will be further analyzed. If the processing is stopped with
each error, the user would have to tackle one error after another. A resumable excep-
tion could log an error and continue the analysis. After the analysis is completed, the
importing wouldn’t be saved because an exception was raised, but the user would get a
list of all issues in the data. Overall, resuming processing at a certain point will make
the overall process hard to follow and reduce maintainability and, thus, should be
avoided.

11.4.3 Catching Exceptions


The idea behind exceptions as described in the previous sections is to inform the caller
of an error situation. Sometimes, these errors are so severe that no proper reaction to it
exists, but it may often be possible to react to the situation.

248 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


11.4 Raising and Catching

The topic of this section is catching exceptions as one way of reacting to a raised excep-
tion. When an exception is raised within a TRY block, the keyword CATCH introduces a
block for handling the error. Following the keyword, a list of one or more exceptions is
added to define which exceptions are handled by the block. Optionally, the caught
exception can be stored in a variable to access details of the exception within the block.
A basic example for this structure is shown in Listing 11.15. One TRY block can have mul-
tiple CATCH blocks, which should be ordered from most specific to less specific, since the
exception is handled in first block that is applicable. If no such block exists, the excep-
tion is either propagated, or the runtime will raise a CX_SY_NO_HANDLER exception, in the
case of dynamically checked exceptions. If it’s a no-check exception, adding the excep-
tion to the signature is not necessary. The exception will be propagated to the calling
layer anyway during runtime and only terminates execution if no handler exists at all.

TRY.
calculate_income( order ).
CATCH cx_currency_conversion INTO DATA(currency_exception).
send_exception_to_ui( currency_exception ).
ENDTRY.

Listing 11.15 Catch Single Exception

In the CATCH block, the complete error handling can be located. One of the principles of
clean code is that methods should only do one thing, and error handling is one of those
things. Therefore, error handling should be in its own submethod to make it easier to
follow the code. As described by Robert C. Martin, sometimes code is so cluttered with
error handling that the real task of the code is obscured.
Relocating the error handling for exceptions into a submethod has the highest impact
if all exceptions are based on the same exception so that all signatures only need to
define a single exception. Within the method to handle the exception, the exception
can be handled specifically by casting it into the original exception type. To avoid cast-
ing errors, the type should be first checked via IS INSTANCE OF, like the exception
checked in Listing 11.6.

TRY.
method_call( ).
CATCH cx_root INTO data(exception).
IF cx_root IS INSTANCE OF cx_specific_exception.
handle_specific_exceptions( exception ).
ELSE.
RAISE EXCEPTION exception.
ENDTRY.

Listing 11.16 Usage of IS INSTANCE OF in CATCH Block

Personal Copy for Andrei Arabolea, [email protected] 249


11 Error Handling

To achieve well-structured code, we recommend starting with the TRY – CATCH part and
building the rest of the code from that starting point. Automatically, the method will be
more structured and more intuitive, and you’ll see more clearly if the method does
more than one thing. Additionally, test-driven development (TDD) is supported by this
approach since exception handling is added early and can be tested properly. If added
later, some tests may be invalidated.
In general, well-written code removes dependencies whenever possible. However, it is
often necessary and reasonable to rely on external code and, in doing so, introduce a
new dependency. In many cases, external method calls not only introduce a depen-
dency to the method itself, but also to the exception that is raised by the method being
used. Furthermore, the exception might not be handled directly and must be propa-
gated, thus introducing new dependencies in all affected methods.
To avoid these additional dependencies, exceptions that are raised by the depending
module should be caught and not propagated. If the error itself needs to be propagated
to the caller, a new exception of an internal type should be raised, as shown in Listing
11.17. If the external method changes the exception, only the mapping from the exter-
nal to the internal exception must be adapted. All surrounding code is not affected. The
exception for wrapping the external exception needs to inherit from the general com-
ponent exception too. The exception can be further handled by all areas that are pre-
pared to handle that specific exception, and when using the PREVIOUS parameter of the
exception constructor, the reference to the original exception is not lost during run-
time. Whenever available, the PREVIOUS parameter should be filled in to preserve the
original context and facilitate error analysis.

METHOD calculate_tax.
DATA(calculation_enginge) = tax_factory=>get_enginge( ).
TRY.
tax = calculation_enginge->calculate(
amount = amount
country = country ).
CATCH cx_tax_ennginge_exception INTO DATA(foreign_exception)
RAISE EXCEPTION NEW own_exception_type(
previous = foreign_exception ).
ENDTRY.
ENDMETHOD.

Listing 11.17 Wrapping foreign_exception

An addition to the TRY – CATCH block is the CLEANUP keyword, shown in Listing 11.18. In
this example, the method call do_something( ) is wrapped by two blocks for catching
exceptions. The method can throw the exceptions exception_a and exception_b. When
exception_a is raised, it is handled within the inner block and not propagated to the
outer block. exception_b is not handled within the inner block but is caught by the

250 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


11.4 Raising and Catching

outer block. Additionally, the CLEANUP part of the inner block is executed first. As the
name suggests, in this part, cleanup tasks can be performed before the actual error han-
dling is executed. This construct is not often used and can become obscured easily.
Thus, when practicing clean code, this construct should be avoided as much as possible
since a method that contains both blocks is doing more than one thing.

TRY.
TRY.
do_something( ).
CATCH exception_a.
do_something_else( ).
CLEANUP INTO DATA(exception).
first_do_this( exception ).
ENDTRY.
CATCH exception_b INTO DATA(exception_b).
error_handling( exception_b ).
ENDTRY.

Listing 11.18 CLEANUP for Exceptions

11.4.4 When to Dump


The last resort in error handling is to terminate the runtime without any possibility for
the caller to avoid termination. Raising a no-check exception is not enough in these
cases since the caller still can catch the exception. If the caller has no proper error han-
dling that terminates all programs, unwanted side effects may occur. A dump in ABAP
will directly stop processing, from which the caller cannot avoid the termination.
From an end-user perspective, a dump is completely unsatisfying because all unsaved
work is lost. Often, the error messages are not specific, and no clear reason for the
behavior can be determined.
Therefore, terminating the processing with a dump must be a conscious decision. Situa-
tions that require a dump are rare, but dumps are still valid approaches to avoid severe
issues, like inconsistencies. Inconsistencies can occur when some parts, but not all, are
already saved and saving the second part fails. Typically, a specific point in the process-
ing may exist that, once crossed, an error results in bigger issues than just termination.
If an error is detected after this point of no return, a dump may be considered.
Whenever a dump is triggered, you want to make analyzing the issue as easy as possi-
ble. For example, a failed ASSERT statement is often missing enough context even
though ASSERT statements are based on simple conditions. In the dump report, addi-
tional information on why the condition was violated can be hard to gather. Providing
enough information for analysis greatly reduces the effort for analysis. Thus, when ter-
mination is unavoidable, additional data that about what might have led to the termi-
nation should be gathered.

Personal Copy for Andrei Arabolea, [email protected] 251


11 Error Handling

Another reason justifying a termination is a programming error. The state that has
been reached is only reachable for by developer and not any end user. For this termina-
tion, you should provide enough details so that the developer can correct the error.
Since SAP NetWeaver 7.53, the statement RAISE SHORTDUMP is available and can be used
with an exception class, as in the following statement:

RAISE SHORTDUMP TYPE cx_sy_sequence_violation.

Raising a message with type X is how processing is terminated in earlier SAP NetWeaver
versions.

11.5 Summary
Error handling in ABAP has a strong focus on messages built into the language and con-
tinues to be important during the transition to class-based exceptions. Since messages
reside in their own development objects and enable translation and reusability, they
remain an important part of developing in ABAP.
This chapter covered the following clean ABAP recommendations and guidelines for
error handling:
쐍 Make messages easy to find by using the message statement.
쐍 Avoid using return codes for error indications.
쐍 Use class-based exceptions.
쐍 Wrap function calls in a method and convert classic exceptions to class-based excep-
tions.
쐍 Exceptions are for errors, not for usual cases.
쐍 Create your own exception superclass.
쐍 Raise CX_NO_CHECK exceptions internally.
쐍 Raise CX_STATIC_CHECK exceptions externally.
쐍 Use RAISE EXCEPTION NEW instead of RAISE EXCEPTION TYPE.
쐍 Limit foreign exceptions by wrapping them in your own exception.

Error handling is often an afterthought, the part of development performed after the
main functionality has been developed. Instead, error handling should be treated with
the same focus as functional development. Error handling that is added at the end
often results in unstructured code that is harder to unit test. Automated tests are
important in developing an application to ensure functional correctness and also give
developers more confidence to change code. As testing is a critical part of clean code,
the next chapter focuses on test automation with unit tests.

252 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Chapter 12
Unit Testing
Clean ABAP code is only clean after the code has been tested. In this
chapter, you’ll learn about unit testing principles and understand how
to design and run unit tests on your ABAP code.

We’re not exaggerating when we assert that software runs the world—from the most
basic infrastructure to consumer electronics to the International Space Station and
beyond. Software is everywhere, and it comes as no surprise that software defects can
range from little annoyances to catastrophic failures.
Professional software development has come a long way as well. Not only have our lan-
guages, concepts, and tools evolved immensely in recent decades, so has our under-
standing on how to design, integrate, and execute software artifacts.
One area that has seen enormous growth is the area of automated testing. We’re now
testing our software in automated ways at many different levels to assess its correct-
ness, performance, and other important characteristics. There will always be a place for
manual, exploratory-like tests, but automated testing plays a crucial role in the soft-
ware lifecycle. With automated testing, you can deliver, maintain, and evolve your soft-
ware with a high degree of confidence—not only confidence that your software does
what it is supposed to do, but also that new changes have not inadvertently introduced
new bugs or reintroduced old ones.
In this chapter, we’ll delve into unit testing, which is automated testing at the most fun-
damental level of our software, that of a single unit. In the context of ABAP, a unit usu-
ally means a single class, but you can have unit tests spanning smaller or larger scopes
as well, for example, checking a single method or the interaction of many classes. ABAP
Unit is the tool we use to create ABAP unit tests. It introduces new concepts, libraries,
and tools for automated testing in ABAP.
In this chapter, you’ll first learn how to code test classes and test methods in Section
12.1 and Section 12.2, respectively. Then, we’ll focus on the class under test in Section 12.3
and describe several considerations to keep in mind regarding names in Section 12.4.
We’ll then show you how to use assertions in Section 12.5. You’ll learn how to tackle
dependency injection and use test doubles in Section 12.6 as well as use test seams in
Section 12.7. We’ll then wrap up this chapter with a look at important principles to keep
in mind with unit testing and automated testing in general in Section 12.8.

Personal Copy for Andrei Arabolea, [email protected] 253


12 Unit Testing

12.1 Test Classes


In the ABAP Unit tool, test classes are classes whose definition is marked with the anno-
tation FOR TESTING. This annotation makes the class an ABAP Unit test class. ABAP Unit
test classes can contain test code and can only be used by other ABAP Unit test classes.
Non-test classes, or production classes, cannot reference or use test classes.
Typically, a test class will be developed as a local class. In ABAP Development Tools
(ADT), local test classes are defined and implemented under the Test Classes tab of a
global class. You’ll find this tab at the bottom of the source code panel for the global
class, as shown in Figure 12.1.

Figure 12.1 Test Classes Tab

In this section, we’ll take a look at the properties of test classes and their scope, before
exploring test helper classes and walking through executing unit tests in ADT.

12.1.1 Test Class Properties


For the temperature_conversion class, which we created in Listing 4.4 and discussed in
detail in Chapter 4, Section 4.1.1, we’ll create a local test class, as shown in Listing 12.1.

CLASS temperature_conversion_test DEFINITION


FINAL
FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.

PRIVATE SECTION.

METHODS:
freezing_temp_conversion FOR TESTING.

ENDCLASS.

254 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.1 Test Classes

CLASS temperature_conversion_test IMPLEMENTATION.

METHOD freezing_temp_conversion.

DATA(act_temp_in_celsius) =
temperature_conversion=>fahrenheit_to_celsius( 32 ).
cl_abap_unit_assert=>assert_equals(
exp = 0
act = act_temp_in_celsius
msg = 'Unexpected temperature in celsius' ).

ENDMETHOD.

ENDCLASS.

Listing 12.1 ABAP Unit Test Class

This temperature_conversion_test class includes the FOR TESTING annotation, which


makes it an ABAP Unit test class. We also included the annotations DURATION and RISK
LEVEL, which are important test properties that are used to control test execution.
Notice also the test method freezing_temp_conversion. Test methods are parameter-
less methods marked with the FOR TESTING annotation, which are called when executing
the tests. Test methods implement the test scenarios and verify the correct functional-
ity of the class under test using assertions, like the assertion methods found in the
framework class cl_abap_unit_assert.
The method freezing_temp_conversion checks that converting 32 degrees Fahrenheit to
Celsius yields 0 degrees Celsius, the freezing temperature of water. We’ll learn more
about test methods and assertions later in this chapter.
With the DURATION and RISK LEVEL properties, you’re indicating to the runtime what
characteristics of your test class are related to the expected running time and risk level
to the system.
In Transaction SAUNIT_CLIENT_SETUP, on a per client basis, an administrator can con-
figure system timeout values for the duration options and the maximum risk level that
can be executed. Test execution can even be disabled; it’s disabled by default in produc-
tion and demo clients.
The DURATION property has three options, as shown in Table 12.1, with default timeout
values. These properties control the expected duration of the class test execution,
which includes all its test methods. The test execution environment imposes the corre-
sponding timeout and interrupts and fails the test if it takes more time than what’s
expected.

Personal Copy for Andrei Arabolea, [email protected] 255


12 Unit Testing

Duration Default Timeout

SHORT 1 minute

MEDIUM 5 minutes

LONG 1 hour

Table 12.1 Unit Test Duration Options

The RISK LEVEL property also has three options, shown in Table 12.2, with increasing risk
levels and the usual interpretation of each level.

Risk Level Description

HARMLESS No effects on persisted application data, customizing data or system settings.

DANGEROUS Makes changes to persisted application data.

CRITICAL Makes changes to persisted application data, customizing data, or system


settings.

Table 12.2 Unit Test Risk Level Options

Pick the Most Appropriate Values for DURATION and RISK LEVEL
Signaling the expected duration and risk level of your test class to the runtime is
important so that your tests can be better managed in different systems or test runs.

After each test class execution, the test runtime environment issues a ROLLBACK WORK
statement to revert any database changes that the test methods in the class could have
made. If your test class makes any changes to persisted data and commits them, or
other durable changes, select the risk level DANGEROUS or CRITICAL, as appropriate.
Even in these cases, a well-behaved test should clean up after execution and revert the
system to its previous state before the test. However, bugs in the code or intermittent
system issues may occur, and the system can still be left in an inconsistent state after a
test execution.

Strive for SHORT and HARMLESS Unit Test Classes


As you’ll learn in the following sections, unit test classes should be fast and isolated.
Any external dependencies, even database tables, should be replaced by test doubles.
Therefore, strive for your unit tests to use the properties DURATION SHORT and RISK
LEVEL HARMLESS.
These properties might not be possible in some situations, for instance, when using
ABAP Unit to perform more high-level integration tests, testing data migrations, or
conducting performance testing. However, the bulk of your tests should be low-level,
functional unit tests that check the functionality of your classes and methods.

256 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.1 Test Classes

12.1.2 Test Class Scope


ABAP Unit test classes can be developed as global classes or local classes. In this section,
we’ll look at both options and see when to use each one.

Unit Tests
Local unit test classes are used to verify the behavior of a single class.
Global unit test classes can still be used as superclasses and provide common function-
ality to the local unit test classes. Remember to design these global unit test classes
with inheritance in mind. For more information, refer to the “Inheritance” section in
Chapter 3, Section 3.1.2. These classes should be marked with FOR TESTING and should
also be ABSTRACT. These classes can contain common code or even reusable test meth-
ods. Making them ABSTRACT prevents the direct execution of their test methods, which
would also result in an ABAP warning being issued, since test methods are only meant
to be executed from the context of local test classes. Executing the tests for subclasses
automatically executes all the superclass’ test methods.

Use Local Test Classes for Unit Tests


To ensure that the corresponding unit test classes of a class are easy to find and exe-
cute, unit test classes should be created as local test classes of the global class being
tested.
This recommendation is also relevant when writing local test classes to test other local
classes within the scope of the global class.

Higher-Level Tests
Higher-level tests, like component, integration, or system tests, should not be arbi-
trarily associated as a local test class to one of your classes. The nature of these tests is
that they cover more than one class, either the interaction of a set of classes or the high-
level integration of multiple classes into a component or a system.

Use a Separate Global Class as a Container for Higher-Level Tests


Create a separate global class and include the high-level tests as its local test classes.
The global class serves only as a container to the tests.
As a simple container, this global class should not be referenced in production code or
reused in test code. Therefore, annotate it with FOR TESTING, ABSTRACT, and FINAL.
There’s no need to add any methods or other elements to this class.

To test the higher-level concept of our devices and related functionality, introduced in
Chapter 3, we can use the global container class shown in Listing 12.2.

Personal Copy for Andrei Arabolea, [email protected] 257


12 Unit Testing

CLASS devices_integration_tests DEFINITION


PUBLIC
FOR TESTING
ABSTRACT
FINAL.
ENDCLASS.

CLASS devices_integration_tests IMPLEMENTATION.


ENDCLASS.

Listing 12.2 Global Container Class for Local Integration Tests

For the local test classes, test relations can be used to document which objects are being
tested. Test relations are special ABAP Doc comments (see Chapter 9, Section 9.5) at the
class definition level that refer to the classes or other objects being tested by the test
class.
For the global class shown in Listing 12.2, the definition header of a local test class that
verifies the integration of two related classes is shown in Listing 12.3, which includes
the tested classes referenced by test relations.

"! @testing fan


"! @testing thermal_switch
CLASS thermal_switched_fan DEFINITION
FINAL
FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.

Listing 12.3 Local Integration Test Class Definition Header

Use Test Relations to Refer to External Objects Being Tested


With test relations, you can refer to classes or even core data services (CDS) views to be
the targets of the tests. For the referenced object, all tests that refer to the object are
collectively known as the object’s foreign tests. Refer to Section 12.1.4 to learn how to
execute foreign tests in ABAP Development Tools (ADT).
Test relations are redundant for a local unit test class testing its global class (since they
are implicitly associated with each other) but might be beneficial for higher-level test
classes testing external global classes or CDS views.

In our example, the test relations in Listing 12.3 indicate that the test class thermal_
switched_fan tests the related classes fan and thermal_switch. Note that the spacing is
important for test relations and should follow the pattern: "! @testing <target class or

258 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.1 Test Classes

CDS view name>. When executing foreign tests for either the fan or the thermal_switch class,
the tests defined in thermal_switched_fan will be executed.

12.1.3 Test Helper Classes


When multiple unit test classes need common functionality, you can create a test
superclass, as mentioned in Section 12.1.2. A test superclass is particularly helpful if you
want subclasses to inherit test methods. The superclass’s test methods will be executed
automatically when running the tests of the subclass.
When you don’t need to inherit test methods, a better way to provide the functionality
might be creating a test helper class. A test helper class provides a service to test classes,
which can be reused via composition or simply by calling its methods. You can design
this class as an instantiable class or, more typically, as a utility class. (For more informa-
tion, check out the “Utility Classes” section in Chapter 3, Section 3.1.2.)
You can, for example, create a global test helper class with custom assertion methods
that understand how to compare objects of your domain objects, as shown in Listing 12.4.

CLASS money_assert DEFINITION


PUBLIC
ABSTRACT
FINAL
FOR TESTING.

PUBLIC SECTION.

CLASS-METHODS assert_equals
IMPORTING
money1 TYPE REF TO money_object
money2 TYPE REF TO money_object.

...

ENDCLASS.

Listing 12.4 Test Helper Utility Class

Be sure that you design your test helper classes according to the rules of class design
described in Chapter 3.

Mark All Classes Made for Testing Explicitly as FOR TESTING


All classes meant to be used exclusively in the context of ABAP Unit should be marked
with the FOR TESTING annotation. This recommendation includes not only local unit
test classes, but test superclasses, test container classes, test helper classes, and test
doubles as well.

Personal Copy for Andrei Arabolea, [email protected] 259


12 Unit Testing

The annotations DURATION or RISK LEVEL are only needed in concrete local test classes
whose test methods will be executed by the ABAP Unit environment.
Marking all these classes with the annotation FOR TESTING ensures they’re never used
by production code or even compiled in production or demo systems, where ABAP Unit
is disabled by default.

12.1.4 Executing Tests


As a developer working on a project, you can execute unit tests in ABAP Development
Tools (ADT) in many ways. In the Project Explorer, right-click a class or a package and
choose Run As 폷 ABAP Unit Test to execute the unit tests found in the class or the pack-
age. You can select Coverage As 폷 ABAP Unit Test to execute the unit tests measuring
test coverage.
Running these tests will display the test execution results in the ABAP Unit tab, as
shown in Figure 12.2.

Figure 12.2 ABAP Unit Results View

If you navigate to Run As 폷 ABAP Unit Test With… , you’ll be presented with the dialog
box shown in Figure 12.3, where you’ll choose which options to use when running the
tests, including duration and risk level, whether to measure coverage or to include trac-
ing, and which tests to run: Own Tests and Foreign Tests. As described in Section 12.1.2,
foreign tests are external tests declared with a test relation to the current object being
tested.
The third option is to use the hotkeys (CTRL)+(SHIFT)+(F10) to run all tests,
(CTRL)+(SHIFT)+(F11) to run all tests with coverage, or (CTRL)+(SHIFT)+(F12) to choose
your testing options in the Run ABAP Unit Test With… dialog box. On macOS, use (CMD)
instead of (CTRL).
When running tests with coverage, the ABAP Coverage tab will display the coverage
report for the class or package. You’ll see a list of all relevant classes and methods with
coverage percentages based on the current coverage type being displayed: statement
coverage (the default), branch coverage, or procedure coverage. You can change which
coverage report to display by clicking on the view menu icon on the ABAP Coverage tab

260 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.1 Test Classes

toolbar. You can also toggle code highlighting with the Hide Highlighting button in the
same toolbar.

Figure 12.3 Run ABAP Unit Test With… Dialog Box

Figure 12.4 shows a statement coverage report under the ABAP Coverage tab for the
temperature_conversion class. The highlighted code in the class is green for the state-
ments that were executed during the test. The statement coverage for this class is at
100%, but it only has a single executable statement.

Figure 12.4 ABAP Coverage Report

Personal Copy for Andrei Arabolea, [email protected] 261


12 Unit Testing

We’ll discuss test coverage in more detail in Section 12.8.3.

12.2 Test Methods


Test methods are the main executing targets of the ABAP Unit framework. When you
execute the unit tests of a class, as described in Section 12.1.4, the ABAP Unit runtime
will determine all test methods in the class and run them one by one.
A test method has the following characteristics:
1. It belongs to a test class.
2. It is an instance method.
3. It is marked with the annotation FOR TESTING.
4. It has no parameters but can have a RAISING clause.
5. It is typically private in a local test class and protected if defined in a test superclass.

Apart from test methods, ABAP Unit also recognizes special test fixture methods, which
help better organize test code. We’ll look at test fixture methods next and then discuss
test method design.

12.2.1 Test Fixture Methods


Test fixture methods can provide common setup and teardown code for test methods
and for the test class. Test fixture methods come in two sets: static setup and teardown
(class_setup and class_teardown), which are executed before and after all test methods
are executed, and instance setup and teardown (setup and teardown), which are exe-
cuted before and after each test method is executed.
These methods must be declared as private and can also be inherited. When inherited,
both superclass and subclass methods will be called in a specified order: first superclass
then subclass for setup methods and the reverse for teardown methods.
Setup and teardown methods are always called even if test methods raise an assertion
error or an exception.
The sequence of calls that the ABAP Unit runtime goes through, including the optional
test fixture methods, is illustrated in Figure 12.5.
As shown in Figure 12.5, to execute the tests in a single test class, the ABAP Unit runtime
will complete the following steps:
1. Before any test methods are executed, the runtime will call the class_setup static
method of the test class.
2. For each test method found in the class, the runtime will then:

262 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.2 Test Methods

– Create a new instance of the test class.


– Call the setup method.
– Call the current test method.
– Call the teardown method.
3. After executing all test methods, the runtime will call the class_teardown static
method of the test class.
4. Finally, the runtime executes a ROLLBACK WORK statement.

ABAP Unit
Test Class
Runner

run unit tests

class_setup()

loop [for all test methods]


create object Instance:
Test Class

setup()

call test method

teardown()

class_teardown()

rollback

Figure 12.5 ABAP Unit Test Class Execution Sequence

Every test method is thus executed in its own environment, where a fresh instance of
the test class is created, and the setup and teardown methods are individually called.
This process is meant to increase isolation between test methods and to prevent the
effects of one test method from inadvertently affecting the execution of a separate test
method. Also, test methods should not depend on the order in which they’re executed,
which is not guaranteed by the framework.
The framework only goes so far. The design of your classes is what ultimately impacts
how decoupled and testable the code is. Good design not only enables better reuse and
maintainability but also enhances the testability of the code. Good design and testabil-
ity go together, and we recommend you closely follow the object-oriented design
guidelines described in Chapter 3.

Personal Copy for Andrei Arabolea, [email protected] 263


12 Unit Testing

Let’s look at the fan_toggle local test class shown in Listing 12.5. This local test class will
check the functionality of the switchable~toggle method of the fan class and how it
impacts its switchable~is_on method. The fan class was introduced in Chapter 3, Listing
3.11, and discussed in detail in Chapter 3, Section 3.1.2, where the switchable interface
can be also be found in Listing 3.14.

CLASS fan_toggle DEFINITION


FINAL
FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.

PRIVATE SECTION.

METHODS:
setup,
off_by_default FOR TESTING,
on_after_toggle FOR TESTING.

DATA:
fan_instance TYPE REF TO switchable.

ENDCLASS.

CLASS fan_toggle IMPLEMENTATION.

METHOD setup.
fan_instance = NEW fan( ).
ENDMETHOD.

METHOD off_by_default.
cl_abap_unit_assert=>assert_false( fan_instance->is_on( ) ).
ENDMETHOD.

METHOD on_after_toggle.
fan_instance->toggle( ).
cl_abap_unit_assert=>assert_true( fan_instance->is_on( ) ).
ENDMETHOD.

ENDCLASS.

Listing 12.5 Test Class with setup Method

This test class has two test methods and a setup method. The setup method creates an
instance of the class under test (fan) and assigns it to the fan_instance instance attri-
bute. Each test method will then have access to a separate instance of the class under
test to perform its checks, isolated from the other test methods.

264 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.2 Test Methods

Know When to Use Teardown


The teardown methods (teardown and class_teardown) are usually only needed to
clean up database entries or other external resources in integration tests. You may also
need these methods to correctly use some mocking frameworks, like the ABAP SQL Test
Double Framework and the ABAP CDS Test Double Framework mentioned in Section
12.6.3.
Resetting members of the test class, like the class under test or any used test doubles,
is superfluous since the test environment always creates a fresh new instance of the
test class and calls the setup methods before the next test method is executed. The old
instance is not reused, and any state it may have had is irrelevant to the next test
method.

12.2.2 Given-When-Then Style


When designing and implementing a test method, you might find it beneficial to think
about which scenario you’re testing. Each scenario tests an aspect of a functionality
that your class provides.
The enact a scenario, a test method can go through the following steps:
1. Given: Set up any needed instances, test doubles, and dependencies for the test.
2. When: Perform the desired operation on the test target.
3. Then: Verify the correct result of the operation, which can include return values, the
state of object instances, or test double checks. In this step, assertions are used.

Thus, the on_after_toggle test method shown in Listing 12.5 checks the following sce-
nario:
1. Given that I have an instance of a fan,
2. When I toggle the fan,
3. Then, it should be on.

The given step is implemented in the setup method. Since the given step, or at least
some parts of it, is usually shared between all test methods, this is a common practice.

Use the Given-When-Then Style


Organize your test code along the given-when-then style.
If the given or then sections get so long that you cannot visually separate the three sec-
tions anymore, extract submethods. Given is usually implemented in the setup
method when it’s common to all test methods in the class.
All the guidelines for good method design still apply to test methods, like the rule to
descend one level of abstraction, described in detail in Chapter 4, Section 4.3.2.

Personal Copy for Andrei Arabolea, [email protected] 265


12 Unit Testing

12.3 Class under Test


The class under test, also referred to as the code under test, is the target of the test, i.e.,
the class that the unit test class refers to. Usually, the class under test is the global class
to which its local unit test class refers.
In Chapter 4, Section 4.1.2, we advocated that the public instance methods of a class
should be declared in an interface. As a result, the class becomes an implementation of
one or more interfaces which informs how the class is meant to be consumed by other
classes: through its interfaces.

Tests Should Consume the Class under Test as Intended


Since unit tests are typically the first consumers of the class under test, testing it
through its interfaces makes sense as well.
Let’s look at our earlier unit test class fan_toggle, shown in Listing 12.5. The toggle and
the is_on methods are part of the switchable interface, and to test their operation on
the fan class, we use a reference to fan typed to the switchable interface: the fan_
instance attribute.
Testing through the class interfaces better simulates the intended usage of the class
under test and makes the test more meaningful and relevant.
Your class may not implement interfaces, maybe because the class is a utility class or
some other value class, or the class is a legacy class that doesn’t implement suitable
interfaces. In these cases, you should make the tests consume the class under test as
close as possible to how the real consumers of the class would.

A unit test class can be made into a local friend of the global class it’s testing. This
friendship allows the unit test class to access private and protected members of the
global class.
Some design idioms dictate limiting the visibility of a class instance constructor to pri-
vate and only allowing instantiation through a factory class, declared as a friend. To test
this kind of class directly, the unit test class must be able to create an instance by calling
the private constructor. To enable this step, the unit test class must be a local friend of
the global class.
In the local test classes, before the declaration of a local unit_test_class, use the follow-
ing statements to set up the friendship:

CLASS unit_test_class DEFINITION DEFERRED.


CLASS class_under_test DEFINITION LOCAL FRIENDS unit_test_class.

In this way, the unit_test_class has access to all members of the class_under_test and
can call its private constructor and inject the necessary dependencies for the test.

266 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.4 Naming

Use LOCAL FRIENDS to Access a Private Constructor


Only make the unit test class a friend of the class under test to access a private con-
structor.
Beyond that, don’t misuse friendship access to invade tested code. Unit tests that
access private and protected members to insert mock data or call otherwise inaccessi-
ble methods are fragile and break the encapsulation of the tested class.

12.4 Naming
The given-when-then style guides the design of test methods and test classes. You can
create separate test classes that require different given sections or classes dedicated to
testing a certain when situation, like a specific public method of a class.
Be sure you name these classes and methods according to their purpose and clearly
communicate what the test is checking.
Classes are usually named after a common given or when section. The fan_toggle class
shown in Listing 12.5 is named after the when part of the test: We want to check the tog-
gle method of the fan class in test method on_after_toggle.
Even though the off_by_default test method doesn’t call toggle, the test method is still
part of the test class because it checks a baseline situation: what should the state of the
fan be before toggle is called? This assertion could have been added to the setup
method itself but putting it into a dedicated test method makes it a full-fledged sce-
nario: Given that I have an instance of a fan, then it should be off by default.

Call Test Classes by Their Purpose, Either Given or When


Test classes should be designed around common given parts or common when parts
and should be named accordingly.

You can use a more generic name if you can’t find a specific given or when part, for
example, when testing a utility class like the temperature_conversion_test, shown ear-
lier in Listing 12.1. If you’re using Hungarian notation, a common prefix for a local test
class is ltc_, in which case temperature_conversion_test would simply be called ltc_
temperature_conversion.

Call Test Methods by What’s Expected: When and Then


A test method name should reflect what’s expected of the test. That’s usually a part of
the when and the then parts, or simply the then part, if the class name already refers to
the when. Together with the class name, the method name should succinctly convey
what it’s testing.

Personal Copy for Andrei Arabolea, [email protected] 267


12 Unit Testing

A good starting point for a test method name is to think of the then part. If the method
name is still not clear enough, then add in relevant pieces of the when part. Table 12.3
lists some examples.

When and Then Possible Method Names

When I toggle the fan, then it should be on. on_after_toggle


toggle_then_on

When I enter an invalid key, then an exception should be raised. raises_on_invalid_key

When I enter input with invalid characters, then the input should rejects_invalid_input
be rejected.

When I toggle the fan twice in a roll, then it should end up as off. off_after_toggle_2x
toggle_2x_then_off

Table 12.3 Test Method Names

Since test method names are not meant to be called by other parts of the system, but
are primarily consumed by the ABAP Unit runtime, you should put as much informa-
tion in their names as possible. In Java, for example, you can be extra verbose with a
name. For the third example shown in Table 12.3, you could use something as big as
should_reject_when_entering_input_with_invalid_characters.
But, since ABAP only allows 30 characters in method names, adding an explanatory
comment if the name is too short to convey enough meaning is acceptable. You can
use ABAP Doc, simply add a comment at the top of the test method body, or comment
each section of the given-when-then style. Having lots of test methods whose names
are too long may be an indicator that you should split your test class into separate test
classes and express their differences in their names.

Name the Class under Test Meaningfully


The class under test is usually referred to by one or more attributes of the unit test
class. Meaningful names will show what’s special about those references and how
they aid the unit test class carry out its purpose.

The attribute name for the class under test can be generic, like fan_instance shown in
Listing 12.5, or perhaps simply fan, or something more specific if you have many differ-
ent attributes as in the snippet shown in Listing 12.6.

DATA empty_blog_post TYPE REF TO blog_post.


DATA simple_blog_post TYPE REF TO blog_post.
DATA very_long_blog_post TYPE REF TO blog_post.

Listing 12.6 Multiple Class under Test Attributes

268 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.5 Assertions

In some cases, a unit test class will have many attributes, and perhaps, only one or just
a few attributes refer to the class under test. Especially for legacy code, where the tests
and the code are still not easily readable and understandable, calling the class under
test reference by its acronym cut, or using it as a prefix or suffix, can help the reader
spot the class under test more easily. However, tidying up the tests and the class under
test is preferred in the long run.

12.5 Assertions
Assertions are the checks that a unit test class makes against the class under test behav-
ior. An assertion is the then part of the given-when-then style. It’s what verifies that
what you want to be true about the class under test is actually true.
Without assertions, tests would lose their automated ability to verify the expected
behavior. Assertions communicate with the test environment and influence the out-
come of a whole suite of tests. A single assertion failure is enough to fail all the tests
being executed.
The most fundamental and broadly useful assertion methods are found in the frame-
work class cl_abap_unit_assert. This class is a utility class with many static assertion
methods, like assert_equals to compare two inputs for equality, or assert_true, to
check that an abap_bool value is equal to abap_true.
The methods on cl_abap_unit_assert typically check for an actual result (parameter
act) to meet a certain requirement, like in assert_true or assert_initial, possibly
against an expected result (parameter exp), like in assert_equals.
More specialized assertion methods will have more specific parameter names, like the
method assert_table_contains, which checks that an internal table given in parameter
table contains the row given in parameter line.
Some common optional parameters can be used to control what happens when an
assertion fails. Table 12.4 shows the most common parameters used in assertion meth-
ods of cl_abap_unit_assert.

Parameter Description

act Actual result of the tested operation.

exp Expected result of the tested operation.

msg Optional. Specific error description when the assertion fails.

level Optional. Sets the severity of the assertion error should it fail. Either high,
medium (default), or low. Low errors are shown as warnings in the ABAP Unit
tab in ABAP Development Tools (ADT). Use the constants declared in if_aunit_
constants=>severity.

Table 12.4 Most Common Assertion Methods Parameters

Personal Copy for Andrei Arabolea, [email protected] 269


12 Unit Testing

Parameter Description

quit Optional. Decides what should happen to the control flow in case of an asser-
tion failure. The default is to abort the current test method (if_aunit_con-
stants=>quit-test), but you can also choose to continue the execution of the
method after the assertion call (if_aunit_constants=>quit-no).

Table 12.4 Most Common Assertion Methods Parameters (Cont.)

Now, let’s explore assertions further, including how to write them effectively and how
to check for and deal with exceptions, custom assertions, and constraints.

12.5.1 Writing Effective Assertions


When checking for a condition with assertions, test methods communicate what’s
important to verify about an operation, and what must be true to decide whether a sce-
nario has fulfilled its requirements.

Use as Few and as Focused Assertions as Possible


Assert exactly what the test method is about, using the minimum number of asser-
tions necessary.
Having too many assertions is an indicator that the test method has no clear focus.
This approach couples productive and test code in many ways: Changing a feature will
require rewriting many tests, even when the tests are not related to the changed fea-
ture. Too many assertions can also confuse the reader, obscuring the most important
assertions that verify what the scenario is all about.
You can also raise the level of abstraction and make the test method more cohesive by
writing custom assertions, as shown earlier in Listing 12.4. We’ll look at custom asser-
tions in more detail later in Section 12.5.4.

As an example, when checking for a validation error for an entity, as shown in Listing
12.7, you should check that the specific expected error message was added to the mes-
sage container but should probably not check for other messages or for the total num-
ber of messages in the container. Assert exactly what’s needed, not less, not more.

METHOD rejects_invalid_country.

" Given I have a customer with an invalid country


DATA(my_customer) = NEW customer(
name = `Megadodo Corp.`
country = invalid_country ).

" When I validate the customer

270 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.5 Assertions

my_customer->validate( message_container ).

" Then an error should be added for the invalid country


DATA(messages) = message_container->get_messages( ).
cl_abap_unit_assert=>assert_table_contains(
table = messages
line = invalid_country_error_message
msg = `Expected error message not found` ).

ENDMETHOD.

Listing 12.7 Asserting That a Validation Error Occurred

Assert Content, Not Quantity


Don’t write magic-number quantity assertions, like the expected number of lines in an
internal table, if you can express the actual content that you expect.
Numbers can vary although expectations can still be met, and the test would fail even
though it shouldn’t (a false negative). Similarly, numbers may match, but the content
might be something completely unexpected, and the test will pass even though it
shouldn’t (a false positive, much more difficult to detect).

At the same time, choosing the right assertion method to use makes your code easier
to read and communicates more clearly the intent of the check. As shown in Listing
12.7, you can use the assert_table_contains method to check that a message in the mes-
sage table of the message container corresponds to the expected message for an invalid
country.
Methods like assert_table_contains and assert_equals often do more than meets the
eye. They can include type matching and provide precise descriptions when the actual
value differs from the expected value.
To improve the test methods shown earlier in Listing 12.5, let’s add better error descrip-
tions with the msg parameter, as shown in Listing 12.8.

Make It Easy to See What’s Wrong When an Assertion Fails


When an assertion fails, the error message in the ABAP Unit tab should communicate
what’s expected and what’s wrong about the check to make it easier to understand
and fix the problem.
Use the right assertion method that will better describe the failure but also provide a
specific description of the error with the msg parameter to the assertion methods.
When practicing test-driven development (TDD), you’ll always start with a failing test
and thus are confronted right away with a failure message. In the book Growing

Personal Copy for Andrei Arabolea, [email protected] 271


12 Unit Testing

Object-Oriented Software, Guided by Tests (Addison-Wesley, 2009), make the diagnos-


tics clear is a step added to a modified TDD cycle, right after the step to write a failing
test. We’ll have more to say about TDD later in Section 12.8.1.

METHOD off_by_default.
cl_abap_unit_assert=>assert_false(
act = fan_instance->is_on( )
msg = `Fan should be off` ).
ENDMETHOD.

METHOD on_after_toggle.
fan_instance->toggle( ).
cl_abap_unit_assert=>assert_true(
act = fan_instance->is_on( )
msg = `Fan should be on` ).
ENDMETHOD.

Listing 12.8 Using the msg Parameter to Clarify Assertion Errors

Sometimes, you’re interested in a meta-quality of the result. For example, if you’re test-
ing the functionality provided by the method split_text_into_lines, described in
detail in Chapter 4, Section 4.4.1, Listing 4.43, you want to know if the method returns
lines of text with a maximum number of characters per line from a larger text. If you
check for the exact contents of the result, this test might break in a future revision of
your algorithm, even if the overall result is still perfectly good.

Assert Quality, Not Content


If you’re interested in a meta-quality of the result, but not in the actual result contents,
express that emphasis with a suitable assertion, like the custom assertion shown in
Listing 12.9.
assert_all_lines_shorter_than(
actual_lines = lines
expected_max_length = 80 ).
Listing 12.9 Checking the “Quality” of the Result

Asserting the precise contents of the result obscures what the test is all about, and is
also fragile because changes may produce a different, but perfectly acceptable, result
and break your meticulously crafted unit tests.

12.5.2 Checking Expected Exceptions


When checking for an expected exception from an operation, you’ll need to catch the
exception in the test method and then assert any necessary characteristics of the
exception.

272 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.5 Assertions

You should also check for cases when the exception is not raised. If the exception is not
raised, then the control flow will continue after the target method call. Therefore, it’s
important that the test fail immediately with a call to cl_abap_unit_assert=>fail.
In Listing 3.18, described in Chapter 3, Section 3.2, we presented a message_iterator
interface that raises an exception when the iterator has reached the end of the message
collection in method get_next.
The idea is to iterate over a message_collection and raise the exception if trying to get
an element past the last element of the collection. The exception should also be raised
when trying to iterate over an empty collection.
To test the local message_iterator implementation of the message_list class in Listing
3.19 (also in Chapter 3, Section 3.2), we have the following scenario for the empty case:

쐍 Given an empty message list,


쐍 When iterating over the list,
쐍 Then an exception should be raised.

This scenario can be enacted by the test method shown in Listing 12.10.

METHOD raises_when_iterating_empty.

" Given an empty message list


DATA(message_collection) =
CAST message_collection( NEW message_list( ) ).

" When iterating over the list


DATA(message_iterator) = message_collection->get_iterator( ).
TRY.
message_iterator->get_next( ).

" Then an exception should be raised


cl_abap_unit_assert=>fail(
msg = `Expected exception was not raised` ).
CATCH no_element_exception.
ENDTRY.

ENDMETHOD.

Listing 12.10 Checking for an Expected Exception

If you need to check any properties of the exception, you can capture the exception in
a variable and add the necessary assertions within the CATCH block. The cl_abap_unit_
assert=>fail call makes sure that the test method fails if no exception is raised. If an
unexpected exception is raised, the test also fails with an unexpected exception failure.

Personal Copy for Andrei Arabolea, [email protected] 273


12 Unit Testing

12.5.3 Unexpected Static Exceptions


Apart from assertion errors, the ABAP Unit environment will also report a failure if an
exception is raised and is not handled by a test method.
If you’re calling a method that is declared as raising a static exception (see Chapter 11,
Section 11.3.3), which is an exception ultimately inherited from CX_STATIC_CHECK, then
you’re either forced to handle it or to forward it.
For example, in a customer repository interface, you can have a method that raises a
static exception, like the read method shown in Listing 12.11.

METHODS read
IMPORTING customer_key TYPE customer_key
RETURNING VALUE(result) TYPE REF TO customer
RAISING no_such_customer_exception.

Listing 12.11 Method Raising a Static Exception

Since no_such_customer_exception is a static exception, any method calling the read


method in Listing 12.11 will need to handle the exception in one of two ways: either sur-
rounding the call in a TRY-CATCH block or forwarding the exception by redeclaring it in
the calling method definition.
If the scenario exercised by your test method doesn’t require handling the exception,
then you should redeclare the exception in the test method definition section. The
ABAP Unit runtime will still call the test method as usual and report a failure for any
unhandled exceptions, static or not. Otherwise, you’re forced to handle the exception
when you don’t need to, obscuring the goal of your test. Your test code should remain
focused on the happy path, not on exception handling, as shown in Listing 12.12.

METHOD reads_existing_customer.

" When I read an existing customer


DATA(customer) = customer_repository->read( 'MEGADODO' ).

" Then the result should not be null


cl_abap_unit_assert=>assert_bound(
act = customer
msg = `Customer is null` ).

ENDMETHOD.

Listing 12.12 Calling a Method That Raises a Static Exception

The reads_existing_customer test method we just implemented must be declared as


raising the static exception declared on the read method, as shown in Listing 12.13.

274 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.5 Assertions

METHODS:
reads_existing_customer FOR TESTING
RAISING no_such_customer_exception.

Listing 12.13 Forwarding a Static Exception

The code in your test method can also raise multiple static exceptions, which would
force you to forward them all even if you’re not interested in them, as shown in Listing
12.14.

METHODS:
maintains_existing_customer FOR TESTING
RAISING
no_such_customer_exception
validation_exception.

Listing 12.14 Forwarding Multiple Static Exceptions

Forward Irrelevant Static Exceptions as cx_root


If your test method calls methods that raise static exceptions that are irrelevant to
your scenario, forward these exceptions by adding a RAISING cx_root clause to your
test method declaration. Using cx_static_check instead of cx_root is also fine.
This approach makes the test method code clearer and acknowledges the fact that any
exceptions coming out of the test method, even though they would fail the test, are
not important for the scenario being tested.

The customer repository testing methods discussed earlier in this section should then
be declared as shown in Listing 12.15.

METHODS:
reads_existing_customer FOR TESTING
RAISING cx_root,
maintains_existing_customer FOR TESTING
RAISING cx_root.

Listing 12.15 Forwarding cx_root

12.5.4 Custom Assertions


Custom assertion methods can raise the level of abstraction and generally make your
code easier to read and understand. Earlier in Listing 12.4, we used a test helper class
that provides specific assertions for a domain object. Depending on the applicability of
your assertion, you can also create custom assertion methods in a base test class or on
a specific local test class if the assertion will only be used by that class.

Personal Copy for Andrei Arabolea, [email protected] 275


12 Unit Testing

For a more detailed example, to improve the assertion shown earlier in Listing 12.7, let’s
create the message_container_assert test helper class, as shown in Listing 12.16.

CLASS message_container_assert DEFINITION


PUBLIC
ABSTRACT
FINAL
FOR TESTING.

PUBLIC SECTION.

CLASS-METHODS assert_contains_message
IMPORTING
message_container TYPE REF TO message_container
act TYPE message
msg TYPE string
DEFAULT `Expected message not found`
level TYPE int1
DEFAULT if_Abap_Unit_Constant=>severity-medium
quit TYPE int1
DEFAULT if_Abap_Unit_Constant=>quit-test.

ENDCLASS.

CLASS message_container_assert IMPLEMENTATION.

METHOD assert_contains_message.
DATA(messages) = message_container->get_messages( ).
cl_abap_unit_assert=>assert_table_contains(
table = messages
line = act
msg = msg
level = level
quit = quit ).
ENDMETHOD.

ENDCLASS.

Listing 12.16 Custom Assertion Test Helper Class

Note how the method assert_contains_message also declares some common optional
parameters of the cl_abap_unit_assert class methods: msg, level, and quit. This
method even provides a more suitable default for msg. The declaration of these param-
eters in your custom assertion methods makes them more broadly usable since they
follow the same pattern and provide the same features as the base assertion methods.

276 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.5 Assertions

Now, the test method shown earlier in Listing 12.7 can be rewritten to take advantage of
the new assertion method, as shown in Listing 12.17.

METHOD rejects_invalid_country.

" Given I have a customer with an invalid country


DATA(my_customer) = NEW customer(
name = `Megadodo Corp.`
country = invalid_country ).

" When I validate the customer


my_customer->validate( message_container ).

" Then an error should be added for the invalid country


message_container_assert=>assert_contains_message(
message_container = message_container
act = invalid_country_error_message
msg = `Expected error message not found` ).

ENDMETHOD.

Listing 12.17 Using a Custom Assertion Method

12.5.5 Constraints
One issue with custom assertion methods is that, if you need a logical alternative, like
a {not [contains message]} or a {[contains message] and [some other condition]}, then
you’re forced either to write many other alternative methods or make the method
much more complex by adding parameters for these variations.
Composable constraints can be achieved by implementing the if_constraint interface
and using the method cl_abap_unit_assert=>assert_that. Consider the constraint class
shown in Listing 12.18.

CLASS message_container_contains DEFINITION


PUBLIC
FINAL
FOR TESTING.

PUBLIC SECTION.

INTERFACES: if_constraint.

METHODS:
constructor
IMPORTING
message_container TYPE REF TO message_container.

Personal Copy for Andrei Arabolea, [email protected] 277


12 Unit Testing

PRIVATE SECTION.

DATA:
message_container TYPE REF TO message_container.

ENDCLASS.

CLASS message_container_contains IMPLEMENTATION.

METHOD constructor.
me->message_container = message_container.
ENDMETHOD.

METHOD if_constraint~is_valid.
DATA(messages) = message_container->get_messages( ).
READ TABLE messages
WITH KEY table_line = data_object
TRANSPORTING NO FIELDS.
result = xsdbool( sy-subrc = 0 ).
ENDMETHOD.

METHOD if_constraint~get_description.
result = VALUE #( ( `Expected message not found` ) ).
ENDMETHOD.

ENDCLASS.

Listing 12.18 Constraint Class

This constraint class checks that a message container contains a given message. The if_
constraint~is_valid method checks that the data_object input parameter is contained
in the message container, and the if_constraint~get_description returns an error
message that’s used if the constraint condition fails.
Now, this constraint class can be used with the assert_that method, as shown in Listing
12.19.

METHOD rejects_invalid_country.

" Given I have a customer with an invalid country


DATA(my_customer) = NEW customer(
name = `Megadodo Corp.`
country = invalid_country ).

" When I validate the customer


my_customer->validate( message_container ).

278 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.6 Test Doubles

" Then an error should be added for the invalid country


DATA(is_contained) =
NEW message_container_contains( message_container ).
cl_abap_unit_assert=>assert_that(
act = invalid_country_error_message
exp = is_contained
msg = `Expected error message not found` ).

ENDMETHOD.

Listing 12.19 Assertion with Constraint

The true power of constraints is that they’re composable. You can use the methods in
the utility class cl_aunit_constraints to create powerful compositions like the condi-
tion {[not (constraint1 and constraint2)] or constraint3}, as shown in Listing 12.20.

DATA(constraint) =
cl_aunit_constraints=>or(
c1 = cl_aunit_constraints=>not(
cl_aunit_constraints=>and(
c1 = constraint1
c2 = constraint2 ) )
c2 = constraint3 ).

Listing 12.20 Constraint Composition

As usual, pay attention to the readability of your code and remember to make your
constraints and your assertion error messages clear.

12.6 Test Doubles


Whenever a class has dependencies to other classes or interfaces (as a parameter to a
method, an instance attribute, or simply by using the other element in its code), sepa-
rating the operation of the class from those dependencies is useful to better test its
unique behavior in isolation.
In this section, you’ll see several ways to isolate the class under test from its depended-
on components (DOC) and to create and inject test doubles that will play the role of
those components in the context of unit testing.

12.6.1 Dependency Inversion and Test Doubles


Many of the guidelines discussed in Chapter 3 on classes and interfaces and Chapter 4
on methods focus on creating clean designs that will be easier to consume and easier to
test.

Personal Copy for Andrei Arabolea, [email protected] 279


12 Unit Testing

The dependency inversion principle (the D in SOLID) is particularly relevant: One way to
design a class is to receive its needed dependencies as references passed to its construc-
tor. In this way, the class can be instantiated with test-specific implementations of
those dependencies, created and controlled by the test code. These test-specific imple-
mentations are called test doubles. This technique is also called dependency injection
because the production class receives its dependencies from the outside, instead of cre-
ating them by itself.

Use Dependency Inversion with Constructor Injection


When your class needs a resource to do its work, you should strive to receive that
resource as a constructor parameter.
Other ways are available to set up the dependency, like with setter injection, where you
receive your dependency in a setter method, or friend injection, where the test class
updates private attributes of the class under test directly.
However, with these other types of injection, the production class could have already
created its internal dependencies upon instantiation, which may result in inadvertent
side effects that could impact the test. The tests would not be isolated anymore, which
is an important characteristic of reliable tests.

Let’s revisit the thermal_switch class, presented in Chapter 3, Section 3.1.2, and refac-
tored in Listing 4.7, as described in detail in Chapter 4, Section 4.3. Its PUBLIC SECTION
definition is shown in Listing 12.21.

CLASS thermal_switch DEFINITION


FINAL
PUBLIC.

PUBLIC SECTION.

INTERFACES:
switchable,
thermal_protector.

ALIASES:
trip FOR thermal_protector~trip,
reset FOR thermal_protector~reset.

METHODS:
constructor
IMPORTING
managed_device TYPE REF TO switchable
sensor TYPE REF TO thermal_sensor.
...

Listing 12.21 The thermal_switch Class Public Section Definition

280 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.6 Test Doubles

In its constructor, the thermal_switch class receives two dependencies as parameters:


managed_device, for the switchable that’s managed by the class, and sensor, for the ther-
mal sensor that the class will listen to the event when a critical temperature is reached.
To test that the thermal_switch trips when a critical temperature is reached and turns
the switchable off, we’ll need implementations of those dependencies. We can create
stub classes that provide the needed implementations for the test, as local test classes
located in the same place as the unit test class. A stub is a simple kind of test double that
can have partial implementations and provide canned responses for some methods.
For the switchable dependency, a simple class that starts as on and can toggle to off is
enough. Listing 12.22 shows the device_stub class.

CLASS device_stub DEFINITION


FINAL
FOR TESTING.

PUBLIC SECTION.

INTERFACES:
switchable PARTIALLY IMPLEMENTED.

PRIVATE SECTION.

DATA:
turned_on TYPE abap_bool VALUE abap_true.

ENDCLASS.

CLASS device_stub IMPLEMENTATION.

METHOD switchable~is_on.
result = turned_on.
ENDMETHOD.

METHOD switchable~toggle.
turned_on = abap_false.
ENDMETHOD.

ENDCLASS.

Listing 12.22 A Device Stub Class

In the unit test, since we won’t be using the method switchable~is_off, we don’t need
to implement it in the test double. Normally, you would be forced to implement any
non-optional interface methods. But in a test class, and only in a test class, you can use

Personal Copy for Andrei Arabolea, [email protected] 281


12 Unit Testing

the PARTIALLY IMPLEMENTED addition to the interface and omit the implementations that
you don’t need. If later your test code uses the unimplemented methods, a runtime
error will occur. So, only leave out the methods that your test doesn’t need.
Then, device_stub is designed to start as on and able to be toggled to off just once. Later,
we might add more features to our stub when we add more tests, but this design will
suffice for now.
The other test double that we need is an implementation of the thermal_sensor inter-
face, shown in Listing 3.13, described in detail in Chapter 3, Section 3.1.2. The sensor_stub
interface shown in Listing 12.23 implements the thermal_sensor interface and provides
a simple way to raise the temperature event with its raise_event method.

CLASS sensor_stub DEFINITION


FINAL
FOR TESTING.

PUBLIC SECTION.

INTERFACES: thermal_sensor.

METHODS: raise_event.

ENDCLASS.

CLASS sensor_stub IMPLEMENTATION.

METHOD raise_event.

RAISE EVENT
thermal_sensor~critical_temperature_reached.

ENDMETHOD.

ENDCLASS.

Listing 12.23 A Sensor Stub Class

Stubs can provide methods or attributes that help the test check or trigger interactions
with the class under test, like the raise_event method in Listing 12.23. As a result, a test
class that uses stubs will usually type their references to the stub class so that these
extra features can be accessed.
With those two stubs, the thermal_switch_trip test class can check the tripping func-
tionality of the thermal_switch, as shown in Listing 12.24.

282 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.6 Test Doubles

CLASS thermal_switch_trip DEFINITION


FINAL
FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.

PRIVATE SECTION.

METHODS:
setup,
assert_device_off,
turns_off_when_tripped FOR TESTING,
turns_off_when_temp_critical FOR TESTING.

DATA:
thermal_switch TYPE REF TO thermal_protector,
device TYPE REF TO switchable,
sensor TYPE REF TO sensor_stub.

ENDCLASS.

CLASS thermal_switch_trip IMPLEMENTATION.

METHOD setup.

" Given that the managed device is on


device = NEW device_stub( ).

" device_stub starts as on, this is just a sanity check


cl_abap_unit_assert=>assert_equals(
exp = abap_true
act = device->is_on( )
msg = 'The device should be on' ).

sensor = NEW #( ).
thermal_switch = NEW thermal_switch(
managed_device = device
sensor = sensor ).

ENDMETHOD.

METHOD turns_off_when_tripped.

" When the thermal switch is tripped manually

Personal Copy for Andrei Arabolea, [email protected] 283


12 Unit Testing

thermal_switch->trip( ).

" Then the device is turned off


assert_device_off( ).

ENDMETHOD.

METHOD turns_off_when_temp_critical.

" When a critical temperature is reached


sensor->raise_event( ).

" Then the thermal switch trips and the device is turned off
assert_device_off( ).

ENDMETHOD.

METHOD assert_device_off.
cl_abap_unit_assert=>assert_equals(
exp = abap_false
act = device->is_on( )
msg = 'The device should not be on' ).
ENDMETHOD.

ENDCLASS.

Listing 12.24 Unit Test Class Using Stubs

The two scenarios implemented in Listing 12.24 can be described in the following way:
쐍 Given that the managed device is on, when the thermal switch is tripped manually,
then the device is turned off.
쐍 Given that the managed device is on, when a critical temperature is reached, then the
thermal switch trips and the device is turned off.

Using custom stubs is one way to create simple and effective implementations of the
DOCs. However, you must code stubs by hand and carefully implement the necessary
features that will benefit the test. The next section will present a generally better alter-
native to using stubs.

12.6.2 ABAP Object-Oriented Test Double Framework


The ABAP Object-Oriented Test Double Framework (also called simply the ABAP Test
Double Framework) is a tool that simplifies the implementation and configuration of
test doubles. With its entry point in the cl_abap_testdouble class, you can create

284 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.6 Test Doubles

automatic implementations of global interfaces or global classes and configure


method return values, optionally based on which input parameters were passed in.
You can also control the raising of exceptions and events.
The ABAP Test Double Framework allows for simple stubs, as described in the previous
section, and also allows mocks. A mock is a kind of test double that can be configured
with certain expectations, such as how many times a method should be called and with
which input parameters. The test code configures the mock and injects it into the class
under test, which then interacts with the mock. Later, the test code can verify that the
interaction was as expected, raising an assertion error otherwise.
Let’s now check that, when the thermal_switch class trips, the managed device toggle
method is called if the device is on, but not called if the device is off.
In our example, we’ll configure a mock object with the ABAP Test Double Framework
for the switchable interface and check that toggle is called (or not) as appropriate for
each scenario. We’ll also create a stub for the thermal_sensor interface, but only because
thermal_switch needs it. We won’t use that stub otherwise. Look at the test class ther-
mal_switch_trip_toggle shown in Listing 12.25 for the relevant scenarios.

CLASS thermal_switch_trip_toggle DEFINITION


FINAL
FOR TESTING
DURATION SHORT
RISK LEVEL HARMLESS.

PRIVATE SECTION.

METHODS:
setup,
toggles_on FOR TESTING,
doesnt_toggle_off FOR TESTING.

DATA:
thermal_switch TYPE REF TO thermal_protector,
device TYPE REF TO switchable,
sensor TYPE REF TO thermal_sensor.

ENDCLASS.

CLASS thermal_switch_trip_toggle IMPLEMENTATION.

METHOD setup.

device ?= cl_abap_testdouble=>create( 'switchable' ).


sensor ?= cl_abap_testdouble=>create( 'thermal_sensor' ).

Personal Copy for Andrei Arabolea, [email protected] 285


12 Unit Testing

thermal_switch = NEW thermal_switch(


managed_device = device
sensor = sensor ).

ENDMETHOD.

METHOD toggles_on.

" Given that the managed device is on


cl_abap_testdouble=>configure_call(
device )->returning( abap_true ).
device->is_on( ).
cl_abap_testdouble=>configure_call(
device )->and_expect( )->is_called_once( ).
device->toggle( ).

" When the thermal switch is tripped manually


thermal_switch->trip( ).

" Then the device is toggled


cl_abap_testdouble=>verify_expectations( device ).

ENDMETHOD.

METHOD doesnt_toggle_off.

" Given that the managed device is off


cl_abap_testdouble=>configure_call(
device )->returning( abap_false ).
device->is_on( ).
cl_abap_testdouble=>configure_call(
device )->and_expect( )->is_never_called( ).
device->toggle( ).

" When the thermal switch is tripped manually


thermal_switch->trip( ).

" Then the device is not toggled


cl_abap_testdouble=>verify_expectations( device ).

ENDMETHOD.

ENDCLASS.

Listing 12.25 Using the ABAP Test Double Framework

286 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.6 Test Doubles

To create a test double with the ABAP Test Double Framework, you’ll need a reference
typed to the desired global interface or class (it doesn’t work with local types). The ref-
erence can be written simply, such as in the following code:

DATA device TYPE REF TO switchable.


device ?= cl_abap_testdouble=>create( 'switchable' ).

The device variable now holds a fully functional implementation of switchable, with all
methods implemented with empty bodies.
Then, the toggles_on test method configures the call to switchable~is_on to return
abap_true, simulating that the device is on, as shown in Listing 12.26.

cl_abap_testdouble=>configure_call(
device )->returning( abap_true ).
device->is_on( ).

Listing 12.26 Configure device->is_on to Return abap_true

The test method also configures that the call to switchable~toggle must happen exactly
once, as shown in Listing 12.27.

cl_abap_testdouble=>configure_call(
device )->and_expect( )->is_called_once( ).
device->toggle( ).

Listing 12.27 Expectation that device->toggle is Called Exactly Once

To configure a call, you’ll complete two steps:


1. Use the method cl_abap_testdouble=>configure_call on the mock reference. You
can configure EXPORTING and RETURNING parameters, raise exceptions or events,
decide whether to ignore input parameters, and set expectations for the number of
calls a method should receive, which makes the test double into a true mock object.
2. To finish off the configuration, you’ll call the desired method to signal to the frame-
work which method is being configured, with any needed input parameters. Input
parameters, if not being ignored, must match the parameters that the class under
test will use to call the method.

After exercising the class under test, which in our example is the call thermal_switch->
trip( ), then the test code verifies that the mock was called exactly as specified:

cl_abap_testdouble=>verify_expectations( device ).

The framework has many more features. You can find additional information at the fol-
lowing link: http://s-prs.co/v519006. You can also learn more about it in the documen-
tation for class cl_abap_testdouble.

Personal Copy for Andrei Arabolea, [email protected] 287


12 Unit Testing

Use the ABAP Test Double Framework, Only Mock What’s Needed
As you’ve seen, the ABAP Test Double Framework is a powerful tool to create stubs and
mocks and automates the tedious process of manually creating, configuring, and
asserting against test doubles. The ABAP Test Double Framework can cover a wide
range of scenarios, so consider using it by default.
Revert to manually implemented test doubles when you need features the ABAP Test
Double Framework does not provide. As an example, thermal_sensor is an atypical
interface, which contains only one event and no methods. To configure the framework
to raise an event, you need at least one method to trigger the event, and thus, you
would need to implement thermal_sensor manually, as in the sensor_stub class
shown earlier in Listing 12.23.
Note that not every dependency needs mocking or stubbing. Some dependencies are
not used by the class under test. Don’t set data that your test doesn’t need, and don’t
mock objects that are never called.
In some cases, you might not mock anything at all, for instance, when testing data
structures and data containers. As another example, your unit tests may work with the
productive version of a transient_log because it only stores data without any side
effects.

12.6.3 More ABAP Test Tools


In general, a clean programming style will let you do much of the work with standard
ABAP unit tests and test doubles with the ABAP Test Double Framework.
However, other ABAP test frameworks and tools allow you to tackle specialized and
trickier cases in elegant ways. A quick rundown of what’s available includes the follow-
ing tools:
쐍 ABAP SQL Test Double Framework
Allows you to stub database tables and create tests for Open SQL statements com-
pletely decoupled and isolated from the underlying database. For more information,
visit the following URL: http://s-prs.co/v519007.
쐍 ABAP CDS Test Double Framework
Works like the ABAP SQL Test Double Framework, but for CDS views. CDS views often
have tricky logic, which can be exercised with this framework. You can also use test
relations (shown earlier in Listing 12.3) to link your unit test classes with the target
CDS views. For more information, visit the following URL: http://s-prs.co/v519008.
쐍 CL_OSQL_REPLACE
Implements a replacement service that allows you to test Open SQL statements by
redirecting access to data sources—either database tables, views, or CDS views—to
test data bins that can be filled with test data. For more information, visit the follow-
ing URL: http://s-prs.co/v519009.

288 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.7 Test Seams

쐍 ABAP Authority Check Test Helper API


A flexible tool that allows for the mocking of ABAP AUTHORITY-CHECK statements.
Accessible through the cl_aunit_authority_check class where available. For more
information, visit the following URL: http://s-prs.co/v519010.

You can find more information in the online documentation for each tool, like imple-
mented features, code examples, and platform/version compatibility.

Don’t Build Test Frameworks


Unit tests—as opposed to integration tests—should be data-in-data-out, with all test
data being defined on the test code as needed.
Use the available tools for ABAP unit testing. Don’t start building frameworks, for
example, something to distinguish “test case IDs,” to decide what data to provide. The
resulting code will be harder to maintain in the long run.

12.7 Test Seams


Test seams allow you to mark sections of code in your class and replace them with test
specific code in your unit test class.
For example, let’s say you have a sales_document class with the following private data
and method declarations, as shown in Listing 12.28.

DATA document_currency TYPE currency.


METHODS convert_to_document_currency
IMPORTING source TYPE money
RETURNING VALUE(result) TYPE money.

Listing 12.28 Declarations for a sales_document Class

The convert_to_document_currency method is an instance utility method that converts


an input amount to a result using the document_currency of the sales document, as
shown in Listing 12.29.

METHOD convert_to_document_currency.
TEST-SEAM currency_conversion_seam.
CALL FUNCTION 'CONVERT_AMOUNT_TO_CURRENCY'
EXPORTING
date = sy-datum
foreign_currency = source-currency
foreign_amount = source-amount
local_currency = document_currency
IMPORTING
local_amount = result-amount.

Personal Copy for Andrei Arabolea, [email protected] 289


12 Unit Testing

result-currency = document_currency.
END-TEST-SEAM.
ENDMETHOD.

Listing 12.29 Method with a Test Seam

The method calls a function module for the currency conversion. Testing the sales_
document class when currency conversion is involved is tricky because currency conver-
sion is volatile and non-deterministic. To control what is returned from the method,
you wrap the function module call in a TEST-SEAM block. In the test code, you’ll create a
TEST-INJECTION block for that test seam—using its name—which will completely
replace the test seam with the code within the test injection, as shown in Listing 12.30.

METHOD add_item_with_diff_currency.
TEST-INJECTION currency_conversion_seam.
result = VALUE #(
amount = 1000
currency = document_currency ).
END-TEST-INJECTION.
...

Listing 12.30 Test Seam Injection in a Test Method

Although powerful at first sight, test seams are quite invasive and can become coupled
with private dependencies. Note how the injection code in Listing 12.30 has access to
the document_currency private attribute, since it is stitched into the production code at
the place of the test seam. Ultimately, test seams can erode your design and make your
code harder to understand and maintain in the long run.
Nevertheless, test seams can be invaluable for legacy code, in which dependencies are
not clear-cut and interfaces are usually nonexistent. Test seams can be used as a step-
ping stone to modifying legacy code under test and improving its design, slowly carv-
ing out dependencies and replacing test seams with DOCs that implement proper
interfaces.
There are two alternatives to using test seams:
쐍 Test flags
An older alternative to using test seams was the use of test flags in the productive
code that would be activated only in a unit test context. Listing 12.31 shows an exam-
ple of using a test flag.

IF me->in_test_mode = abap_true.
...

Listing 12.31 Checking for a Test Flag in Production Code

290 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.8 Principles

This style pollutes the productive code with intrusive testing concerns, making it
harder to read and maintain. We recommend using test seams instead since test
seams are simple demarcations, more lightweight and less intrusive.
쐍 Testing subclasses
Yet another way to adjust the behavior of the class under test is to create a testing
subclass that inherits from the class and redefines methods to have test-specific
behavior. Although this works, testing subclasses are fragile because the tests can
break when the code is refactored. Furthermore, if your class under test is not
designed for inheritance, testing subclasses also enable real consumers to inherit
from the class, which might create undesired coupling and complexity. For more on
inheritance, refer to Chapter 3.
To get legacy code under test, resort to test seams instead of testing subclasses. Test
seams don’t force you to change the productive behavior of your code, which could
happen when enabling inheritance or by changing a method scope from private to
protected.

A good resource for the process of improving legacy code is the book Working Effec-
tively with Legacy Code (Prentice Hall, 2004). Note that, in that book, the term test seam
is used more generally to refer to any part of the productive code that can be replaced
by test-specific code, whether a DOC based on an interface to be replaced by a test dou-
ble or a protected or public method that can be overridden in a testing subclass.

12.8 Principles
Now that you’ve seen the mechanics of how to write ABAP unit tests and some import-
ant code-level concepts and guidelines, let’s turn our focus to some high-level princi-
ples and processes of unit tests and automated tests in general.

12.8.1 Test-Driven Development


The most important process to be aware of is test-driven development (TDD). TDD is a
staple of modern agile software development and an important part of any developer
toolbox. Simply stated, TDD refers to the iterative process of coding your most granu-
lar scenarios one-by-one, following this cycle:
1. Write a failing unit test.
2. Write just enough code to make the test pass.
3. Repeat until satisfied.

The surprising nature of this process, and perhaps an important reason why it works, is
that you write the test before you write the code. Not only will this approach cover the
code with a timely test, you’ll also be forced to think about how you’ll consume the code

Personal Copy for Andrei Arabolea, [email protected] 291


12 Unit Testing

from an external perspective. The unit test becomes the first consumer of the code,
even before the code exists, driving its design in a sensible way. The code becomes test-
able by default. The process is often extended to tighten up the code and its tests:
1. Write a failing unit test.
2. Make the failure message clear. (Remember the guideline to make it easy to see
what’s wrong when an assertion fails.)
3. Write just enough code to make the test pass.
4. Refactor to improve the design.
5. Repeat until satisfied.

With this process, you’ll drive your design and code with tests and ensure that the code
and the tests remain clean and have high quality.

Consider TDD, Make Your Code Testable


Consider practicing and using TDD in your daily coding routine. Try TDD where it makes
sense; revert to other processes where it doesn’t. Often, applying TDD in ABAP projects
is difficult due to a lot of legacy code.
Legacy code is challenging and might require some investment to bring it under con-
trol. Pace yourself and carefully refactor and cover legacy code with tests, at least the
parts of the code that you need to change, either to add new features or to fix defects.
Try to isolate new features and practice TDD when you can.
In the end, your code should not only be testable, but be tested with a high code cover-
age.

If you write code to be consumed by other developers, enable them to write unit tests
for their own code, for example, by creating interfaces for all outward-facing methods,
providing helpful test doubles that facilitate integration tests, and applying depen-
dency inversion to enable consumers to substitute productive DOCs with test doubles.

12.8.2 Clean Test Properties


Beyond TDD, you should keep the following clean test properties in mind:
쐍 Test code should be professional
Test code is real code. All the high-quality standards and clean code guidelines that
apply to production code also apply to test code. Test code usually has a simpler
design than production code, but all code should still follow all the rules of good
design: low-coupling, high-cohesion, and everything in-between.
쐍 Test code should be automatic
Automatic tests are tests that execute automatically and that fail or succeed without

292 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.8 Principles

the need to manually check their output. They can be scheduled and report failures
automatically, for example, via email.
Don’t use a test report that calls some code in a specific way and that requires man-
ually checking for the expected output. Take the next step and create an automated
ABAP unit test, with an automatic assertion that tells you whether the test succeeds
or fails.
쐍 Tests should be isolated
ABAP Unit test methods and classes should not depend on each other or on the
order of their execution. The ABAP Unit execution environment doesn’t guarantee
the order in which unit test methods execute. Each unit test method should exercise
a granular scenario, and you should ensure that each unit test method can run iso-
lated and independent from all other scenarios.
At the same time, unit tests should not depend on the environment in which they
run. They should be system independent. Unit tests should run on different systems
in the same way. Use dependency injection and test doubles and other specialized
ABAP unit testing tools to achieve this consistency.
쐍 Tests should be fast
Tests should execute quickly. This makes your whole suite of tests fast and allows
you to run the tests as frequently as possible to check your code, ideally after each
small change. Note that isolation helps to make the tests run fast.
쐍 Test the public interface
The public components of your classes and interfaces are the components its con-
sumers will ultimately use. Your tests should mimic those consumers: Use your
classes and interfaces the way they are intended to be used, through their public
interfaces.
Private and even protected components are implementation details of your classes
that are less stable and that are unimportant to consumers. Do not invade the
encapsulation boundary of the class and test its private or protected components
through friendship access or inheritance.
A need to test private or protected methods may be an early warning sign of several
kinds of design flaws. Ask yourself the following questions:
– Is there a concept buried in your class that wants to come out into its own class,
with its own dedicated suite of tests?
– Are the domain logic and the glue code intertwined? For example, implementing
domain logic directly in the class that is plugged into Business Object Processing
Framework (BOPF) as an action, determination, or validation, or that was gener-
ated by SAP Gateway as a *_DPC_EXT data provider, creates unnecessary coupling
and makes your code harder to read and understand.
– Are interfaces too complicated, and do they request too much data that is irrele-
vant or that cannot be mocked easily?

Personal Copy for Andrei Arabolea, [email protected] 293


12 Unit Testing

12.8.3 Test Coverage


You’ve already seen how to execute unit tests with coverage in Section 12.1.4. The ABAP
coverage report can display the following coverage metrics:
쐍 Statement coverage
The amount of code statements that were executed by the tests.
쐍 Branch coverage
The amount of code branches that were executed by the tests.
쐍 Procedure coverage
The amount of methods or functions that were executed by the tests.

While TDD will naturally result in tests with a high coverage, reaching 100% coverage of
all coverage types might be impractical, or even impossible, for many reasons.

Don’t Obsess about Test Coverage


Test coverage helps you find code you forgot to test or helps you measure steady prog-
ress in covering legacy code. Test coverage is not meant to meet some random key per-
formance indicator (KPI).
Don’t make up tests just to reach a desired coverage. Tests are for exercising scenarios
and checking the functionality of your classes.

12.9 Summary
In this chapter, we delved into the crucial discipline of unit testing in ABAP. We started
by presenting the ABAP Unit tool, test classes, test methods, and the notion of the class
under test. Throughout this chapter, we discussed the following guidelines:
쐍 Pick the most appropriate values for DURATION and RISK LEVEL.
쐍 Strive for SHORT and HARMLESS unit test classes.
쐍 Use local test classes for unit tests.
쐍 Use a separate global class as a container to higher-level tests.
쐍 Use test relations to refer to external objects being tested.
쐍 Mark all classes made for testing explicitly as FOR TESTING.
쐍 Know when to use teardown.
쐍 Use the given-when-then style.
쐍 Tests should consume the class under test as intended.
쐍 Use LOCAL FRIENDS to access a private constructor.
쐍 Call test classes by their purpose, either given or when.
쐍 Call test methods by what’s expected: when and then.
쐍 Name the class under test meaningfully.

294 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


12.9 Summary

Then, we talked about assertions and test doubles, focusing on the following recom-
mendations:
쐍 Use as few and focused assertions as possible.
쐍 Assert content, not quantity.
쐍 Assert quality, not content.
쐍 Make it easy to see what’s wrong when an assertion fails.
쐍 Forward irrelevant static exceptions as cx_root.
쐍 Use dependency inversion with constructor injection.
쐍 Use the ABAP Test Double Framework; only mock what’s needed.
쐍 Don’t build test frameworks.

Test seams were discussed next, which are great tools for dealing with legacy code.
Finally, we presented the following principles that you should consider when imple-
menting unit testing:
쐍 Consider TDD and make your code testable.
쐍 Test code should be professional.
쐍 Test code should be automatic.
쐍 Tests should be isolated.
쐍 Tests should be fast.
쐍 Test the public interface.
쐍 Don’t obsess about test coverage.

In the next chapter, we’ll investigate the highest-level components to structure your
ABAP code: packages.

Personal Copy for Andrei Arabolea, [email protected] 295


© 2025 by Rheinwerk Publishing Inc., Boston (MA)
Chapter 13
Packages
Packages provide a simple yet powerful way to structure software. Used
properly, packages ensure modularity, cohesion, simplicity of consump-
tion, and ease of refactoring. When packages are omitted at the begin-
ning of a project, you open the door to massive technical debt and
nightmares even for well-intentioned developers.

Too often, packages end up being afterthoughts to a development project, which leads
to technical debt and becomes exponentially more difficult to manage as new objects
are created.
On the flipside, mindfully designing a package hierarchy, with the right objects being
exposed or hidden to external consumers, takes a bit more time to set up at the begin-
ning, but this approach is proven to be tremendously useful for any project, regardless
of size or duration.
In Section 13.1, we’ll first cover general principles to create higher-level structures for
your code and creating packages. Then we’ll dive more deeply into ABAP-specific pack-
age concepts in Section 13.2, followed by a look at package design, package interfaces,
usage, and package hierarchies in Section 13.3. Then, in Section 13.4, we’ll introduce you
to package checks and their usage before closing out this chapter with a discussion of
the importance of sound package strategies in Section 13.5.

13.1 General Package Concepts


In software programming, packages allow you to group and structure large numbers of
development objects into smaller, more manageable and organized subsets of objects.
While high cohesion and loose coupling can be achieved within classes, packages allow
you to apply the same old, yet still valid, principles of cohesion and coupling at a higher
level. Several decades ago, David Parnas described his idea of modular design focusing
on high cohesion, which is the degree to which the elements inside a given set belong
together, and loose coupling, which is the degree of interdependence between ele-
ments.
In the following sections, we’ll consider package use cases and key concepts like reuse
levels and cohesion.

Personal Copy for Andrei Arabolea, [email protected] 297


13 Packages

13.1.1 Use Cases


Packages help you slice and dice a set of different development objects according to
specific criteria. You must carefully plan these criteria before starting an implementa-
tion, since moving objects around after their creation is time consuming and usually
results in a lot of technical debt.
You can group objects into packages in many ways, such as the following:
쐍 Layers
쐍 Applications
쐍 Frameworks
쐍 Tests
쐍 Reuse levels

We’ll cover several concrete examples in Section 13.3.

13.1.2 Reuse Levels


Packages not only help you group semantically related objects together, but also help
expose the right objects to the right consumers. This selectivity is based on reuse levels,
which basically tell, at a high level, who can use what.
The main reuse levels, from the most restricted to the most open, are:
1. No reuse
The contained objects only exist for internal modularity.
2. Private reuse
Objects can be reused among a small set of objects, such as within a given applica-
tion only.
3. Internal reuse
Several different applications or layers could reuse those objects, but these objects
are still considered restricted (e.g., internal to SAP, internal to partner code).
4. Public reuse
Objects that anyone can use or consume, such as for customers and partners.

By providing a clear purpose for each package, reuse intents are obvious and less arbi-
trary. Too often, reusable objects are duplicated because that they were meant to be
reused is not obvious, and too often, internal objects are reused because whether they
are meant to be reused or whether they only exist for internal modularity is not clear.
Making reuse levels explicit helps you keep a proper level of cohesion, which we’ll dis-
cuss in the next section.

298 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


13.2 Package Concepts in ABAP

13.1.3 Cohesion
In a software development context, cohesion is a quality attribute or a measure of how
elements grouped together, such as within a given package, have a relationship with
each other. A counterexample would be a bunch of unrelated elements, such as classes
that have nothing to do with each other all under the same package.
Cohesion can be kept high as long as you carefully place each element at the right place.
Failure to organize your elements can quickly grow out of control, leading to technical
debt and accidental architectures.
Cohesion applies in several different areas of software development, such as the cohe-
sion of methods defined within a class or an interface; however, in this chapter, we’ll
focus on the cohesion of the development objects contained in a given package.
Strong or high cohesion brings several advantages, such as the following:
쐍 Reduced complexity or entanglement
쐍 Better maintainability
쐍 Improved reusability

13.2 Package Concepts in ABAP


While ABAP supports all package concepts like visibility, encapsulation, and depreca-
tion, you must understand both the terminology that is specific to ABAP and how to
apply a conceptual vision to ABAP packages.
In the following sections, we’ll explore the three main parts of the package concept in
ABAP, before delving into encapsulation, interfaces, and best practices for packages.

13.2.1 Package Types


Three types of packages are available in ABAP:
쐍 Structure packages
쐍 Main packages
쐍 Development packages

As shown in Figure 13.1, the following relationships can exist among packages:
쐍 A structure package can contain structure packages, main packages, and develop-
ment packages. Its primary purpose is to serve as the root element of an application
or an entire solution.
쐍 A main package can contain main packages and development packages. Its primary
purpose is to structure development content at a high level, such as layers, compo-
nents, features, etc.

Personal Copy for Andrei Arabolea, [email protected] 299


13 Packages

쐍 A development package is the only type of package that can contain development
objects. In fact, this package can contain both development packages and develop-
ment objects; however, we recommend that a development package either contains
only development packages or only development objects, not both at the same time.

Structure Main Development Development


Package Package Package Object

For example, class, interface,


program, ABAP Data Dictionary
object, BAdI, etc.

Figure 13.1 Package Types and Their Possible Content

Each package can have up to two roles at the same time:


쐍 Server package
Development objects from this package are exposed to the outside for consump-
tion.
쐍 Client package
Development objects from this package are consuming development objects out-
side of that package.

13.2.2 Encapsulated Packages


When creating a package within ABAP Development Tools (ADT), we recommend
defining your packages as encapsulated, as shown in Figure 13.2.

Figure 13.2 Flag to Set a Package as Encapsulated

With encapsulation, all development objects of the package are not visible by default.
To allow their usage outside of the package, these objects must be exposed through a
package interface, which we’ll describe in Section 13.2.3. In contrast, a package that is
not encapsulated means that all its objects can be used publicly by anyone. In Java,

300 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


13.2 Package Concepts in ABAP

lacking encapsulation is equivalent to defining all your objects as public, which quickly
leads to high coupling with unintended consumers. This Encapsulated flag has a direct
impact on the package checks, which we’ll explain in Section 13.4.

13.2.3 Package Interfaces


A package interface is an object that allows the exposing of development objects out-
side a given package for external consumption. Package interfaces can expose any
ABAP development object but can also expose other package interfaces.
As shown in Figure 13.3, three types of package interfaces are available: standard
(default), virtual default, and filter.

Figure 13.3 Types of Package Interfaces

Most package interfaces that you’ll create will be standard, since the other two options
are only possible for structure packages. The virtual default interface is a blank check
giving access to all objects; however, this access could be restricted to a certain name-
space using the filter interface. If you don’t see the option to choose the type, that
means it’s limited to the standard package interface.
Now that you know that most package interfaces you’ll create are standard package
interfaces, let’s understand the scope definition of a package interface. Why is the scope
definition important? Of all the objects you may want to expose outside a package, you
rarely want to expose them all equally to the same consumers. Thus, we recommend
using the following three scopes:
쐍 Public
쐍 Internal
쐍 Restricted

Among these three scopes, the first two are the most commonly used. While checks can
validate the usage of restricted package interfaces, no built-in ABAP functionality can

Personal Copy for Andrei Arabolea, [email protected] 301


13 Packages

validate the usage of other interfaces. Thus, you must rely on a naming convention to
clearly expose the purpose of a given package interface.
In the following sections, we’ll elaborate on each of these scopes in more detail.

Public Package Interfaces


As shown in Table 13.1, this scope is to publish the selected package content for con-
sumption by any external packages. No restriction exists on client packages, and we
should assume that we won’t be aware of all possible consumers.

Naming Convention Typical Content

<PACKAGE_NAME>_PIF 쐍 ABAP Data Dictionary elements (except database tables)


쐍 ABAP class interfaces

Table 13.1 Guidelines for the Public Package Interface

Internal Package Interfaces


As shown in Table 13.2, this scope is to publish selected package content for consump-
tion by other development packages of the same application. While no restriction
exists on client packages with built-in checks, the semantic meaning should let the
consumers know that they are exposed internally, and not publicly.

Naming Convention Typical Content

<PACKAGE_NAME>_IIF ABAP classes

Table 13.2 Guidelines for the Internal Package Interface

Restricted Package Interfaces


As shown in Table 13.3, this scope is to publish selected package content for consump-
tion by specific development packages within the same application or in other layers or
main packages of the same solution.

Naming Convention Typical Content

<PACKAGE_NAME>_RIF Database tables

Table 13.3 Guidelines for the Public Package Interface

While creating a package is straightforward, adding restrictions can be tricky. For exam-
ple, an external package MY_EXTERNAL_PACK would need to access database tables for test-
ing purposes or need to create core data services (CDS) views on top of them. The way
to achieve this access would involve the following steps (from Transaction SE21):
1. Create package interface MY_RESTRICTED_PACK_RIF to expose the required database
tables in a restricted package interface.

302 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


13.2 Package Concepts in ABAP

2. Select the Enable Restriction of Client Packages flag, as shown in Figure 13.4.

Figure 13.4 Enabling Restrictions on Client Packages

3. Add the package MY_EXTERNAL_PACK under the Restriction of Client Packages tab and
select Point-to-Point Access, as shown in Figure 13.5.

Figure 13.5 Setting the Point-to-Point Access

4. Add a Use Access for package interface MY_RESTRICTED_PACK_RIF in the parent pack-
ages of MY_EXTERNAL_PACK and within MY_EXTERNAL_PACK itself, as shown in Figure 13.6.

Figure 13.6 Creation of a Use Access

Package maintenance and use accesses are covered in more detail later in Section 13.4.1.

13.2.4 Best Practices


Defining the scope of a package interface is relatively straightforward, so our main rec-
ommendation is to avoid exposing everything in a single package interface. Instead, we
recommend the following two best practices:
쐍 Create multiple package interfaces
Even if a given element is exposed in different interfaces, you should bundle objects
into interfaces with their potential consumers in mind.
쐍 Put meaningful descriptions on elements
Doing so will ease the need for research by the potential consumers and explain
right away the purpose of each object.

Personal Copy for Andrei Arabolea, [email protected] 303


13 Packages

While these rules are simple, not following them makes it tremendously difficult for
consumers to choose what to use or not.
Now, the challenge is to properly select which element to expose in which interface.

13.3 Package Design Options


As shown in Figure 13.7, you can break down software into several smaller parts. In this
example, we limited ourselves to two dimensions, either per layer or per application or
feature, but this example can become more complex if we consider other dimensions,
perhaps leading to a hexagonal architecture.

Legend App/Feature A App/Feature B


✓ Access allowed

? Access possible Layer n


X Access prohibited

✓ ✓

Layer n -1

X ✓ X X ✓


?

✓ ✓
?
Layer 0

Figure 13.7 Example of a Software Broken Down into Several Packages

The challenge is how to map these smaller parts to a hierarchy of packages, which are
all related with a simple parent-child relation. This challenge would ultimately force us
to either first split by layer or by feature, as shown in Figure 13.8.
Each approach has their pros and cons, and neither approach will be perfect for every
project. Therefore, you must carefully consider the implications in your project, and
what you explicitly want to allow or prohibit, before you create your package hierarchy.

304 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


13.3 Package Design Options

My Project By Layer My Project By Application

Layer 1 Layer 0 App A App B

App A App A Layer 1 Layer 1

App B App B Layer 0 Layer 0

Figure 13.8 Side-by-Side Comparison of a Package Broken Down by Layer and by Application

In the following sections, we’ll explore each of these approaches in more detail.

13.3.1 Side-by-Side Application Hierarchies


As shown in Figure 13.9, you could first break down the package by application. In turn,
the content of each application could be further broken down into technical packages,
such as layers.

Legend Products Customers


✓ Access allowed

? Access possible
API API

✓ ✓

BO ? BO

✓ ✓

Data Data

Figure 13.9 Example of Package Structure by Application

Personal Copy for Andrei Arabolea, [email protected] 305


13 Packages

In our example, each application is self-contained and easier to manage; however, set-
ting up access to specific parts of another application is more challenging. For instance,
if you were to expose some business objects (BOs) and application programming inter-
faces (APIs) from customers to products, we do not really have control over which parts
of products would consume which part of customers; for instance, the product BO
could access the customers API. As shown in our example, if you were to expose the
customers API, then unless you have knowledge on how the products packages are
designed and combined to a restricted package interface, you cannot easily control
which part of the products code would access the customers API.

13.3.2 Layer-Based Hierarchies


As shown in Figure 13.10, another possible breakdown is by technical packages first,
such as layers, and then further by application.

Legend
✓ Access allowed

? Access possible

API BO Data

Product_API ✓ App A ✓ App A


- -

? ? ? ? ?
Customer App B App B
✓ ✓

Figure 13.10 Example of Package Structure by Layer

In this example, each layer is self-contained and easier to manage, and we can easily
prohibit one layer from accessing objects in another layer. However, within one layer,
controlling internal application consumption is more difficult.

13.3.3 Translation Relevance Split Hierarchies


There is no default value for the classification of translation relevance, as shown in Fig-
ure 13.11. This property is mandatory and must be defined as soon as you create a pack-
age.

306 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


13.3 Package Design Options

Figure 13.11 Possible Values for the Translation Relevance of a Package

When you select a value for the translation relevance, you directly make the decision
on whether the texts stored in the package in question (such as UI texts, message texts,
or long texts for (F1) help) will be translated and into which target languages.
Thus, the classification of translation relevance determines the amount of translation
required in follow-on translation systems for the repository objects in a given package.
The translation relevance of a package is classified by an indicator set for the package in
question. This classification is not inherited by subpackages. Every package and sub-
package must be classified separately, and they can have different classifications from
their parent package.
The classification of translation relevance is based on various roles or target groups.
You can select precisely one of the translation relevance options for each package listed
in Table 13.4.

Translation Option Purpose

Administration Tools (supports The packages with this classification contain texts
translation into English, German, intended for system administrators. This target group
French, and Japanese) is provided with administration tools available in the
four standard language versions. A package with this
classification must not contain any texts intended for
the end users of business applications.

End User – Translation into all Sup- The packages with this classification contain texts
ported Languages intended for business end users. The texts in these
packages are translated into all supported languages.
Thus, projects including packages with this classifica-
tion incur the highest level of translation costs.

End User – Reduced Translation The packages with this classification contain texts
Scope (project-specific) also intended for business end users. However, the
translation-relevant texts in these packages are usu-
ally translated according to country-specific require-
ments, which reduces the number of translation
languages from the previous option. In this case, the
target languages are not predefined, and the transla-
tion scope must be discussed with the responsible
translation coordinator.

Table 13.4 Translation Options and Their Purposes

Personal Copy for Andrei Arabolea, [email protected] 307


13 Packages

Translation Option Purpose

Development Tools (translation into The packages with this classification contain texts for
English and German) developers. These packages generally consist of ABAP
Workbench applications, such as tools for develop-
ment, testing, and analysis, plus additional support
tools, and methods for modifying or enhancing code.
The texts in the packages are translated into English
and German only.

No Translation The packages with this classification are not trans-


lated.

Table 13.4 Translation Options and Their Purposes (Cont.)

As a rule of thumb, local packages, such as the temporary packages ($TMP), and the HOME
packages are never translated.
You must carefully decide the translation relevance of a package and be sure you put
your development objects in the right packages, since changing the translation rele-
vance of a package or moving a development object between two packages with differ-
ent translation relevance can lead to issues, such as displaying obsolete texts or
missing translations.
Now, in some situations, you may have objects that require translation (e.g., a message
class, a data element), while other objects don’t (classes, business objects), even though
these objects all semantically belong within the same package. Unfortunately, you can-
not select a subset of objects for translation; the way to achieve this separation is an
additional package breakdown by translation relevance with the following subpackages:
쐍 <my_package>_TRANSL to store translation-relevant objects
쐍 <my_package>_NO_TRANSL to store objects that don’t need translation

As you’ve seen in this section, designing your package hierarchy ahead of time is criti-
cal so that everyone works with the same vision in mind. To achieve this consistency, a
good naming convention for the package is necessary, but not sufficient. Names alone
cannot guarantee your package structure nor guarantee that each object will be in the
right place. However, proper prefixes (e.g., MYSOL_<xyz>) or suffixes (e.g., <xyz>_TRANSL)
can make navigation easier and guide developers. Thus, the most reliable way to ensure
adherence to your package strategy is to leverage package checks.

13.4 Package Checks


We recommend activating client and server checks on your package. Note, however,
that these checks are automatically active in SAP NetWeaver 7.31 or higher. As a result,

308 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


13.4 Package Checks

only objects that are part of a package interface can be used by objects in other pack-
ages.
What’s unfortunate is that package check errors can be ignored, as no compile-time or
runtime validation exists to prevent an object from being used by an unauthorized
object. For this reason, keeping an eye on the package checks is critical to avoid accu-
mulating technical debt in the form of undesired coupling or cyclic dependencies.

Coupling and Cyclic Dependency


Undesired coupling is when one object is being used by another object that is not
intended to use the first object in the first place. For example, if you have a database
table that you expect to be accessed by a specific API, then no one else should read
directly from that table. Otherwise, this undesired coupling makes any change impos-
sible to make without adapting all unexpected consumers.
Cyclic dependency, or circular dependency, is a special type of coupling that is even
harder to deal with. This kind of dependency can happen with two or more modules.
For example, let’s say object A depends on object B, and object B depends on object A.
Any kind of rework would require adapting both objects, which defeats the principle of
modularity.

While we recommend you selectively add use access and don't blindly inherit from the
use access of the parent package, we generally consider the package interfaces listed in
Table 13.5 part of the standard use access for most applications.

Package Interface Package

BS_ANALYTICS_INFRASTRUCTURE_P BS_REUSE

BS_BUSINESS_OBJECT_FRAMEWORK BS_REUSE

BS_FUNCTIONS_FOR_BOPF_ENV BS_REUSE

_ABA_DEFAULT ABA

_ABA_VIRTUAL_DEFAULT ABA

_AKB_INITIAL BASIS

_BASIS_DEFAULT BASIS

_BASIS_VIRTUAL_DEFAULT BASIS

Table 13.5 Package Interfaces

In the following sections, we’ll further explore package checks: what they are, how to
execute them, how to fix errors, and best practices.

Personal Copy for Andrei Arabolea, [email protected] 309


13 Packages

13.4.1 What Are Package Checks?


Package checks validate that each object is only accessed or used by the allowed objects.
While every object in package A can access all objects of package A, objects of package A
can only access objects of package B if the following conditions are true:
1. Those objects are declared in a package interface.
2. That package interface is meant to be used by package A.
3. Package A has a use access to the package interface of package B.

Establishing a proper package strategy and diligently paying attention to the package
checks is key to ensuring proper cohesion and reducing accidental coupling in any
development project.

Use Access Error


If you ever encounter an issue where you cannot add a use access to a package and
receive a message saying “Package interface ABC points to a package that is too deeply
nested”, this is because a package can only access what’s made available at its level.
Therefore, anything lower (such as within a parallel hierarchy) would be “too deeply
nested”. To deal with this issue, what you want to access needs to be exposed at the
same level of the package where you need it, which is its sibling package.

While the package concept in ABAP is a bit more complex than necessary (when com-
pared to other languages), this powerful tool can be leveraged to help in the following
ways:
쐍 Avoid layer bridging or bypassing (e.g., OData accessing database tables directly,
business objects using SAP Gateway objects)
쐍 Prevent the usage of illegal objects, which in case of breaking changes, can also break
your own code
쐍 Make refactoring easier and cheaper
쐍 Lower coupling
쐍 Ease of unit testing

When a project or application has hundreds or even thousands of package checks, the
Pareto principle easily applies: You generally need 20% of the overall effort to solve
80% of the errors. Thereafter, more complex issues will become easier to tackle.

13.4.2 Manual Execution of Package Checks


You can manually execute package checks in many ways, both in SAP GUI and ABAP
Development Tools (ADT); however, the two main places you can trigger the checks are
either on a package itself, which will give you all the issues for all objects in the package,
or directly on a single object, which will give you all access issues that that object has.

310 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


13.4 Package Checks

In the Project Explorer in ADT, as shown in Figure 13.12, right-click either on a package
or an object and select Run As 폷 ABAP Package Check to trigger a package check. The
results will be shown under the Problem tab, as shown in Figure 13.13.

Figure 13.12 Triggering Package Checks in ADT

Figure 13.13 Problem Tab Displaying the Results of the Package Checks

To trigger a package check in SAP GUI, right-click either a package or an object within
the Repository Browser and select Check 폷 Package Check (Active Version), as shown in
Figure 13.14.

Figure 13.14 Triggering Package Checks in SAP GUI

Personal Copy for Andrei Arabolea, [email protected] 311


13 Packages

13.4.3 Automated Execution of Package Checks


Often, people assume with their package checks everything is going well; however,
note that the execution of automated package checks can be configured with a differ-
ent scope, such as the execution of package checks only on ABAP Data Dictionary
objects (quicker execution, but incomplete picture) or the execution of package checks
on all ABAP objects (slow execution, but of complete view of your issues).
You can define the automated execution based on your needs, but one way to mitigate
both runtime performance and completeness of the results is to have two automated
runs:
쐍 A daily run on all ABAP Data Dictionary objects
쐍 A weekly run on all objects

We won’t cover the configuration of automated package check executions in this book,
which is usually configured by system admins.
To access the results of the automated executions, follow these steps, as shown in Fig-
ure 13.15:
1. Run Transaction SE80.
2. Select the ATC Result Browser (Checkman).
3. Choose the result entry that corresponds to the execution run you want.
4. Visualize the results in the pane on the right.

Figure 13.15 Accessing Package Check Results in the ABAP Dictionary

If you selected the results for all objects (not limited to ABAP Data Dictionary objects),
you’ll see the result structure, which you can browse, as shown in Figure 13.16.

312 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


13.4 Package Checks

Figure 13.16 Package Check: Violations for Development Objects

13.4.4 How to Fix Package Check Errors


Once you’ve visualized the results, whether from automated or manual execution, and
once you’ve understood the issues, you can start fixing the errors. Package errors can
be fixed in many ways, but you’ll need time to understand the issue before making any
change.
For example, a common mistake is that people are tempted to take the easy way out by
clicking the Apply from Superpackage button to get rid of the package errors. Avoid this
approach and instead manually add the use access you need; otherwise, you risk giving
access to too many objects unintentionally or unnecessarily. This rule is particularly
critical if the superpackage is a generic one that has access to everything.
To fix package errors, you can perform a single action or a combination of the following
actions, depending on the situation:
쐍 Create a use access to the objects that are used.
쐍 Expose the required object in a package interface.
쐍 Request an exception.
쐍 Change the design.

We’ll cover each of these options in the following sections. To fully grasp these options,
however, you’ll need to understand a few key terms, as shown in Figure 13.17.

Use Access

Client Server
PIF I

Object C Uses Object S

Figure 13.17 Terminology

Personal Copy for Andrei Arabolea, [email protected] 313


13 Packages

The client and server are both packages, where object C is the client of object S, being
the served object. For C to use S, package client needs to declare a use access to package
server with package interface I.

Creating a Use Access to the Objects That Are Used


This option is the simplest approach: Given an object C where you need to use object S,
and that S is already exposed in package interface I, you would follow these steps:
1. Open object S.
2. Perform a where-used on object S selecting package interfaces.
3. Pick the best fitting package interface I.
4. Open the package where object C is located.
5. Create a new use access for the chosen package interface I.

Note that you may need to give that use access to additional packages in the hierarchy.
For instance, the parent package must have access to it for the child packages to gain
access to those objects.

Exposing the Required Object in a Package Interface


Not all objects are meant to be exposed, so make sure you expose the objects you
intend to expose and make sure you choose the right package interface (e.g., public,
internal, or restricted). When you own both the object S you want to use and the object
C, you can more easily manage where you want to use it by following these steps:
1. Open object S.
2. Open the package containing object S.
3. Open the right package interface I or create a new one in case none suits your needs.
4. Once the right package interface I is open, add the object you wish to expose in the
list of visible elements.
5. If the package interface I is not yet in the list of use access of your client package
where object C is located, perform the steps described in the previous section to add
that use access.

However, if you’re not the owner of the object S, you can request access to it by asking
the owner to publish it in a package interface. Once done, you could add to the use
access of the package containing object C.
In case you simply cannot or are not allowed to expose an object in a package interface,
you could either request an ABAP Test Cockpit/Checkman exemption (also known as a
usage exemption) or adjust your design, as we’ll describe next.

314 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


13.4 Package Checks

Requesting an Exception
An exception may be required because of legacy objects or because a legal object was
omitted by mistake in a package interface (e.g., object in a reuse generic layer). Make
sure you request the exception with the smallest validity period. For example, if the
generic reuse layer owner promises to expose it in the follow-up release, you could
request the exception for a certain release number or a fixed date.

Design Changes
Sometimes, discussing package check errors with your colleagues will result in better
designs while avoiding any action on the package side (e.g., no new use access, no new
object to be exposed), such as simply moving your own client object because it might
not be in the right package after all.
Another design alternative could be to shadow the object you need with your own
object, whether an exact copy or a variant that suits your needs.

13.4.5 Best Practices


The following are some best practices for packages:
쐍 Create a public package interface to expose your API and the related ABAP Data Dic-
tionary objects used in the API interface.
쐍 If you want to expose interfaces or factory classes, put them in the least exposed
interface (e.g., internal if sufficient).
쐍 If you must expose database tables outside of a package, favor a restricted package
interface.
쐍 Design packages to have the highest cohesion and the lowest coupling. While ini-
tially more effort, it always pays off in the end.

The following practices should be avoided when you create and work with packages:
쐍 Do not expose database tables in public package interfaces.
쐍 Do not use internal package interfaces of other application areas. You must be dili-
gent about this rule since ABAP package checks do not differentiate whether you use
a public interface or an internal interface and cannot know whether an interface is
an internal interface that should be avoided.
쐍 Avoid creating double or circular dependencies between TRANSL and NO_TRANSL sib-
ling packages. In general, a no-translation package can depend on a translated pack-
age, but not the other way around.

While exceptions are possible, not following these strict rules makes package and pack-
age interface maintenance more complex and more costly and makes any refactoring
or object relocation more difficult as well.

Personal Copy for Andrei Arabolea, [email protected] 315


13 Packages

13.5 Consequences of Poor or No Package Strategy


As mentioned at the start of this chapter, packages are often poorly designed, which is
the result of a poor or absent package strategy definition at the beginning of a project,
and makes clean code even more difficult to achieve. The more people you have work-
ing on a project, the earlier you will get into trouble.
A common negative consequence is accidental architecture. But what is accidental
architecture anyway? This architecture emerges from a variety of design decisions,
which are often made independently, overlooking the bigger picture, as opposed to an
intentional architecture, in which the result is something you actually intended.
In other words, an accidental architecture is a vast collection of different technical
debts, sometimes with minor negative impacts, but often with critical impacts that put
a project at risk or, even worse, kill a project when its technical debt becomes unman-
ageable and more effort is put trying to keep it working rather than developing new
features.
Some examples of technical debt that can occur when you lack a proper package strat-
egy include the following:
쐍 Cyclic dependencies between objects
쐍 Relaxed layered architecture or layer bridging
쐍 External consumers depending on objects that where not meant to be reused
쐍 The refactoring of internal objects is difficult or even impossible
쐍 Poor isolation and, thus, difficulty testing parts of the software
쐍 Direct read access to internal APIs or even database tables

While establishing a proper package strategy early on cannot guarantee that your soft-
ware will be free of all these issues, it can greatly reduce their incidence.

13.6 Summary
In light of what we’ve presented in this chapter, we hope you can see that no single rec-
ipe for success exist since many factors, like the size of the project, the number of layers
and technologies, development techniques, the outer infrastructure, and the overall
architecture will need to be considered. We discourage packages with too many objects,
but we cannot recommend a particular size. However, what matters the most is what’s
exposed outside of each package.
You’ll need to think about your overall goal, keep the bigger picture in mind, and struc-
ture your code in a way that serves both the functional and architectural objectives of
your project, while defining the right places for people to put their development
objects. By enabling stricter rules from the beginning and automated package check

316 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


13.6 Summary

executions, you can proactively catch deviations and use those concrete examples to
reinforce the initial vision with every developer. Designing the right packages for the
right purposes makes software development an easier and more enjoyable journey.
Let's review the key recommendations we covered in this chapter:
쐍 Slice and dice your packages according to specific use cases.
쐍 Make the reuse levels explicit.
쐍 Keep cohesion in mind when assigning a new object to a package.
쐍 Create new packages as encapsulated.
쐍 Define well-targeted package interfaces with the proper visibility instead of offering
a single interface containing all objects.
쐍 Carefully consider the implications of your package design (for example, led by
application or led by layer).
쐍 Pick the necessary translation relevance while considering the extra costs.
쐍 Be proactive by addressing package checks early on.

While establishing a clear package strategy is necessary to obtain a sound intentional


architecture, a good strategy alone is usually not sufficient since, for most recipes, it is
the combination of all ingredients in the right proportions that makes the difference
between a success and a failure. The next ingredient for success is to establish clean
ABAP practices in development teams, which we’ll cover in the next chapter.

Personal Copy for Andrei Arabolea, [email protected] 317


© 2025 by Rheinwerk Publishing Inc., Boston (MA)
Chapter 14
How to Implement Clean ABAP
Knowing the clean ABAP recommendations is only part of the journey.
This chapters deals with establishing clean ABAP principles as an individ-
ual, as a team, and as an organization.

Clean code is all about easy-to-understand code that can be adjusted easily. The produc-
tivity of a team and the quality and adaptivity of a product is strongly correlated to the
readability, maintainability, and testability of the code. Unreadable legacy code can
slow every team down to a snail’s pace and result in many bugs.
We’re constantly reading old code as part of our efforts to change code. Generally,
you’ll spend more time reading code than writing new code. But reading code is not the
same as maintainability in general. How much effort is required to correct a bug in
existing code or adjust legacy code to changing requirements? Learning every aspect of
clean code is a long journey. In this chapter, we’ll provide an overview of several tech-
niques for learning clean code that can be used by individuals, teams, or organizations.
In this final chapter, we’ll explore ways to create a common understanding among
team members in Section 14.1, with a closer examination on the topic of collective code
ownership in Section 14.2 and the Clean Code Developer Initiative in Section 14.3. Then,
in Section 14.4, we’ll discuss the broken window effect and ways to combat it. Starting
in Section 14.5, which focuses on learning from code reviews, we’ll explore the many
ways you can advance your skills in clean ABAP, such as the Clean Code Advisor, the
topic of Section 14.6. We’ll look closely at numerous learning techniques in Section 14.7,
before closing out this chapter with a look at continuous learning for cross-functional
teams in Section 14.8.

14.1 Common Understanding among Team Members


These techniques should help you establish a common understanding of clean code
principles in your team and across teams. Not all rules in this book will fit all your proj-
ects. You’ll need to have some discussions as a team. To encourage a fact-based discus-
sion, we recommend that every developer should read the relevant chapters
beforehand. Although we recommend staying with the Clean ABAP GitHub repository,
sometimes, deviating from that guide is acceptable if your team does not agree with all
its rules. You could create a fork of the repository and adjust a few rules where your

Personal Copy for Andrei Arabolea, [email protected] 319


14 How to Implement Clean ABAP

team has a different opinion. At the end, you should write down all the rules, so that
everyone can learn and apply those rules. This codification is also useful when new
hires join the team.

14.2 Collective Code Ownership


In contrast to individual code ownership, with collective code ownership, the entire
team owns the code, feels responsible for the code, and can make changes to the code.
Collective code ownership has several advantages and disadvantages, and in this sec-
tion, we’ll focus on how collective code ownership affects learning. When the team is
collectively responsible for the code, everybody learns, and no single knowledge bottle-
neck will exist. Thus, if one team member is sick or leaves the company, the team can
quickly overcome the loss. Collective code ownership also helps to build up the com-
mon goal for maintainable, readable, and testable code. More people will have incen-
tives and feel responsible for giving good feedback in code reviews, which will also
become more efficient over time, since other team members will be more familiar with
the code. With individual code ownership, people stay in their knowledge silos for a
long time, which results in team members stagnating in terms of skill development if
they do the same work over and over. In contrast, collective code ownership leads to
constant interactions between the team members, so developers can learn continu-
ously from others.
You’ll need to gauge how well collective code ownership is working in your team. If
everyone is responsible, sometimes, no one feels responsible. In disciplined and highly
motivated teams, collective code ownership works well. For other teams, you’ll need to
find the right balance, but never accept purely individual code ownership.
Individual ownership comes with too many risks, such as knowledge bottlenecks, bar-
riers to refactoring, delayed changes, and the inability to cope with high demand for
change in a specific area. Still, collective code ownership is not something that can be
achieved easily. Before you strive to achieve collective ownership, we recommend
implementing some intermediate steps, such as the following:
쐍 Start with a small collective
In this scenario, the complete team does not own the code. Instead, for each piece of
code, at least two developers are owners so that no single bottleneck can hold up
that piece of code. From individual code ownership, you can move towards collec-
tive ownership by doing pair programming or code reviews. At the beginning, these
techniques may slow an individual developer down, but the payoff for the team will
be significant.
쐍 Weaken code ownership
Unlike individual code ownership, in this scenario, others are also allowed to change
the code but should ask the owner before. Additionally, the owner should review all
changes and eventually take over the responsibility for maintaining the changes.

320 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


14.2 Collective Code Ownership

쐍 Establish a common understanding of clean code


An agreement on a common understanding about clean code can greatly help col-
lective code ownership become more productive. You should have clear rules
defined for formatting and static code checks, and you should choose a style guide.
쐍 Increase the engineering discipline
Collective code ownership requires high discipline from the team, especially with
regard to unit testing, clean code principles, and refactoring. Therefore, we recom-
mend growing these skills before you introduce collective code ownership.

Collective code ownership is restricted to the boundaries of a team. When a developer


outside of the team’s boundaries wants to change the code owned by the team, that
outsider should always ask the owning team to review the change.
An additional term related to collective code ownership is the code steward. As a code
steward, you’ll ensure that all knowledge related to the code, which can be domain
related or engineering related, is distributed among team members and that the neces-
sary skills are grown. Assigning a code steward is an intermediate step from a team of
mainly individual code ownership to a team operating with collective ownership. The
goal of the code steward should be empowering other team members to make changes
to the code. To get to this point, the code steward chooses the most appropriate leader-
ship style depending on the motivations and skills of his or her colleagues so that team
members can make changes to the code. Kenneth H. Blanchard’s and Paul H. Hersey’s
model, which is described in their book Management of Organizational Behavior,
defines four different quadrants for leadership styles:
쐍 Directing
When colleagues lack the ability to do the job, the code steward gives clear direction
on what to do.
쐍 Supporting
When colleagues lack motivation to do the job, the code steward should spend time
convincing and motivating.
쐍 Coaching
When colleagues have the motivation and can do the job, at least to some extent,
they could be demotivated by a leadership style that is too direct. So, the code stew-
ard leads by listening and asking questions.
쐍 Delegating
When colleagues have the motivation and can do the job, the code steward can sim-
ply delegate the job and review the result.

Applying the principles of code stewardship and different leadership styles, you can
move from individual code ownership, to code gatekeeper (one owner reviews all
changes), to several gatekeepers, and finally to collective code ownership.

Personal Copy for Andrei Arabolea, [email protected] 321


14 How to Implement Clean ABAP

14.3 Clean Code Developer Initiative


Inspired by the book Clean Code by Robert C. Martin, Stefan Lieser and Ralph Westphal
started the Clean Code Developer Initiative in 2009. The initiative’s goal is to encour-
age the adoption of clean code principles and valuing internal quality among software
developers. Stefan and Ralph divided the huge number of topics into seven grades so
that developers have a clear path to follow. The Clean Code Developer Initiative in-
cludes planned time to practice and divides the large book into digestible junks. Many
developers and teams are overloaded. The grades allow developers to set aside the nec-
essary time to change habits or attitudes, which results in a higher code quality. Devel-
opers and teams are guided through the following grades:
쐍 Black grade
The black grade is the initial grade when you start. A clean developer commits to the
learning journey.
쐍 Red grade
The red grade includes your first exercises involving essential elements of clean
code and the encouragement of positive attitude for a clean code developer.
쐍 Orange grade
The orange grade introduces essential engineering principles and focuses on the
automation of activities.
쐍 Yellow grade
The yellow grade is focused on automated tests. Compared to the orange grade, this
grade is not about system tests but, rather, on the lower-level tests where you need
to write the code in a testable way. Therefore, object-oriented principles are an
essential part of this grade.
쐍 Green grade
The green grade continues with automation, with a focus on moving code to produc-
tion. Additionally, this grade has a strong focus on modularization.
쐍 Blue grade
The blue grade considers automation in the context of deployment, several architec-
ture topics, and an iterative development process.
쐍 White grade
In the white grade, all principles, rules, and practices come together. A developer in
the white grade always considers all aspects of the previous grades. Only a developer
with major experience and discipline can achieve this grade. If some topics cannot
be applied yet, a clean code developer puts effort into filling the gap.

Each of these grades focuses on a certain aspect of developmentand therefore com-


bines well-known principles and guidelines with some suitable practices. Each grade
builds upon the preceding path.

322 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


14.4 Tackling the Broken Window Effect

Further Resources
More details about the different grades can be found at the Clean Code Developer Ini-
tiative website: https://clean-code-developer.com/.

The first steps can be completed by each developer on his or her own. They should not
need to ask someone for management approval. Since software development is a team
activity from a certain stage onwards, the whole team is needed. Therefore, you should
intend, as a team, to work through the grades and reflect on this learning journey. The
goal is to establish a common base of generally accepted principles and best practices
among team members.
Let’s look at applying this method to both individual and team learning:
쐍 Individual learning
This method can be applied by each individual. You’ll start with a certain grade and
evaluate yourself on whether you uphold clean code principles. Additionally, you
could ask an experienced developer to review your code. After 3 to 4 weeks, you can
move to the next grade. Do not start with the next grade if do not live up to the prin-
ciples of your current grade.
쐍 Team learning
Your team should commit to learning together and applying the lessons learned in
their code. Therefore, they’ll go individually through the materials and have joint
discussions. Additionally, they can review changes to see whether learned principles
are actually lived. After 3 to 4 weeks, the team could move to the next grade. Find a
reviewer who has more experience applying clean code principles. Your team
should only go to the next grade if the principles are lived. This approach can also
help to establish a common understanding of maintainable and readable code.

Further Resources
We strongly recommend you read the book Clean Code by Robert C. Martin and discuss
iterative team learning with your team, for instance, through the Clean Code Devel-
oper Initiative. The result of the discussion should be summarized, so that you can refer
to this commitment in future code reviews or during new hire onboarding.

14.4 Tackling the Broken Window Effect


According to the broken window theory, visible signs of disorder create an environment
for more disorder. For example, consider a building with a few broken windows. If the
windows are not repaired, vandals will likely come around to break more windows.
Eventually, they may even break into the building and, if unoccupied, perhaps become
squatters and start fires inside.

Personal Copy for Andrei Arabolea, [email protected] 323


14 How to Implement Clean ABAP

How does this relate to software engineering?


Software development is all about details, and if you don’t act on any deviations in the
design, even minor in nature, you’ll likely end up with a project that inherits complex-
ity. A crucial factor that makes software rot is the way the growth of the code is man-
aged in terms of complexity and quality.
We often tend to make the quality suffer due to the crunch time to make our project
deliver on time causing complexity creep due to forced efforts to meet timelines.
“Quick and dirty” duct tape fixes to deliver code results in poor quality, and the system
will become impossible to maintain. When complexity is high, developers may con-
sider a complete code rewrite or major refactoring, which often leads to frustration
because this kind of overhaul costs so much time and energy.

Technical Debt
Technical debt is a metaphor to describe the debt you build up when you create a soft-
ware system. When you don’t mind your technical dept, you’ll spend more time dealing
with the consequences of that debt. Ward Cunningham, the originator of the meta-
phor, advises:
Although immature code may work fine and be completely acceptable to the cus-
tomer, excess quantities will make a program unmasterable, leading to extreme
specialization of programmers and finally an inflexible product. Shipping first-time
code is like going into debt. A little debt speeds development so long as it is paid
back promptly with a rewrite... The danger occurs when the debt is not repaid. Every
minute spent on not-quite-right code counts as interest on that debt. Entire engi-
neering organizations can be brought to a standstill under the debt load of an
unconsolidated implementation.

Examples of broken windows in software engineering include the following:


쐍 If you have a high number of static code check issues in your codebase, you may not
recognize if a change adds new issues.
쐍 If you have a low code coverage, adding new tests is almost invisible in the overall
code coverage. Therefore, your colleagues may not add a unit test, which further
deteriorates the situation.
쐍 With flaky tests, you may think that each failing test is a false positive.

How can you avoid this situation? You should have zero tolerance regarding static code
check issues. For example, no one should be allowed to release a transport if ABAP Test
Cockpit issues exist. From our past experience, we know that “later” quite often means
“never.” A policy can act as a trigger for a change in habits and can help sustain the pro-
ductivity of your team. For example, without such a policy, static code check issues can
pile up, and sometimes, important issues are lost since already too many issues exist.
You should also keep in mind the following considerations:

324 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


14.4 Tackling the Broken Window Effect

쐍 Flaky tests are tests that sometimes run red and sometimes run green. These fragile
tests should be made stable immediately or put into quarantine (removing the test
from the test suite and creating a ticket), so that the developer can fix the flaky test.
쐍 Code reviews and pair programming can then focus more on the other stuff that
can’t be tested by tools, for example, the maintainability of your code, the quality of
your tests, and your adherence to clean code principles.

What do you do if the windows are already broken?


쐍 For static code check issues
Set a threshold based on the current number of found static code check issues. This
threshold ensures that no new issue is introduced. After refactoring and cleanups,
adjust the threshold to a lower number.
쐍 Code coverage
Tools are available for checking the code coverage of new code. Other tools can check
whether there is a positive trend in your code coverage.

In the following sections, we’ll take a deeper look into applying static code checks, met-
rics, and code coverage.

14.4.1 Static Code Check


If a shared understanding in your team exists with the goal of maintainable and read-
able code, you should use a common definition for static code check tools. The stan-
dard tool for static code checks in ABAP is the ABAP Test Cockpit. You can configure the
ABAP Test Cockpit so that many clean ABAP rules are checked automatically. We rec-
ommend that you configure the system so that all ABAP Test Cockpit issues will be
fixed before you can release a transport request or even a task, thus avoiding the bro-
ken window effect.

14.4.2 Metrics
Metrics alone cannot help much and only cover a small part of the principles. Still, met-
rics can provide value if used carefully, especially in parts of your codebase where
deeper review might be required. For example, the cyclomatic complexity metric calcu-
lates the complexity of a method. A higher value means a higher risk that a change will
introduce a bug. Therefore, you should investigate whether high cyclomatic complex-
ity is justified and covered by a well-written suite of unit tests. Additionally, we recom-
mend you consider defining upper thresholds for these metrics, which can be
maintained in the ABAP Test Cockpit.
When metrics are purely seen as key performance indicators (KPIs), a danger arises. For
externally defined KPIs, the motivation for the KPI might be unclear. This ambiguity
may result in unwanted optimizations by developers, for example, high unit code

Personal Copy for Andrei Arabolea, [email protected] 325


14 How to Implement Clean ABAP

coverage but tests that don’t really test anything. Therefore, every developer must have
a good understanding of the purpose of the metric. A metric should not be seen as a
KPI, but rather as a learning metric to continuously improve as a team.

Cyclomatic Complexity
Cyclomatic complexity is a software metric that indicates the complexity of a program.
This quantitative measure involves the number of linear, independent paths through a
program’s source code. A high number indicates hard-to-maintain code and is a hint
that you should look more deeply into the code to make it more maintainable.

14.4.3 Code Coverage


When statement or branch coverage is insufficient, then the code is not ready for
review. To write a good test suite, you’ll need to write the code in a testable way. There-
fore, defining coverage goals in the ABAP Test Cockpit makes sense, so that transports
without sufficient code coverage for the classes cannot be released.

Code Coverage
Statement coverage and branch coverage are measures that describe the degree to
which the source code of a program is executed when a particular test suite runs.
Statement coverage measures how many lines of the overall codebase have been
reached. Branch coverage measures how many branches of control structures (e.g., IF
or CASE statements) have been reached.

14.5 Code Review and Learning


The main goal of code reviews is to improve the quality of the code, but code reviews
can also be helpful for learning and teaching how to write readable and maintainable
code. Sharing knowledge is essential for improving code quality. Therefore, you should
use code reviews for teaching and learning by treating them as opportunities to give
and take comments on the code with the intention of learning.
In the following sections, we’ll take a deeper look into different aspects of code reviews.

14.5.1 Code Review Prefix


You can think about introducing a prefix when the comment is purely educational. At
Google, they introduced the prefix Nit: to state that the comment is purely educa-
tional and not critical to meet the quality standards or style guide, hence it is not a bug.
The prefix indicates that it’s not mandatory for the author to resolve the issue in this
change.

326 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


14.5 Code Review and Learning

14.5.2 Style Guide


To codify clear guidance for all team members, we strongly recommend that, as a team,
you agree on a style guide. We recommend using the existing open-source style guide
Clean ABAP (http://s-prs.co/v519011). The style guide should serve as the absolute
authority to avoid meaningless discussions during code reviews.
Any style-related topic that is not in the style guide should be considered a matter of
personal preference. New additions to existing code should comply with the estab-
lished style guide and with the style from the existing codebase.

Origin of the Clean ABAP Guide


The open-source Clean ABAP guide also originated from the discussions on what is a
common understanding of clean code from a few teams in the SAP governance, risk,
and compliance department, as explained in Chapter 1. Florian Hoffmann and Klaus
Haeuptle moved that content later to an inner source project on GitHub and presented
this work in an internal grassroot network. Afterwards, many engineers from across
SAP opened up issues or created pull requests. Once the content was in good shape,
Florian and Klaus decided to make it available for customers and partners as an open-
source project. Again, many engineers from within as well as outside of SAP contrib-
uted to improving the guide.

14.5.3 Make It Visible


Another recommended technique everyone should follow is making clean code prac-
tices visible so that everyone remembers the rules during programming or design dis-
cussions. For example, you can print out a chart and hang it in the team room. To
encourage a higher commitment, you can also ask team members to sign the chart. To
remind yourself about more details, you could print a cheat sheet for your desk. The
Clean Code Developer Initiative proposes even more artifacts to make the commit-
ment to clean code visible—wristbands, stamps, flags, desktop backgrounds, postcards,
and mousepads!

Further Resources
A printable cheat sheet for clean ABAP can be found at the Clean ABAP GitHub page in
the cheat-sheet folder: http://s-prs.co/v519012.

14.5.4 Feedback Culture


In the past, we’ve seen some teams where code review did not work. Team culture,
especially how developers give and take feedback, is often the reason for this failure.
This section describes the challenges of encouraging feedback and how the team can

Personal Copy for Andrei Arabolea, [email protected] 327


14 How to Implement Clean ABAP

improve in giving and receiving feedback. Only with a good feedback culture can code
reviews be an efficient learning tool.
Sometimes, developers feel criticized as a person, even though the review’s intention is
only to lead to improved code. One reason for this defensiveness might be that the
developer of a piece of code relates the feedback to his personal capabilities. Another
reason might be that the feedback directly criticizes the developer. To enable efficient
code review and good team culture, you must take care of these personal aspects.
Enabling a strong feedback culture is not an easy task but is worth the effort. To give
and take feedback requires practice, learning, trust, and reflection—both as a team and
as an individual. The communication style of the reviewer has a strong impact on the
productivity of a review. As a reviewer, you should consider the following communica-
tion style points:
쐍 You should clearly communicate what can be improved and explain the reasoning
behind your comment. You don’t have to provide solutions; you can simply point
out the problem. Letting the developer find the solution helps the developer learn
more. Thus, you should balance how much guidance you give in your comments.
쐍 Your communication style should be friendly, respectful, and supportive. Especially
important is to make comments about the code and not about the developer. For
example, we recommend an indirect style like “The comment could be expressed in
the code” instead of “You added a comment, which could be expressed in the code.”
쐍 You should write comments in a constructive way, so that the author does not take
them personally. The author should not feel personally attacked by a review; other-
wise, they won’t look forward to another review.
쐍 The goal should be to improve the readability and maintainability of the code. Con-
sequently, encourage the author to improve the code or to add a comment. Do not
let them explain to you how the code works in the code review tool. Adding a com-
ment should also be an exception.
쐍 As a reviewer, when you see code that is hard to understand, just ask for clarification
and give some hints on how the readability could be improved. This kind of com-
ment might be the most helpful comment of all. Quite often the code is too compli-
cated, so asking for clarification before you spend too much time on it is important.
Reading well-written and readable code should be as easy as reading a book. Better
readability allows everyone to see problems more easily.

Developers are usually remarkably busy and hardly have time for doing code reviews.
As an author, you need to consider the time of reviewers as a valuable resource. You can
do a lot to improve the effectiveness of reviews and building up a good feedback cul-
ture, by keeping in mind the following rules:
쐍 To become familiar with a piece of code can cost the reviewer a significant amount
of time. As someone who wants a review from a colleague, you should come into the

328 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


14.5 Code Review and Learning

process with the attitude of using the time of others wisely. You should value the
time of a reviewer and see their feedback as a gift to you that can help you grow as an
engineer and improve the quality of the codebase.
쐍 Therefore, you should follow the agreed-upon style guide, have sound test coverage,
and fix all static code check issues before you submit a change for code review. Oth-
erwise, you’ll waste the time of developers after you.
쐍 Don’t take the feedback personally. The reviewer gives feedback about your code,
not about your abilities.
쐍 Sometimes, reviewers are frustrated for reasons not related to your code. This frus-
tration can be seen in his or her communication style. Allowing frustration to dom-
inate a review isn’t a good practice for reviewers, and you should give friendly
feedback to the reviewer. However, before you give this feedback, you should reflect
on whether the comment contains something constructive as well.
쐍 To minimize the effort required to familiarize oneself with a piece of code, the
change to be reviewed should be small, which makes the review more focused and
more productive. Otherwise, the reviewer can be overburdened. A big change costs
significantly more time, causes more confusion, and stays open much longer.
쐍 If the reviewer does not understand your code, most likely, other readers will also
not understand your code. Therefore, do not directly explain the code in the code
review tool. You should first try to make the code more understandable or, when
absolutely necessary, add a comment in the code to explain the issue.

Managers should learn about the benefits of code reviews, allocate enough time, pro-
mote code reviews to stakeholders, and communicate their commitment to the team.
A team’s manager should also help the team build up such a strong feedback culture
and reward active reviewers, those that submit to reviews, and team members that
make useful contributions. Additionally, managers should help coach team members
in providing more thoughtful feedback. Managers should also invest much of their
time creating a psychologically safe environment and improving the level of trust
among the team members.
In general, team leaders and the team itself must together strive to create a learning
culture in which feedback is an essential medium for growing both as an individual and
as a team. Team members should seek to learn from each other and do a better job the
next time.
Another challenge is when team members are from different cultures. Different cul-
tures have different attitudes towards feedback. Some cultures are more direct, and
others indirect and avoid confrontation. If you’re in such multicultural environment,
learn about how to get those cultures to collaborate efficiently and what you can do to
make code reviews work.

Personal Copy for Andrei Arabolea, [email protected] 329


14 How to Implement Clean ABAP

14.6 Clean Code Advisor


The goal is to form a circle of clean code experts that can be asked to take part in a
review. The advisor does not need to be an expert in the domain but can give the
author hints on how to improve the readability and maintainability of the code. Some
companies enforce clean code advisors in an even stricter way. For example, at Google,
no change can be released without a review by a colleague who has completed a curric-
ulum on clean code. At SAP, we don’t have a formal program yet, but many teams who
have set up a similar process so that an author of a change has access to clean code
experts to review changes.

14.7 Learning Techniques


To grow your skills, just reading books and guides is usually not sufficient. Another
great way to hone your programming skills is practice. There are several learning tech-
niques based on intense but short practice, such as the following:
쐍 Kata
A kata is a small exercise that helps a programmer improve their skills through prac-
tice and repetition. The developer does the exercises on their own.
쐍 Dojo
The main difference between a dojo and a kata is that, in a dojo, a group of develop-
ers practices together. The exercises used in a code kata are usually also used for a
dojo.
쐍 Code retreat
In a code retreat, a group of people come together, usually for one day, sometimes
longer. The intention is to practice their skills and reflect on their learning together.
쐍 Pair programming
Two programmers work together on a programming task.
쐍 Mob programming
Several programmers work together on a programming task.
쐍 Habits
Implementing triggers in your IDE, such as the build or continuous delivery pipe-
line, to change behavior.

In the following sections, we’ll look more deeply into several learning techniques.

14.7.1 Kata
In karate, a kata is a well-defined sequence of movements and a typical exercise. The
intention is to store a sequence of movements in muscle memory to allow for lightning-
fast execution in a fight. Training without an opponent helps to focus on the correct exe-
cution of the movements.

330 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


14.7 Learning Techniques

Based on a similar idea, a code kata is a well-defined programming exercise, in which


you exercise certain programming practices, for example, developing without a mouse
or adopting test-driven development (TDD).
A code kata is a short programming exercise where you’ll usually complete several
cycles with different learning goals. For example, during the first exercise based on a
specific code kata, the main task is to understand a problem and find a solution. The
next cycle of the kata is focused on creating a qualitative solution with high test cover-
age.
Afterwards, you can focus on the way you came to the solution. For example, the fol-
lowing cycle could focus on applying pure TDD or developing the solution only with
keyboard and shortcuts without using a mouse.
Another common variant of a code kata is refactoring an existing piece of code. In the
gilded rose kata, you’ll first create a sufficient code coverage and afterwards improve
the readability of the code.

Further Resources
More details about organizing the gilded rose kata can be found at http://s-prs.co/
v519013.

14.7.2 Dojo
In a code dojo, multiple team members come together to practice as a group. The exer-
cise can be a Kata, or you might focus on different forms of group activities like pair
programming or mob programming techniques. At the end of a coding dojo, team
members should reflect and talk with others on what was tried and what was learned.
A code dojo is started with a short discussion about the task the group will work on.
Afterwards, a pair starts to work on the programming task. After a defined interval (for
example, 5 minutes), a change takes place. The navigator becomes the driver (the one
who has the keyboard). The preceding driver becomes part of the audience, and some-
one from the audience becomes the driver. After everyone has been in the driver role
once, the group should conduct a retrospective on what was learned in the process.

14.7.3 Code Retreat


A code retreat is an intense, usually day-long, practice event focusing on the funda-
mentals of software development and design. The idea is to create a retreat where
everyone can focus on learning more about development and design and remove
themselves from the daily pressure to get things done. A code retreat can allow a group
of developers to focus on clean code, TDD, and other topics.

Personal Copy for Andrei Arabolea, [email protected] 331


14 How to Implement Clean ABAP

During a usual day at work, not enough time is available to improve coding skills. Too
many meetings are scheduled, and the next release is looming. Under time pressure,
we always fall back to our old, bad habits, even though we know better techniques and
methods are available. A code retreat should create a space to improve your coding
skills without the usual everyday pressures.
In a pair, you could try to implement a task with TDD. The target is not to solve the task
but to try different approaches and programming techniques. After 45 minutes, the
developed code will be deleted. A few minutes later, you’ll discuss as a group what
you’ve learned. Then, you’ll start from scratch again. Since you cannot finish the task in
45 minutes, it is of no value to use shortcuts, and you can focus on learning how to
write clean code instead. The pairs will also be switched to foster the exchange of
knowledge among other participants.

Further Resources
More details about how to organize a code retreat can be found at https://www.coder-
etreat.org/.

14.7.4 Fellowship
Another opportunity from SAP is a fellowship in another team for half a year. During
those months, you’re completely assigned to another organization and are part of
another team. The intention is to extend your horizons and teach you new skills. After
the fellowship ends, you’ll return to your original team. One of the best ways to
improve your engineering skills is to work in an experienced team, with colleagues
happy to act as mentors for colleagues who want to learn about clean code or other
engineering topics.

14.7.5 Pair Programming


In pair programming, two developers work together on a programming task, divided
into two roles:
쐍 The driver uses the mouse and the keyboard to create code.
쐍 The navigator has a more conceptual perspective on the current task and evaluates
whether conceptual errors exist.

Upcoming problems are directly resolved in discussions between the pairing partners.
The roles should be switched often. Many benefits can be gained with pair program-
ming, like increased code quality and maintainability, fewer errors, and better end
results. In this chapter, we’re focused on learning and knowledge transfer, which occur
between pairing partners. You can learn from your partner, for example, about strate-
gies for coming up with solutions, tricks in using the IDE, or new language constructs.

332 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


14.7 Learning Techniques

Additionally, pairing can result in a better understanding of the problem domain. One
major challenge is quite often legacy code, and reasoning in a pair can speed up the
learning process about a piece of legacy code. With regard to clean code, pair program-
ming helps to build up a shared understanding of clean code and encourages an envi-
ronment to continuously learn/teach clean code. Pair programming for new team
members allows them to learn and contribute much more quickly.
By pairing different roles, you might also see other benefits. For example, if a developer
and a tester build together as a pair, both can learn a lot from their different perspec-
tives. The tester usually has completely different test cases in mind than the developer.
This diversity can help to improve the testing knowledge of the developer. The tester,
in turn, can benefit from the pairing by getting more productive with tools for testing
the product or by understanding the details of the implementations in a better way,
and they can come up with other kinds of tests. Sometimes, the tester has no program-
ming skills, so from our perspective, the tester should at least learn some basic pro-
gramming skills. Engaging in pair programming with a developer can be a great way for
a tester to learn programming skills—at least for writing system tests.

14.7.6 Mob Programming


In mob programming, several developers work together on a programming task.
Unlike a dojo, the task is a real programming task from the project and can last also
much longer, such as several days. One team member is the driver, and the rest are nav-
igators; the roles are switched in short intervals. Mob programming is usually used for
complex problems or to upskill new team members and is a great way to create a
shared understanding of clean code within a team. To work effectively, a high level of
trust should exist within the team. Nobody should be afraid of revealing uncertainties.
Everyone needs to feel safe to make mistakes in front of the group.

14.7.7 Habits
Being motivated is a fundamental foundation for change and learning but is quite
often not sufficient. To have a sustainable impact on changing habits, you’ll need to
establish permanent triggers. For example, if having a code coverage threshold of 80%
does not help ensuring high-quality tests, engineers should not fall back on old habits
of not writing tests along with the new feature. For the quality of the automated tests,
you can use a mutation testing score.

Mutation Testing
In mutation testing, changes are made to the code, and afterwards, the test suite is
executed to see if the test suite can detect these changes, also known as mutations. If
the test suite detects a high number of surviving mutations, which are mutations that

Personal Copy for Andrei Arabolea, [email protected] 333


14 How to Implement Clean ABAP

do not fail any automated tests, the test suite receives a high mutation score. Mutation
testing is one of the best metrics for reducing the number of bugs in the system.

Keep in mind, however, that metrics and checks do not cover a complete system. For
example, you cannot completely measure the quality or the maintainability of your
tests. So, you’ll need lots of pair programming, code reviews, and coaching to ensure
that these topics are learned and applied appropriately. In addition to coaching, you
can think about implementing some of these triggers for change as thresholds, for
example, when releasing a transport. Unfortunately, not all of the following compo-
nents are available as standard tools for ABAP yet:
쐍 Code coverage threshold (statement and branch coverage)
쐍 Mutation test score
쐍 No fragile tests
쐍 Maximum duration for test execution
쐍 Code readability and code complexity scores
쐍 No static code checks issues

In terms of trends, especially for legacy projects, starting with the desired threshold is
not often feasible. In this case, you can check whether the trend is positive, for example,
that the code coverage does not decrease with a new pull request or that no new static
code check issue has been introduced.
Before you implement these triggers, you’ll need to explain the reasons and benefits
for establishing those habits. You should have coaches and mentors in place who can
offer support if questions arise and who can confirm whether things are done in a
meaningful way.

14.8 Continuous Learning in Cross-Functional Teams


An agile approach involves cross-functional teams where individual team members
complement each other’s skills. With the move to cloud-based development, teams
must often deal with increasing complexity and must learn quickly. Both aspects bring
up new challenges when it comes to growing the necessary skills. This chapter intro-
duces some problems in empowering the teams to deliver value with quality while
keeping a maintainable and testable codebase.

14.8.1 Profile of a Team Member


In this section, we’ll explain the different types of team members in terms of skills pro-
files. These categories are rather theoretical but can help explain the challenges found
in teams:

334 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


14.8 Continuous Learning in Cross-Functional Teams

쐍 T-shaped team members have deep knowledge/skills in one or several areas as well
as a broad base of general supporting knowledge/skills. If this team member has
more than one deep area of expertise, you might call these members M- or E-shaped.
쐍 Generalists have broad skills/knowledge but lack deep expertise in any topic that is
important for the team.
쐍 I-shaped team members only have expertise in one area and lack the breadth of
skills necessary for working with the rest of the team.

The different types are shown in Figure 14.1. The depth of related skills is represented by
the vertical bar, whereas the horizontal bar represents a breadth of skills and the ability
to apply knowledge in areas of expertise other than one’s own and to collaborate across
disciplines with experts in other areas. To achieve that, team members need to possess
more than technical skills, they’ll also need business domain knowledge and soft skills
like empathy, communication skills, and the ability to collaborate in a team.

I-shaped Generalist T-shaped


Expert at one thing Capable in a lot of things Capable in a lot of things
but not expert in any and expert in one of them

Figure 14.1 Different Profiles of Team Members

14.8.2 Cross-Functional Teams


Cross-functional teams consist of several persons with T-shaped profiles, as shown in
Figure 14.2. The breadth allows the T-shaped team member to collaborate effectively
with team members or complete work outside of their field of expertise. I-shaped team
members tend to lack flexibility and have difficulties understanding how their work
impacts others.
The problem of an “it’s not my job” attitude inside the team, where developers don’t
take responsibility for anything other than their own work, can emerge. A team con-
sisting of team members with T-shaped skills encourages team ownership of the whole
product or solution rather than specific chunks.

Personal Copy for Andrei Arabolea, [email protected] 335


14 How to Implement Clean ABAP

More breadth

More
depth

Broader, deeper skills

Figure 14.2 Cross-Functional Team with Multiple T-Shaped Team Members

14.8.3 Multipliers: Need for Topic Experts in the Team


Not every team member can be a deep expert in every field or be connected to other
experts in the company. For example, regarding engineering practices, every team
member should have a good understanding of clean code, pair programming, continu-
ous integration, and test automation. Thus, a basic skill level is required for every team
member, and the goal should be to grow their skill maturity continuously. Not every
developer needs to be, nor can be, an expert in all these topics; they can work on grow-
ing the skills of the other team members, motivate others, inspire with new ideas, and
solve upcoming challenges in their field.

14.8.4 Need for Community of Practice


Working in a cross-functional team, where each team member has different strengths,
can sometimes mean that increasing your depth of knowledge is more challenging
than in a functionally aligned organization. Communities of practice can connect team
members with similar strengths and levels of expertise. For example, if you’re the only
expert for clean code or engineering practices in a cross-functional team, then you
won’t have access to colleagues that could help you advance in that area. Communities
of practice can help otherwise isolated individuals who share similar strengths come
together to learn from each other, develop and share best practices, solve problems
together, and advance each other’s knowledge.

336 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


14.9 Summary

14.9 Summary
Learning and implementing clean code practices takes major effort and discipline.
You’ll meet many challenges on the way to mastery. To master clean code, more than
an individual effort is required; you’ll need to establish a common understanding
across your team and potentially across teams or with other organizations. This chap-
ter provided many different learning techniques for individuals, teams, and organiza-
tions. You’ll need to choose the techniques that fit best based on your current skills and
the specific challenges you face. If you commit as both an individual and as a team, the
payoffs can be immense.

Personal Copy for Andrei Arabolea, [email protected] 337


© 2025 by Rheinwerk Publishing Inc., Boston (MA)
The Authors

Klaus Haeuptle is a developer, trainer, and product owner for


engineering topics. During his career at SAP, he has worked as a
developer on several products based on various technologies.
Additionally, he facilitates communities across SAP with the
intention of sharing knowledge and improving practices and
tools. He has influenced testing improvements for ABAP, SAP
HANA, and SAPUI5. Together with Florian Hoffmann, he wrote
an internal Clean ABAP guide, which was later published exter-
nally as an open-source project and serves as the foundation for
this book.

Florian Hoffmann is a software architect for governance, risk,


and compliance applications at SAP. As an agile driver, he is con-
stantly trying to make writing code more efficient. Together
with Klaus Haeuptle, he started the Clean ABAP open-source
style guide that complements this book.

Rodrigo Jordão is a development architect at SAP currently


working on integrated business planning (IBP) with a focus on
SAP Supply Chain Control Tower. He has spent his SAP career
working on various SAP products, from industry-specific solu-
tions like SAP Intellectual Property Management to founda-
tional products like sales and distribution. Prior to joining SAP,
he worked as a web developer with Perl and Java and as a consul-
tant with Microsoft technologies.

Michel Martin is a development architect at SAP. He has played


various roles during his SAP career, including leading trainings,
managing projects, and coaching teams on ASE, lean principles,
and scrum. He has a strong technical background and is con-
stantly looking for new ways to improve team efficiency and
quality focus, from using technology and tools to adopting lean
processes.

Personal Copy for Andrei Arabolea, [email protected] 339


The Authors

Anagha Ravinarayan is a developer at SAP working on SAP


S/4HANA Cloud and on-premise applications in capacity plan-
ning and demand-driven replenishment product areas using
ABAP, SAP HANA, and SAPUI5. She started her career as a full-
stack developer of SAP S/4HANA procurement applications. She
is also passionate about web development using TypeScript,
Node.js, React.js, MySQL, and MongoDB, and is an open-source
software enthusiast.

Kai Westerholz is a senior developer working in the SAP S/4HANA


quote-to-cash area. In this role, he focuses on creating APIs like
the Sales Order Simulation API or the Sales Order Bulk Process-
ing API. Another topic of focus is the enablement of the sales
order for machine learning capabilities. In parallel, he estab-
lishes clean code and test-driven development in his team. Pre-
viously, he worked as a consultant for SAP Cloud for Customer,
specializing in integration with both SAP and non-SAP systems
using different technology stacks.

340 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Index

A Actions ......................................................................... 57
Affixes .............................................................. 140–141
ABAP ..................................................................... 23, 33 Aliases .......................................................................... 81
comments ........................................................... 212 Alignment ............................................................... 221
exceptions ................................................. 233, 236 assignment statements ................................. 221
messages ............................................................. 229 ragged .................................................................. 226
object orientation ............................................... 41 variable declarations ..................................... 222
packages .............................................................. 299 vertical ................................................................. 226
programming paradigm .................................. 37 Annotations ........................................................... 255
test tools .............................................................. 288 Append statement ............................................... 174
ABAP Authority Check Test Helper API ....... 289 Application hierarchies ..................................... 305
ABAP CDS Test Double Framework ............... 288 Arguments .............................................................. 224
ABAP Data Dictionary ............................. 51, 53, 64 Array-like tables .................................................... 172
ABAP Development Tools (ADT) ............ 77, 118, ASSERT statements .............................................. 251
138, 217, 219, 239, 246, 254, 260, 310 Assertions ....................................................... 255, 269
ABAP Docs ............................................ 212, 258, 268 constraints ......................................................... 277
ABAP Objects .............................................. 51–52, 95 custom ................................................................. 275
implementation inheritance .......................... 65 expected exceptions ....................................... 272
ABAP Programming Guidelines ........................ 30 failure ................................................ 269, 271, 274
ABAP Runtime Type Services (RTTS) ................ 86 methods ...................................................... 269, 277
ABAP SQL Test Double Framework ................ 288 writing .................................................................. 270
ABAP Test Cockpit ............................... 28, 243, 325 Assignment operators ........................................ 221
ABAP Test Double Framework ......................... 284 Assignment statements .................................... 221
configure calls ................................................... 287 Automated executions ...................................... 312
when to use ......................................................... 288 Automated testing ............................ 253, 292, 333
ABAP Unit ............................................. 253, 256, 262 Avoidable errors ................................................... 247
call sequence ...................................................... 262
ABAP Workbench .................................................. 308 B
abap_bool type ...................................................... 162
abap_false ...................................................... 162–163 Backlog items ............................................................ 27
abap_true ................................................................. 162 Base classes ................................................................ 59
abap_undefined .................................................... 162 Black grade .............................................................. 322
abapGit ......................................................................... 29 Blank lines ............................................................... 220
abaplint ........................................................................ 29 Blank spaces ........................................................... 220
ABAP-Managed Database Procedures Block operations ................................................... 182
(AMDPs) .................................................................. 35 Block processing ................................................... 181
abapOpenChecks ..................................................... 29 Booleans .......................................................... 161, 163
Abbreviations ......................................................... 136 conditions ........................................................... 191
Abstract classes ................................................. 59, 63 naming ................................................................ 137
rules .......................................................................... 61 parameters ......................................................... 106
Abstract factory ........................................................ 91 Bottlenecks ............................................................. 320
Abstract final .......................................................... 155 Boy Scout Rule ....................................... 27, 201, 212
Abstract types ............................................................ 53 Branch coverage .......................................... 294, 326
Abstraction .................................. 24, 52, 55, 81, 270 Branches .................................................................. 148
levels ...................................................................... 117 IF statements ..................................................... 188
Accessor methods ................................................... 82 nesting depth .................................................... 190
Accidental architecture ...................................... 316 Broken window effect ......................................... 323
Accounts ...................................................................... 53 examples ............................................................. 324

Personal Copy for Andrei Arabolea, [email protected] 341


Index

Builder class ............................................................... 89 Classes (Cont.)


Builder pattern .......................................................... 89 dependencies ........................................................ 85
Built-in object class ................................................. 66 enumeration ....................................................... 154
friendship ............................................................... 81
C global ....................................................................... 76
immutable ............................................................. 82
CALL FUNCTION statement .............................. 231 inheritance .............................................. 39, 59, 65
Call method ................................................... 127, 189 instance .................................................................. 96
camelCase ................................................................ 140 local .......................................................................... 77
Capitalization ......................................................... 217 messages .............................................................. 230
CASE blocks ............................................................. 198 naming ................................................................. 136
CASE keyword ........................................................ 185 production ........................................................... 254
CASE statements ................................................... 196 small ........................................................................ 36
hide ........................................................................ 199 stateful .................................................................... 75
reusing .................................................................. 199 static ........................................................................ 61
versus IFs ............................................................. 197 stub ......................................................................... 281
CATCH blocks ............................................... 202, 249 test .......................................................................... 254
CATCH keyword .................................................... 249 test helper ............................................................ 259
utility ................................................................ 61, 97
Catching exceptions ............................................ 248
visibility .................................................................. 81
cleanup ................................................................. 250
wrap with functions .......................................... 43
Chaining ......................................................... 150, 166
Classic exceptions ....................................... 234–235
IFs ........................................................................... 200
convert .................................................................. 241
Changing parameters .......................................... 111
Clean ABAP .................................... 23, 29, 33, 35–37
pass by value ...................................................... 115
back story .............................................................. 25
Character limits ..................................................... 219
community engagement ................................. 30
CHECK keyword ..................................................... 186
get started ............................................................. 26
CHECK statements .................. 124, 186, 200, 203
repository .............................................................. 31
Checks ................................ 148, 164–165, 189, 200
rules .......................................................................... 26
automatic .............................................................. 28
Clean ABAP open-source style guide ............ 327
basic ...................................................................... 165
Clean code ............................ 23, 185, 205, 249, 319
packages .............................................................. 308
messages .............................................................. 232
result ..................................................................... 109
Clean code advisor ................................................ 330
suppress ............................................................... 213
Clean Code Developer Initiative ..................... 322
Child classes ....................................................... 66, 69 Clean islands ............................................................. 28
Circular dependency ........................................... 309 Clean test properties ............................................ 292
CL_OSQL_REPLACE statement ........................ 288 Cleanup ..................................................................... 251
Class declaration statement ................................ 83 CLEANUP keyword ................................................ 250
Class instances .......................................................... 59 Client code ................................................ 81, 91, 107
Class interfaces ...................................................... 266 Client packages ....................................................... 300
Class member visibility ......................................... 81 Closing brackets ..................................................... 223
Class under test ..................................................... 266 Code base .................................................................... 35
naming ................................................................. 268 Code coverage ............................................... 325–326
Class-based exceptions ................... 233, 235–237, Code editor .............................................................. 219
242, 245 Code Inspector .................................................. 28, 37
raising ................................................................... 247 Code optimization ................................................ 115
Classes .................................................................. 51, 57 Code retreat ................................................... 330–331
abstract ................................................................... 59 Code review ..................................................... 25, 326
builder ...................................................................... 89 feedback culture ................................................ 327
composition .......................................................... 70 managers ............................................................. 329
constants ............................................................. 154 prefix ..................................................................... 326
constraint ............................................................ 278 rules ........................................................................ 328
definition ................................................................ 59 visibility ................................................................ 327

342 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Index

Code rewrite ............................................................ 324 Constructors (Cont.)


Code steward .......................................................... 321 dependencies ........................................................ 86
Code under test ..................................................... 266 error handling ................................................... 123
isolate ................................................................... 279 injection .............................................................. 280
Cohesion ......................................................... 297, 299 scope and visibility ............................................ 83
Collecting parameters ......................................... 111 specialized ............................................................. 97
Collective code ownership ................................ 320 static ................................................................. 49, 90
steps ....................................................................... 320 CONTINUE statement ......................................... 201
Command ................................................................... 56 Control flow ................................ 185, 193, 203, 273
Commented code ................................................. 211 pseudo loops ...................................................... 200
Comments ............................................................... 205 Converting expressions ..................................... 195
IF branches .......................................................... 210 Copy-on-write optimization ............................ 113
instructive ........................................................... 211 Core data services (CDS) views ................. 51, 288
issues ..................................................................... 205 naming ................................................................ 140
method names .................................................. 268 Coupling ................................................................... 297
placement ........................................................... 208 undesired ............................................................ 309
pseudo .................................................................. 213 Coverage report ........................................... 260, 294
remove .................................................................. 210 CREATE OBJECT ........................................................ 93
special ................................................................... 212 Creational patterns ................................................. 89
what to avoid ..................................................... 208 Credit score service class ................................... 106
when to use ......................................................... 207 Cross-functional teams ............................. 334–335
Common understanding ......................... 319, 321 Currency conversion .......................................... 290
Communication style ......................................... 328 service ...................................................................... 53
Communities of practice ................................... 336 Custom assertions ...................................... 270, 275
Community engagement ..................................... 30 constraints ......................................................... 277
Compatible reference variable ........................... 91 parameters ......................................................... 276
Complex conditions ............................................ 195 Custom stubs ......................................................... 284
Composing object .................................................... 74 CX_DYNAMIC_CHECK ....................................... 244
Composition .............................................................. 70 CX_NO_CHECK ..................................................... 243
constraints .......................................................... 279 superclass ........................................................... 246
versus inheritance ............................................... 73 CX_STATIC_CHECK .................................... 242, 247
COND statements ................................................. 187 Cyclic dependency ............................................... 309
Condensing ............................................................. 220 Cyclomatic complexity metric .............. 190, 325
Conditions ............................................ 186, 190, 195
complex ............................................................... 195 D
deconstruct ......................................................... 196
enhance ................................................................ 192 Data keyword ......................................................... 146
inline ..................................................................... 195 chaining .............................................................. 150
positive formulation ....................................... 190 Database tables ..................................................... 173
raising exceptions ............................................ 248 Decision trees ........................................................ 190
reusing .................................................................. 193 Declaration .................................................... 145–146
Consistency ................................................... 137, 216 alignment ........................................................... 222
Constant pattern ................................................... 155 anonymous ........................................................ 147
Constants ....................................................... 152, 159 chaining .............................................................. 149
grouping .............................................................. 159 inline ..................................................... 44, 146, 227
handling ............................................................... 154 scope ..................................................................... 149
Constraints .............................................................. 277 strings .......................................................... 160–161
classes ................................................................... 278 up front ................................................................ 149
Constructors ........................................ 61, 63, 83, 87 Declarative programs ............................................ 38
ADT ........................................................................ 239 Deconstruction ..................................................... 196
declare multiple ................................................... 86 Default constructor ................................................ 84
default ..................................................................... 84 Default fail method ......................................... 56, 88

Personal Copy for Andrei Arabolea, [email protected] 343


Index

Default ignore methods ........................................ 56 Error handling (Cont.)


Default keys ............................................................ 174 exceptions ........................................................... 236
Default parameter values .................................. 104 messages .............................................................. 229
Depended-on components (DOC) .................. 279 package checks .................................................. 313
Dependencies ...................................... 158, 250, 279 raising and catching ....................................... 245
circular ................................................................. 315 relocating ............................................................. 249
implement ........................................................... 281 return codes ........................................................ 232
return code ......................................................... 234 Error messages ....................................................... 229
test classes .......................................................... 256 Exceptions ........................ 116, 124, 181, 186, 202,
Dependency injection ........................ 85, 280, 293 233, 235–236, 272
Dependency inversion principle ............. 99, 280 catching ................................................................ 248
DESCRIBE TABLE statement ............................. 182 class-based ................................................ 233, 237
Descriptive names ................................................ 133 classes ..................................................................... 99
Design patterns .............................................. 48, 138 CX_DYNAMIC_CHECK .................................... 244
mixing patterns ................................................... 49 CX_NO_CHECK .................................................. 243
Development objects .......................................... 300 CX_STATIC_CHECK .......................................... 242
Development packages ...................................... 300 dependencies ...................................................... 250
Direct assignments .............................................. 147 details .................................................................... 118
DO 1 TIMES statement .............................. 200, 202 dynamically checked ....................................... 244
Dojo .................................................................. 330–331 expected ............................................................... 272
Domain fixed values ........................................... 154 fail fast .................................................................. 122
Domain terms ........................................................ 134 handling ................................................................. 36
Don’t-repeat-yourself principle ... 165, 193, 199 messages .............................................................. 247
Double negations ........................................ 191–192 OTHERS ................................................................. 233
Drivers ............................................................. 331, 333 raise .............................................................. 247, 273
Dumps ....................................................................... 251 request .................................................................. 315
Duplicate entries ................................................... 175 static ...................................................................... 274
Duration ......................................................... 255, 260 superclass ............................................................ 245
Dynamic dispatch ............................................ 69, 95 text IDs .................................................................. 239
Dynamic method calls ........................................ 128 types ....................................................................... 238
Dynamic programming ........................................ 88 unchecked ............................................................ 243
Dynamically checked exceptions ................. 244, unexpected .......................................................... 274
246–247, 249 Exit messages .......................................................... 230
Explicit constructor ................................................ 61
E Explicit false values .............................................. 162
Exporting keyword ............................................... 126
ELSE branches ............................................... 148, 186 Exporting parameters ......................................... 108
Empty key ................................................................ 174 capturing ............................................................. 127
Encapsulation .................................... 41, 52, 76, 300 pass by value ...................................................... 115
Endifs ......................................................................... 210 pass by value versus pass by reference .... 114
Enum keyword ....................................................... 155 quantity ................................................................ 108
Enumeration classes ..................................... 99, 154 versus returning ................................................ 110
conditions ........................................................... 193 Extended program check ..................................... 29
messages ............................................................. 232 External interfaces ................................................ 213
patterns ................................................................ 155 Extract method refactoring .............................. 118
Enumerations ......................................................... 155
CASE statements ............................................... 197 F
SWITCH operators ........................................... 199
Error cases ................................................................ 236 Factories ...................................................................... 91
Error handling .............................. 36, 123, 229, 231 Factory method ................................ 55, 70, 89, 199
assertions ............................................................ 271 Fail fast ....................................................................... 122
dumps ................................................................... 251 Failure handling ..................................................... 234

344 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Index

Failure modes ............................................................ 75 Hash operator ........................................................ 147


Feature code ............................................................ 229 Hashed tables ......................................................... 173
Feedback culture ......................................... 327, 329 append ................................................................. 175
Fellowship ................................................................ 332 Higher level tests .................................................. 257
Field-symbol ................................................. 152, 188 Hungarian notation ......................... 138, 142, 267
Filter keyword ........................................................ 148
Fixed row data ........................................................ 171 I
FIXME prefix ........................................................... 212
Flaky tests ................................................................ 325 IF blocks ........................................ 187, 190, 197, 200
FOR TESTING annotation .................................. 259 IF branches .............................................................. 186
Foreign code ........................................................... 218 comments ........................................................... 210
Foreign tests .................................................. 258, 260 IF keyword ............................................................... 185
Formatting .............................................................. 215 where to put ....................................................... 189
Friendship ......................................................... 81, 266 IF statements ................................................. 179, 188
Function groups ....................................................... 38 versus CASE ........................................................ 196
no inheritance ...................................................... 39 IF_T100_DYN_MSG interface .......................... 241
no instantiation ................................................... 38 Immutability ............................................... 64–65, 75
no interfaces ......................................................... 40 Implicit knowledge .............................................. 195
no method encapsulation ............................... 41 Indentation .................................................... 217, 225
no method namespaces ................................... 39 inline declarations .......................................... 227
weak substitution ............................................... 40 keywords ............................................................. 227
weak variable encapsulation ......................... 41 Indexes .................................................. 172, 175, 177
Function modules ............ 95, 209, 233, 235, 290 Individual code ownership ............................... 320
exceptions ........................................................... 233 Individual learning .............................................. 323
wrapping ............................................................. 236 Infixes ....................................................................... 140
Functional language constructs ........................ 44 Information messages ....................................... 230
examples ................................................................ 45 Inheritance ..................................... 39, 52, 65, 69, 95
Functional programs .............................................. 38 design ...................................................................... 70
exceptions .......................................................... 245
G hierarchies ............................................................. 69
method redefinitions ..................................... 102
Gang-of-four (GoF) design patterns ................. 37 test classes .......................................................... 257
Generalists ............................................................... 335 test fixture methods ....................................... 262
get_messages method ........................................ 146 tree ............................................................................ 65
GitHub .......................................................................... 25 utility classes ........................................................ 63
Given sections ..................................... 265, 267, 284 versus composition ............................................ 73
Given-then-when style ....................................... 265 Inline assignments ..................................... 187, 198
Global classes ............................................................. 77 Inline conditions .................................................. 195
constructors .......................................................... 84 Inline declarations .............. 34, 44, 146, 148, 151
Global container classes ..................................... 257 indent ................................................................... 227
Global field catalog convention ...................... 140 reduce ................................................................... 167
Global namespace ................................................... 76 string literals ..................................................... 160
Global scope ............................................................... 76 Inlining ........................................................................ 36
Global unit test classes ....................................... 257 Input parameters ................................. 44, 102, 111
Grades ........................................................................ 322 Boolean ................................................................ 106
Green grade ............................................................. 322 business partner ............................................... 108
optional ........................................................ 42, 103
H pass by value versus pass by reference ... 113
passing ................................................................. 125
Habits .............................................................. 330, 333 preferred .............................................................. 105
Harmonization ......................................................... 34 quantity ............................................................... 103
Has-a relationships ................................................. 70 reassignment ..................................................... 113

Personal Copy for Andrei Arabolea, [email protected] 345


Index

Input-output parameters .................................. 102 Layer-based hierarchies ...................................... 306


Insert statement .................................................... 175 Layers ............................................................... 135, 305
Instance methods .......................................... 96, 111 Leadership styles ................................................... 321
self reference me ............................................... 129 Learning techniques ............................................ 330
Instantiation ...................................................... 38, 91 Legacy code ................... 26, 33, 120, 218, 292, 319
Integration tests .......................................... 265, 289 declarations ........................................................ 149
Interface segregation principle .................. 54–55 inheritance ............................................................ 70
Interfaces ........................................ 40, 51–52, 58–59 learning techniques ......................................... 333
account ................................................................... 52 naming ....................................................... 142, 269
calling ................................................................... 125 test seams ............................................................ 290
constants ............................................................. 154 Line breaks ..................................................... 220, 224
exceptions ........................................................... 241 Line lengths ............................................................. 219
factory ..................................................................... 91 LINE_EXISTS function ......................................... 177
global ....................................................................... 76 Lines ............................................................................ 183
members ................................................................. 55 Liskov substitution principle ............ 55, 69, 102
method takeover ................................................. 42 Literals ....................................................................... 153
naming ................................................................. 136 string ..................................................................... 160
option methods ................................................... 56 Local area .................................................................... 77
public .................................................................... 293 Local classes ........................................................ 77, 79
public instance methods .................................. 99 constructors .......................................................... 85
static creation methods ................................... 87 Local friends ............................................................ 266
visibility ................................................................... 81 LOOP AT statement .......................... 176, 178–180
Internal package interfaces ............................... 302 Looping variables ........................................ 149, 151
Internal tables .............................................. 148, 171 Loops ................................... 125, 149, 151, 176, 178
append .................................................................. 174 pseudo ......................................................... 185, 200
categories ............................................................ 171 undefined signature ........................................ 203
declaring .............................................................. 173
default keys ........................................................ 173 M
insert ..................................................................... 175
looping variables ............................................. 151 Magic numbers ...................................................... 153
number of rows ................................................. 182 assertion ............................................................... 271
populating ............................................................. 46 Main packages ........................................................ 299
retrieve contents ..................................... 176–177 Maintenance ................................................. 196, 216
unnecessary table reads ................................ 180 Managers .................................................................. 329
Invariants .................................................................... 75 Meaningful names ................................................ 133
Invoking methods ................................................ 102 Measurements ......................................................... 37
Irrelevant constructs .............................................. 34 MESSAGE statements ................................ 229, 231
IS INITIAL statements ......................................... 193 Messages ...................................... 160, 202, 229, 237
IS NOT INITIAL keyword .................................... 165 add to exceptions ............................................. 247
IS NOT statements ................................................ 193 ADT ......................................................................... 239
Is-a relationships ...................................................... 69 classes ......................................................... 230, 241
I-shaped profile ...................................................... 335 clean code ............................................................ 232
Iterations .................................................................. 167 comments ............................................................ 211
Iterator pattern ......................................................... 78 containers ............................................................ 112
error ....................................................................... 271
K handling ............................................................... 229
ID ............................................................................. 230
Kata ............................................................................. 330 map to exceptions ............................................ 238
number ................................................................. 230
L raising ................................................................... 161
types ....................................................................... 229
Latest constructs ...................................................... 34 variables ............................................................... 231

346 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Index

Meta-quality ........................................................... 272 Naming conventions (Cont.)


Methods ..................................... 26, 34, 95, 188, 223 descriptive .......................................................... 133
abstraction levels ............................................. 117 domain terms .................................................... 134
assertion .................................................... 269, 277 Hungarian notation ....................................... 138
body ............................................................. 115, 123 layers .................................................................... 135
Boolean parameters ....................................... 107 legacy code ......................................................... 142
calling ................................................................... 125 noise words ........................................................ 137
CASE statements ............................................... 197 packages ............................................................. 308
CHECK statements ........................................... 187 plural or singular ............................................. 135
check versus return ......................................... 124 reduce operations ............................................ 167
closing brackets ................................................ 223 term consistency .............................................. 137
define outputs ................................................... 108 unit testing ......................................................... 267
do one thing ....................................................... 115 Navigators ...................................................... 331, 333
exceptions ................................................. 238, 242 Neat code ................................................................. 215
funtion module wrapping ............................ 236 Negative conditions ................................... 191–192
IF statements ..................................................... 189 Nested IF statement ................................... 178–179
inheritance .......................................................... 102 Nested types ........................................................... 159
line breaks ........................................................... 226 Nesting depth ............................................... 188, 190
namespaces ........................................................... 39 levels ..................................................................... 190
naming ....................................................... 110, 137 NEW operator ........................................................... 92
nesting ..................................................................... 45 No-check exceptions ........................................... 243
object-oriented programming ....................... 95 Noise words ..................................................... 65, 137
overloading ................................................. 86, 103 Non-object-oriented code .................................... 43
parameters ......................................................... 102 Normalized hierarchy ........................................ 102
parameters formatting .................................. 223 NOT IS statements ............................................... 193
redefinition ......................................................... 100 NOT keyword ................................................ 193–194
refactoring .......................................................... 202 Nouns ........................................................................ 136
remove comments ........................................... 206 Number of statements ....................................... 218
size ......................................................................... 120
small ......................................................................... 36 O
split the behaviors ........................................... 104
takeover by interfaces ....................................... 42 Object orientation ............................................ 37, 41
test ...................................................... 255, 259, 262 Object pattern ............................................... 157–158
test fixture ........................................................... 262 Object references .................................................. 111
Metrics ...................................................................... 325 Object-oriented programming ............ 38, 51, 58
Mob programming .................................... 330, 333 characteristics ...................................................... 51
Mocks ......................................................................... 285 methods .................................................................. 95
instances ................................................................. 98 naming ................................................................ 136
when to use ......................................................... 288 Objects ......................................................................... 58
Multiple parameter calls .................................... 224 allowed ................................................................ 310
Mutation testing ................................................... 333 bundle .................................................................. 303
expose .................................................................. 314
N mock ..................................................................... 285
naming ................................................................ 136
Name collisions ..................................................... 139 regrouping .......................................................... 297
prefixes ................................................................. 140 Obsolete language constructs ............................ 48
Naming conventions ........................................... 133 Obsolete language elements .............................. 46
ABAP ...................................................................... 138 Open communication ........................................ 143
abbreviations ..................................................... 136 Open SQL .......................................................... 51, 288
affixes ................................................................... 140 Open-closed principle ........................................... 55
classes and methods ....................................... 136 Optimization .......................................................... 216
collisions .............................................................. 139 Optional methods ................................................... 56

Personal Copy for Andrei Arabolea, [email protected] 347


Index

Optional parameter names .............................. 128 Partial implementations ...................................... 59


Orange grade .......................................................... 322 Pass-by-reference parameters .......................... 113
OTHERS exceptions ............................................. 233 exporting ............................................................. 114
Output parameters ........................... 102, 109, 112 input ...................................................................... 113
capturing ............................................................. 126 Pass-by-value parameters .................................. 112
Overloading ............................................................... 41 changing .............................................................. 115
exporting ............................................................. 114
P input ...................................................................... 113
Performance .................................................... 35, 207
Package checks ............................................. 308, 310 Pipe operator .......................................................... 161
automated execution ..................................... 312 Plugin architecture ................................................. 55
best practices ..................................................... 315 Plugins ....................................................................... 245
errors ..................................................................... 313 Plural or singular forms ...................................... 135
manual execution ............................................ 310 Polymorphism ........................................ 52, 69, 199
Package hierarchies ................................... 297, 304 Positive conditions ............................................... 191
by applications .................................................. 305 Pragmas ..................................................................... 213
layer-based ......................................................... 306 Precise code ............................................................. 205
translation relevance ..................................... 306 Predefined variables ............................................ 146
Package interfaces ...................................... 301, 309 Preferred parameters ................................. 105, 128
expose objects ................................................... 314 Prefixes ............................................................ 138, 140
scope ..................................................................... 302 code review ......................................................... 326
Packages ............................................................ 52, 297 comments ............................................................ 211
best practices ..................................................... 303 examples .............................................................. 140
breakdown .......................................................... 308 packages .............................................................. 308
cohesion ............................................................... 299 test classes ........................................................... 267
design .................................................................... 304 Pretty printer .......................................................... 217
encapsulated ...................................................... 300 Primary code checks .............................................. 28
errors ..................................................................... 313 Primary keys ........................................................... 172
naming ................................................................. 308 Print margins .......................................................... 219
no strategy .......................................................... 316 Private constructors ............................................. 266
reuse levels .......................................................... 298 Private methods .................................................... 122
roles ....................................................................... 300 Private visibility ....................................................... 81
trigger checks ..................................................... 310 Procedural language constructs ........................ 43
types ...................................................................... 299 Procedural programs ............................................. 38
use cases .............................................................. 298 Procedure coverage .............................................. 294
Pair programming ...................................... 330, 332 Process methods ..................................................... 57
different roles ..................................................... 333 Production classes ................................................ 254
Parameters .................................................... 102, 223 Programs .................................................................... 95
Boolean ................................................................ 106 Propagation ............................................................. 242
changing .............................................................. 111 Property ...................................................................... 82
exporting ............................................................. 108 Protected constructor ........................................... 61
indent .................................................................... 225 Protected members ................................................ 61
input ...................................................................... 103 Proxies ......................................................................... 73
optional .................................................................. 42 Pseudo comments ................................................ 213
optional names ................................................. 128 Pseudo loops ................................................. 185, 200
pass by reference .............................................. 113 Public constants .................................................... 238
pass by value ...................................................... 112 Public instance methods ............................... 98–99
placement ........................................................... 224 Public interface ........................................................ 81
returning ............................................................. 109 test .......................................................................... 293
vertical alignment ........................................... 226 Public package interface ........................... 302, 315
Parent classes .................................................... 65, 69 Public reuse ............................................................. 298
Pareto principle ..................................................... 310

348 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Index

Q Rows (Cont.)
discern number ................................................. 182
Quick fixes ............................................................... 246 find ........................................................................... 46
insert ..................................................................... 175
R read ....................................................................... 177
Runtime ................................................................... 207
Ragged alignments ............................................... 226 test ......................................................................... 256
RAISE EXCEPTION keyword .............................. 247
RAISE SHORTDUMP statements ..................... 252 S
RAISING cx_root clause ..................................... 275
Raising exceptions ............................................... 247 SAP assurance and compliance software ...... 36
conditions ........................................................... 248 SAP GUI ........................................ 138, 229, 231, 310
with messages ................................................... 247 SAP HANA .................................................................. 36
READ TABLE statement ............................ 176, 178 Scenarios .................................................................. 265
Readability ............................. 23, 47, 215–216, 220 Scope ......................................................................... 149
Read-only attributes ............................................... 82 conditions ........................................................... 191
Receiving keyword ............................................... 127 package interfaces .......................................... 301
Red grade .................................................................. 322 test classes .......................................................... 257
Redefined methods .............................................. 102 Self-reference me ................................................. 129
Reduce operations ................................................ 166 omit ....................................................................... 129
concerns ............................................................... 168 Server packages ..................................................... 300
naming ................................................................. 167 Setter injections .................................................... 280
Refactoring .............................. 27, 71–72, 118, 123, Setup methods ................................... 262, 264–265
143, 168, 201, 207, 211, 324 Severity ..................................................................... 159
loops ......................................................................... 28 Short text ................................................................. 230
remove comments ........................ 206, 210–211 Side effects ................................................................. 44
References ................................................................ 152 Single Level of Abstraction Principle
Regrouping .................................................... 297–298 (SLAP) ................................................................... 118
Regular expressions .......................... 145, 164–165 Single parameter calls ........................................ 224
complex ............................................................... 166 Single row operations ......................................... 182
simple .................................................................... 164 Single-responsibility principle ......... 55, 75, 201
Remote function calls (RFCs) .............................. 43 Singleton .............................................................. 48, 89
Renaming ................................................................. 143 Small tables ............................................................. 172
Restricted package interface .................. 302, 315 snake_case .............................................................. 140
Resumability ................................................. 243, 248 Software breakdown ........................................... 304
Return codes ........................................................... 232 SOLID principles ...................................................... 55
errors ..................................................................... 234 Sorted access .......................................................... 172
failure handling ................................................ 234 Sorted tables ........................................................... 172
RETURN statement ............................................... 124 append ................................................................. 175
Returning parameters .............................. 109, 126 Split-up expressions ........................................... 166
capturing ............................................................. 126 Standard tables ...................................................... 172
naming ................................................................. 110 append ................................................................. 175
Returning tables ....................................................... 37 State .............................................................................. 75
Reuse levels ............................................................. 298 Stateful classes ......................................................... 75
Revisions ..................................................................... 35 Statement coverage .................................... 294, 326
Risk level ......................................................... 256, 260 Statement Coverage report .............................. 261
Roles .............................................................................. 59 Static classes .............................................................. 61
Rollback work ......................................................... 256 Static code check .................................................. 325
Root exception class ............................................ 237 issues .................................................................... 324
Rows ........................................................................... 171 Static constructor ............................................. 49, 90
append .................................................................. 174 Static creation methods .................. 49, 86, 89, 97
block processing ............................................... 181 interfaces ............................................................... 87

Personal Copy for Andrei Arabolea, [email protected] 349


Index

Static exceptions ................................................... 274 Technical debt .............................................. 316, 324


forward ................................................................. 275 Technical packages ............................................... 305
multiple ................................................................ 275 Template method .................................................... 61
Static methods ........................................... 55, 63, 95 design pattern ...................................................... 70
call ............................................................................. 98 Termination ............................................................ 251
Status messages .................................................... 230 Test classes ............................................................... 254
Strings ....................................................................... 160 executing ............................................................. 260
building ................................................................ 161 given/when ......................................................... 267
literals ................................................................... 160 global ..................................................................... 257
static ...................................................................... 160 higher level tests ............................................... 257
Structure packages ............................................... 299 isolate .................................................................... 293
Stubs ................................................................ 281–282 local .................................................... 254, 257–258
custom .................................................................. 284 resetting ............................................................... 265
when to use ......................................................... 288 scope ...................................................................... 257
Style guide ..................................................... 327, 329 Test code ................................................................... 292
Subclasses ................................................................ 100 Test coverage .......................................................... 294
Sub-methods .......................................................... 190 Test doubles ................................ 279–280, 282, 293
Subpackages .................................................. 307–308 ABAP Test Double Framework .................... 287
Subroutines ................................................................ 95 mock object ......................................................... 287
Substitution ....................................................... 40, 69 test tools ............................................................... 288
Test execution ........................................................ 255
Suffixes .............................................................. 65, 140
Test fixture methods ........................................... 262
packages .............................................................. 308
Test helper classes ....................................... 259, 276
Superclass ............................................. 61, 65, 68–69
Test injections ........................................................ 290
exceptions ................................................. 237, 245
Test methods ...................................... 255, 259, 262
test ......................................................................... 259
executing ............................................................. 262
Superfluous statements ..................................... 180
isolation ..................................................... 263, 293
SWITCH operations .............................................. 198
naming ................................................................. 268
reusing .................................................................. 199
scenarios .............................................................. 265
Synonyms ................................................................ 137
static exceptions ............................................... 275
Syntax ........................................................................ 193
Test relations ................................................. 258, 288
warnings ........................................... 242–244, 246
Test seams ................................................................ 289
System administrators ....................................... 307
Test superclass ........................................................ 259
sy-subrc .................................................. 231, 233–234
Test-driven development (TDD) .................... 250,
field ........................................................................ 191
271, 291
Testing subclasses ................................................. 291
T TEST-SEAM block ................................................... 290
Text IDs ..................................................................... 239
T100 exception ............................................ 238, 242 The Stepdown Rule ............................................... 118
T100 messages ............................................. 238, 241 Then sections ............................. 265, 267, 269, 284
Tab key ...................................................................... 227 Thermal protector ................................................... 99
Table GTADIR ......................................................... 141 Thermal switch ........................................ 71, 99, 280
Tables ......................................................................... 148 Timely tests ............................................................. 291
categories ............................................................ 172 TODO prefix ............................................................ 212
hashed .................................................................. 173 Transaction
naming ................................................................. 168 CHECKMAN ........................................................... 29
read ........................................................................ 176 SAUNIT_CLIENT_SETUP ................................ 255
sorted .................................................................... 172 SE11 ......................................................................... 141
Target currency ........................................................ 53 SE24 ........................................................................ 217
Teams ........................................................................ 321 SE80 ................................................... 138, 217, 312
learning ................................................................ 323 SE91 ........................................................................ 142
members .............................................................. 334 Translation relevance .......................................... 306
Teardown methods .................................... 262, 265 options .................................................................. 307

350 © 2025 by Rheinwerk Publishing Inc., Boston (MA)


Index

Transparent tables ................................................ 136 Value operator ....................................................... 147


T-shaped profile ........................................... 334–335 Value semantics ....................................................... 63
Type keyword ......................................................... 150 Variables .................................................................. 145
Type safety ............................................ 155, 157, 162 alignment ........................................................... 222
assignment ............................................................ 45
U Boolean ................................................................ 163
branches and scope ........................................ 148
Ubiquitous language ........................................... 135 comments ........................................................... 206
Unavailable constructs .......................................... 34 control flow ........................................................ 193
Unchecked exceptions .................... 243, 246, 251 declaring ............................................................. 146
Understandable code ................ 24, 188, 195, 207 helper .................................................................... 167
Uniformity .............................................................. 217 looping ................................................................. 151
Unit testing ....................... 147, 188, 253, 257, 289 messages ............................................................. 231
assertions ............................................................ 269 strings ................................................................... 161
class under test ................................................. 266 Verbs .......................................................................... 137
classes ................................................................... 254 Vertical alignment ............................................... 226
coverage .............................................................. 260 Vertically dense code .......................................... 220
enumeration ............................................ 154, 156 Visibility ...................................................................... 81
executing ............................................................. 260 constructors .......................................................... 83
messages ............................................................. 231
methods ............................................................... 262 W
naming ................................................................. 267
principles ............................................................. 291 Warnings .................................................................. 213
test doubles ........................................................ 279 messages ............................................................. 230
test seams ............................................................ 289 WHEN block ............................................................ 197
Units ........................................................................... 253 When sections .................................... 265, 267, 284
Unnamed arguments .......................................... 128 Where-used list ..................................................... 232
Unstructured programs ........................................ 37 White grade ............................................................ 322
Unsuitable names ................................................. 134 Whitespaces ............................................................ 220
Upfront declaration ............................................. 149 Wrapping ................................................................. 236
Use access ............................................. 309, 313–314 classes with functions ....................................... 43
Utility classes ........................... 61, 63, 97, 259, 267
rules .......................................................................... 63 X
Utility functions ....................................................... 63
Utility methods ........................................................ 97 XSDBOOL method ................................................ 163
XXX prefix ............................................................... 212
V
Y
Validation messages ............................................ 124
Value copies ............................................................ 113 Yellow grade ........................................................... 322
Value objects ............................................... 63, 78, 99
attributes ................................................................ 65
naming .................................................................... 65

Personal Copy for Andrei Arabolea, [email protected] 351


Service Pages

The following sections contain notes on how you can contact us.

Praise and Criticism


We hope that you enjoyed reading this book. If it met your expectations, please do recom-
mend it. If you think there is room for improvement, please get in touch with the editor of
the book: [email protected]. We welcome every suggestion for improve-
ment but, of course, also any praise!

You can also share your reading experience via Twitter, Facebook, or email.

Technical Issues
If you experience technical issues with your e-book or e-book account at SAP
PRESS, please feel free to contact our reader service: [email protected].

About Us and Our Program


The website http://www.sap-press.com provides detailed and first-hand informa-
tion on our current publishing program. Here, you can also easily order all of our
books and e-books. Information on Rheinwerk Publishing Inc. and additional contact
options can also be found at http://www.sap-press.com.

Legal Notes
This section contains the detailed and legally binding usage conditions for this e-book.

Copyright Note
This publication is protected by copyright in its entirety. All usage and exploitation rights
are reserved by the author and Rheinwerk Publishing; in particular the right of reproduction
and the right of distribution, be it in printed or electronic form.

© 2021 by Rheinwerk Publishing, Inc., Boston (MA)

i
Your Rights as a User
You are entitled to use this e-book for personal purposes only. In particular, you may print
the e-book for personal use or copy it as long as you store this copy on a device that is solely
and personally used by yourself. You are not entitled to any other usage or exploitation.

In particular, it is not permitted to forward electronic or printed copies to third parties.


Furthermore, it is not permitted to distribute the e-book on the Internet, in intranets, or
in any other way or make it available to third parties. Any public exhibition, other publica-
tion, or any reproduction of the e-book beyond personal use are expressly prohibited. The
aforementioned does not only apply to the e-book in its entirety but also to parts thereof
(e.g., charts, pictures, tables, sections of text).

Copyright notes, brands, and other legal reservations as well as the digital watermark may
not be removed from the e-book.

Digital Watermark
This e-book copy contains a digital watermark, a signature that indicates which person
may use this copy. If you, dear reader, are not this person, you are violating the copyright.
So please refrain from using this e-book and inform us about this violation. A brief email to
[email protected] is sufficient. Thank you!

Trademarks
The common names, trade names, descriptions of goods, and so on used in this publication
may be trademarks without special identification and subject to legal regulations as such.

All of the screenshots and graphics reproduced in this book are subject to copyright
© SAP SE, Dietmar-Hopp-Allee 16, 69190 Walldorf, Germany. SAP, the SAP logo, ABAP,
Ariba, ASAP, Concur, Concur ExpenseIt, Concur TripIt, Duet, SAP Adaptive Server Enter-
prise, SAP Advantage Database Server, SAP Afaria, SAP ArchiveLink, SAP Ariba, SAP Busi-
ness ByDesign, SAP Business Explorer, SAP BusinessObjects, SAP BusinessObjects Explorer,
SAP BusinessObjects Lumira, SAP BusinessObjects Roambi, SAP BusinessObjects Web
Intelligence, SAP Business One, SAP Business Workflow, SAP Crystal Reports, SAP Early-
Watch, SAP Exchange Media (SAP XM), SAP Fieldglass, SAP Fiori, SAP Global Trade Services
(SAP GTS), SAP GoingLive, SAP HANA, SAP HANA Vora, SAP Hybris, SAP Jam, SAP Max-
Attention, SAP MaxDB, SAP NetWeaver, SAP PartnerEdge, SAPPHIRE NOW, SAP Power-
Builder, SAP PowerDesigner, SAP R/2, SAP R/3, SAP Replication Server, SAP S/4HANA,
SAP SQL Anywhere, SAP Strategic Enterprise Management (SAP SEM), SAP SuccessFactors,
The Best-Run Businesses Run SAP, TwoGo are registered or unregistered trademarks of
SAP SE, Walldorf, Germany.

ii
Limitation of Liability
Regardless of the care that has been taken in creating texts, figures, and programs, neither
the publisher nor the author, editor, or translator assume any legal responsibility or any
liability for possible errors and their consequences.

iii

You might also like