Clean Abap
Clean Abap
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.
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
Clean ABAP®
A Style Guide for Developers
Dear Reader,
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?
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.
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:
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.
Preface ....................................................................................................................................................... 17
1 Introduction 23
7
Contents
4 Methods 95
8
Contents
5 Names 133
9
Contents
10
Contents
7.8 Block Processing of Table Rows and Single Row Operations ............................. 181
9 Comments 205
9.5 Special Comments: ABAP Doc, Pragmas, and Pseudo Comments ................... 212
9.6 Summary ................................................................................................................................... 214
11
Contents
10 Formatting 215
12
Contents
13
Contents
13 Packages 297
14
Contents
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.
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.
쐍 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.
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
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.
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.
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
features can be plugged in easily, and many more components can be swapped out
with little extra effort.
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.
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.
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.
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.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.
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).
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:
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:
Adding a method will still give you clean code, as in Listing 2.1.
Listing 2.1 Adding Methods can Make Code Cleaner Where Short Forms are Unavailable
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.
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.
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.
쐍 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.
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.
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.
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:
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.
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.
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:
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
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.
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:
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.
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.
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.
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.
Functional language constructs resemble regular calls to methods, with any number of
IMPORTING parameters and a single RETURNING parameter, for example, in the following:
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:
쐍 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
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:
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:
However, it’s usually easier to remember the more normalized form of built-in
methods that do the same:
쐍 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:
Meanwhile, most programmers are familiar with the short forms of increments:
쐍 Object instantiation
ABAP has its own statement CREATE OBJECT to instantiate classes:
The short form = NEW does the same with less letters, and also brings the variable to a
position where it’s easier to spot:
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:
However, the angular brackets that serve other programming languages as accessors
for arrays allow condensing these statements even further:
Whereas the newer form line_exists clearly states what we really mean:
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
쐍 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.
"
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.
METHOD class_constructor.
the_singleton = NEW some_class( ).
ENDMETHOD.
METHOD get_instance.
result = the_singleton.
ENDMETHOD.
ENDCLASS.
...
DATA(my_object) = some_class=>get_instance( ).
"
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.
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.
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.
쐍 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.
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.
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”
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.
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.
METHODS process
IMPORTING context TYPE REF TO context.
METHODS post_process
DEFAULT IGNORE
IMPORTING context TYPE REF TO context.
ENDINTERFACE.
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.
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.
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.
PUBLIC SECTION.
INTERFACES:
money_target.
ALIASES:
stash FOR money_target~deposit.
PRIVATE SECTION.
DATA:
money_list TYPE money_list.
ENDCLASS.
METHOD stash.
APPEND amount TO money_list.
ENDMETHOD.
ENDCLASS.
Listing 3.4 The piggy_bank Class: A Blueprint to Create Piggy Bank Objects
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.
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.
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,
a class would be better off implementing the desired interface directly, skipping the
abstract class.
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.
PUBLIC SECTION.
INTERFACES:
processor ABSTRACT METHODS process.
PROTECTED SECTION.
METHODS is_authorized
ABSTRACT
RETURNING VALUE(result) type abap_bool.
ENDCLASS.
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
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.
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.
...
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(total) = money_utils=>add(
money1 = sub_total
money2 = tax_total ).
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.
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
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.
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.
METHOD constructor.
me->amount = amount.
me->currency = currency.
ENDMETHOD.
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.
B C
D E
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.
PUBLIC SECTION.
METHODS:
is_on
RETURNING VALUE(result) TYPE abap_bool,
is_off
RETURNING VALUE(result) TYPE abap_bool,
toggle.
PRIVATE SECTION.
ENDCLASS.
METHOD is_on.
result = on.
ENDMETHOD.
METHOD is_off.
result = xsdbool( NOT is_on( ) ).
ENDMETHOD.
METHOD toggle.
on = xsdbool( NOT is_on( ) ).
ENDMETHOD.
ENDCLASS.
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.
ENDCLASS.
METHOD increase_intensity.
intensity += 1.
constrain_intensity( ).
ENDMETHOD.
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.
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
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).
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.
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.
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.
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.
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.
...
PUBLIC SECTION.
INTERFACES: switchable.
ALIASES:
is_on FOR switchable~is_on,
is_off FOR switchable~is_off,
toggle FOR switchable~toggle.
PRIVATE SECTION.
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.
Armed with the switchable and the thermal_sensor interfaces, we can finally tackle the
thermal_switch class, shown in Listing 3.15.
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.
METHOD constructor.
me->managed_device = managed_device.
SET HANDLER trip FOR sensor.
ENDMETHOD.
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.
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()
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.
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.
PUBLIC SECTION.
METHODS:
add_message
IMPORTING message TYPE message.
PRIVATE SECTION.
ENDCLASS.
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.
PUBLIC SECTION.
METHODS convert
IMPORTING
file_content TYPE xstring
RETURNING
VALUE(result) TYPE some_inbound_message.
ENDCLASS.
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.
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).
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.
PUBLIC SECTION.
INTERFACES: message_collection.
PRIVATE SECTION.
DATA:
messages TYPE message_object_table.
ENDCLASS.
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.
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.
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
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.
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.
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).
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.
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.
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.
When you omit the constructor visibility option, the default is CREATE PUBLIC.
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.
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.
PUBLIC SECTION.
INTERFACES:
processor ABSTRACT METHODS process.
METHODS constructor
IMPORTING next TYPE REF TO processor.
PRIVATE SECTION.
DATA:
next TYPE REF TO processor.
ENDCLASS.
METHOD constructor.
me->next = next.
ENDMETHOD.
METHOD processor~post_process.
IF next IS BOUND.
context->add_processor( next ).
ENDIF.
ENDMETHOD.
ENDCLASS.
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.
...
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.
INTERFACE processor
PUBLIC.
CLASS-METHODS create
DEFAULT FAIL
IMPORTING config TYPE REF TO config
RETURNING VALUE(result) type ref to processor.
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.
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.
...
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.
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( ).
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
every consumer is working with the same instance in the same state and with the same
data.
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.
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.
METHOD class_constructor.
single_instance = NEW #( ).
ENDMETHOD.
METHOD get_instance.
result = single_instance.
ENDMETHOD.
ENDCLASS.
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.
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.
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:
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.
Alternatively, you can cast to the desired type when declaring and instantiating the
variable in the same statement, as in the following code:
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.
The object type is also required when assigning to a superclass or interface reference, as
in Listing 3.34.
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.
In the next chapter, we’ll look at the building blocks of clean classes: clean methods.
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.
PUBLIC SECTION.
CLASS-METHODS:
publish.
ENDCLASS.
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.
PUBLIC SECTION.
METHODS:
publish.
ENDCLASS.
To call an instance method, you need an instance of the class, followed by a thin arrow
(->):
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.
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.
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.
PUBLIC SECTION.
CLASS-METHODS fahrenheit_to_celsius
IMPORTING
temperature_in_fahrenheit TYPE temperature
RETURNING
VALUE(result) TYPE temperature.
...
ENDCLASS.
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.
METHOD fahrenheit_to_celsius.
result = ( temperature_in_fahrenheit - 32 ) * 5 / 9.
ENDMETHOD.
ENDCLASS.
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.
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.
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.
...
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.
managed_device
device thermal_switch
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.
PUBLIC SECTION.
METHODS:
constructor
IMPORTING step TYPE int4,
increase_intensity REDEFINITION,
decrease_intensity REDEFINITION.
PRIVATE SECTION.
DATA:
step TYPE int4.
ENDCLASS.
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.
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.
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
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.
METHODS add_item
IMPORTING
product_id TYPE product_id
product_group TYPE product_group
amount TYPE int4
unit TYPE unit_of_measure.
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.
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.
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.
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.
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.
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.
METHODS to_csv
IMPORTING
separator TYPE string DEFAULT `,`
RETURNING
...
METHODS to_csv
IMPORTING
separator TYPE string DEFAULT `,` PREFERRED PARAMETER
quote type string DEFAULT `"`
RETURNING
...
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:
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:
If the parameter is not marked as a PREFERRED PARAMETER, the caller will be forced to
name the parameter when calling it.
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
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.
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.
When a method takes a Boolean input parameter, usually the method does two things
instead of one (see also Section 4.3.1).
METHODS:
calculate_simple_credit_score
RETURNING VALUE(credit_score) TYPE int4,
calculate_complex_credit_score
RETURNING VALUE(credit_score) TYPE int4.
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.
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.
check_business_partners(
EXPORTING
business_partners = my_business_partners
IMPORTING
result = DATA(business_partners_check_result)
messages = DATA(business_partner_messages) ).
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.
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.
Note that this approach is still not the cleanest way to declare check_business_partners,
which we’ll explore next.
check_business_partners(
EXPORTING
business_partners = my_business_partners
IMPORTING
result = DATA(business_partners_check_result) ).
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.
DATA(business_partners_check_result) =
check_business_partners( my_business_partners ).
METHODS validate
CHANGING
messages TYPE message_table.
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.
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.
This version is also easier to consume since the method now doesn’t have a CHANGING
parameter, as in Listing 4.26.
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.
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.
METHODS try_parse_int
IMPORTING
text TYPE string
EXPORTING
success TYPE abap_bool
result TYPE int4.
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.
...
ENDMETHOD.
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.
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.
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.
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).
log_line( line ).
ENDLOOP.
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.
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.
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.
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
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.
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.
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,
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.
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.
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.
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.
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.
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.
...
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.
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.
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.
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.
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.
METHODS:
split_text_into_lines
IMPORTING
text TYPE string
columns_per_line TYPE int4 DEFAULT 40
RETURNING
VALUE(lines) TYPE string_table.
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 ).
split_text_into_lines(
EXPORTING
text = text
columns_per_line = 80
RECEIVING
result = DATA(lines) ).
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) ).
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.
However, we recommend you design your application around interfaces and avoid
using dynamic method calls whenever possible.
Naming the parameter in this case, as in the following code, is unnecessarily verbose
and redundant:
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 ).
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.
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.
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.
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.
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.
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.
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.
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.
Listing 5.1 Singular and Plural Providing a Semantic Link between Identifiers
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.
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 ).
FUNCTION /clean/read_alerts
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.
METHOD add_two_numbers.
result = a + b.
ENDMETHOD.
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.
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.
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.)
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.
request_processor REQUEST_PROCESSOR
requestProcessor REQUESTPROCESSOR
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
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 exception classes with CX_ To easily find and highlight exceptions.
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.
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.
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.
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.
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
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.
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.
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.
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:
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.
IF messages->has_entries = abap_true.
DATA(success) = abap_true.
ELSE.
success = abap_false.
ENDIF.
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.
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.
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.
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.
If these principles are applied consciously, the readability of your code can be signifi-
cantly improved in the ways that only seem minor.
messages = container->get_messages( ).
LOOP AT messages INTO message.
IF highest_severity > message-severity.
highest_severity = message-severity.
ENDIF.
ENDLOOP.
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(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.
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
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(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.
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.
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.
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.
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.
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.
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.
ASSERT sy-subrc = 0.
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> ] ).
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.
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.
CLASS-METHODS class_constructor.
METHODS constructor IMPORTING severity TYPE severity.
ENDCLASS.
METHOD constructor.
me->value = value.
ENDMETHOD.
ENDCLASS.
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.
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.
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.
"Anti-Pattern
DATA logger_message TYPE string.
logger_string = 'Log: Some Text'.
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:
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:
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.
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:
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.
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.
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.
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.
IF messages IS INITIAL.
has_entries = abap_false.
ELSE.
has_entries = abap_true.
ENDIF.
Instead of this lengthy code, we recommend using XSDBOOL, as shown in the following
example:
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:
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:
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.
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}' ) ).
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
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.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
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 ).
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,
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 ).
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.
쐍 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.
In order to access a table entry by its index, we use the INDEX keyword in ABAP. For
example:
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.
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.
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.
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:
쐍 To insert multiple rows of an internal table, for instance, new_employees at the end of
another internal table (employees), use the following statement:
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.
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.
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.
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:
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.
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.
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.
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.
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.
The row’s contents are read into the variable specified after the keyword INTO.
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.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.
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.
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.
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.
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
index = 10.
WHILE index <= 25.
READ TABLE old_employees INDEX index INTO employee.
INSERT employee INTO employees.
index = index + 1.
ENDWHILE.
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.
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.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.
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.
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
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.
is_authorized = abap_true.
ENDIF.
ENDMETHOD.
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 ).
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,
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> ).
METHOD calculate_tax_amount.
IF country = 'US' AND state = 'NY'.
tax_amount = special_tax_ny( ).
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.
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_true.
“Do something
ENDIF.
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.
IF sy-subrc <> 0.
“Do Error Handling
ENDIF.
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.
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.
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.
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.
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.
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.
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.
IF document_category EQ 'C'
AND ( payment_terms = 'Z' OR today < cond_date )
OR customer->has_extended_condition( ) = abap_true
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.
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.
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.
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.
CASE vehicle_type.
WHEN 'TRAIN'.
duration = calculate_train_duration( ).
WHEN 'PLANE'.
duration = calculate_plane_duration( ).
WHEN 'CAR'.
duration = calculate_car_duration( ).
ENDCASE.
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.
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.
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.
CASE vehicle_type.
WHEN 'TRAIN'.
instance = new train( ).
WHEN 'PLANE'.
instance = new plane( ).
WHEN 'CAR'.
instance = new car( ).
ENDCASE.
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.
DO 1 TIMES.
DATA(messages) = precheck_input( input ).
CHECK NOT line_exists( messages[ type = error ] ).
prepare_processing(
EXPORTING input = input
IMPORTING prepared_input = data(prepared_input)
messages = messages ).
CHECK NOT line_exists( messages[ type = error ] ).
messages = process( prepared_input ).
CHECK NOT line_exists( messages[ type = error ] ).
ENDO.
result = COND #( WHEN line_exists( messages[ type = error ] )
THEN failed
ELSE success ).
Three different steps are necessary to process the input. Each individual step can return
a table including messages that were raised during the method execution. Whenever
an error message exists within the table, the processing should be stopped and a result
flag should be returned. In the current implementation, several chained IF statements
are avoided by exiting the loop earlier.
Instead of the CHECK statement, you could have also used a CONTINUE statement, but the
block would be even more confusing. If the implementation requires a separate part
that can be exited early and that another part must still be executed, the method might
become convoluted. Moreover, the method clearly does more than one thing, which
contradicts the single-responsibility principle.
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.
TRY.
precheck_input( input ).
data(prepared_input) = prepare_input( input ).
process( prepared_input ).
result = success.
CATCH cx_error_raised.
result = failed.
ENDTRY.
After the three methods are processed without any exceptions, the result is marked as
successful. Any exception of the type cx_error_raised would exit the TRY block and the
execution would be continued in the CATCH block in which the result is set to failed. The
assumption in this refactoring is, on one hand, that the returned messages are not 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.
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
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.
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.
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.
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 ).
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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
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].
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.
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.
For example, you should write code neatly, as shown in Listing 10.1.
DATA:
a TYPE b,
c TYPE d,
e TYPE f.
DATA:
a TYPE b
,c TYPE d
,e TYPE f.
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.
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.
READ TABLE employees REFERENCE INTO employee WITH KEY location = 'London'.
IF sy-subrc = 0.
employee-region = 'UK'.
ENDIF.
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.
However, too many whitespaces and unnecessary blanks can also make your code less
comprehensible and prone to bugs. So, avoid writing the following code:
We recommend writing the same bit of code by removing the pointless additional
whitespaces, as in the following code:
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.
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( ).
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.
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.
employee-id = '4711'.
employee-name = 'Denise'.
employee-location = 'Los Angeles'.
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.
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( ).
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.
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.
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.
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 ).
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:
Notice how this code is messy and unreadable, and thus, adding more than one param-
eter on one line should be avoided.
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` ) ) ).
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` ) ) ).
DATA(sum) = add_two_numbers(
value_1 = 42 + round_down( 19 * step_size )
value_2 = round_up( input DIV 7 ) ).
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 ).
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.
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.
DATA(some_super_long_param_name) =
if_some_annoying_interface~add_two_numbers_in_a_long_name(
value_1 = 5
value_2 = 6 ).
DATA(sum) = add_two_numbers(
EXPORTING
value_1 = 5
value_2 = 6
CHANGING
errors = errors ).
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 ).
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.
DATA(result) = merge_structures(
a = VALUE #( field_1 = 'X'
field_2 = 'A' )
b = NEW structure_type( field_3 = 'C'
field_4 = 'D' ) ).
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:
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.
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.
쐍 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.
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
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 (&).
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.
number = sy-msgno
type = sy-msgty ).
ENDIF.
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.
METHOD get_address.
CALL FUNCTION 'ADDR_GET'
EXPORTING
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
METHOD save.
IF status = inconsistent.
RAISE EXCEPTION NEW cx_business_error(
reason = co_inconsistent ).
ENDIF.
ENDMETHOD.
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.
METHOD save.
IF status = inconsistent.
RAISE EXCEPTION TYPE cx_business_error
MESSAGE ID 'MSGID'
NUMBER 123
TYPE 'E'
WITH co_inconsistent.
ENDIF.
ENDMETHOD.
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.
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.
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.
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.
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
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.
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:
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.
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.
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.
PRIVATE SECTION.
METHODS:
freezing_temp_conversion FOR TESTING.
ENDCLASS.
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.
SHORT 1 minute
MEDIUM 5 minutes
LONG 1 hour
The RISK LEVEL property also has three options, shown in Table 12.2, with increasing risk
levels and the usual interpretation of each level.
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.
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.
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.
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.
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.
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
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.
PUBLIC SECTION.
CLASS-METHODS assert_equals
IMPORTING
money1 TYPE REF TO money_object
money2 TYPE REF TO money_object.
...
ENDCLASS.
Be sure that you design your test helper classes according to the rules of class design
described in Chapter 3.
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.
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
toolbar. You can also toggle code highlighting with the Hide Highlighting button in the
same toolbar.
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.
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.
ABAP Unit
Test Class
Runner
class_setup()
setup()
teardown()
class_teardown()
rollback
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.
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.
PRIVATE SECTION.
METHODS:
setup,
off_by_default FOR TESTING,
on_after_toggle FOR TESTING.
DATA:
fan_instance TYPE REF TO switchable.
ENDCLASS.
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.
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.
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.
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:
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.
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.
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.
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 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
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.
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.
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
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.
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).
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.
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.
my_customer->validate( message_container ).
ENDMETHOD.
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.
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.
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.
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.
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:
This scenario can be enacted by the test method shown in Listing 12.10.
METHOD raises_when_iterating_empty.
ENDMETHOD.
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.
METHODS read
IMPORTING customer_key TYPE customer_key
RETURNING VALUE(result) TYPE REF TO customer
RAISING no_such_customer_exception.
METHOD reads_existing_customer.
ENDMETHOD.
METHODS:
reads_existing_customer FOR TESTING
RAISING no_such_customer_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.
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.
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.
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.
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.
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.
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.
ENDMETHOD.
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.
PUBLIC SECTION.
INTERFACES: if_constraint.
METHODS:
constructor
IMPORTING
message_container TYPE REF TO message_container.
PRIVATE SECTION.
DATA:
message_container TYPE REF TO message_container.
ENDCLASS.
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.
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.
ENDMETHOD.
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 ).
As usual, pay attention to the readability of your code and remember to make your
constraints and your assertion error messages clear.
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.
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.
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.
...
PUBLIC SECTION.
INTERFACES:
switchable PARTIALLY IMPLEMENTED.
PRIVATE SECTION.
DATA:
turned_on TYPE abap_bool VALUE abap_true.
ENDCLASS.
METHOD switchable~is_on.
result = turned_on.
ENDMETHOD.
METHOD switchable~toggle.
turned_on = abap_false.
ENDMETHOD.
ENDCLASS.
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
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.
PUBLIC SECTION.
INTERFACES: thermal_sensor.
METHODS: raise_event.
ENDCLASS.
METHOD raise_event.
RAISE EVENT
thermal_sensor~critical_temperature_reached.
ENDMETHOD.
ENDCLASS.
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.
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.
METHOD setup.
sensor = NEW #( ).
thermal_switch = NEW thermal_switch(
managed_device = device
sensor = sensor ).
ENDMETHOD.
METHOD turns_off_when_tripped.
thermal_switch->trip( ).
ENDMETHOD.
METHOD turns_off_when_temp_critical.
" 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.
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.
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.
METHOD setup.
ENDMETHOD.
METHOD toggles_on.
ENDMETHOD.
METHOD doesnt_toggle_off.
ENDMETHOD.
ENDCLASS.
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:
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( ).
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( ).
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.
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.
You can find more information in the online documentation for each tool, like imple-
mented features, code examples, and platform/version compatibility.
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.
result-currency = document_currency.
END-TEST-SEAM.
ENDMETHOD.
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.
...
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.
...
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.
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
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.
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.
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?
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.
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.
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.
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.
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.
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
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.
쐍 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.
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,
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.
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
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.
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.
2. Select the Enable Restriction of Client Packages flag, as shown in Figure 13.4.
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.
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.
Package maintenance and use accesses are covered in more detail later in Section 13.4.1.
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.
✓ ✓
Layer n -1
X ✓ X X ✓
…
?
✓ ✓
?
Layer 0
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.
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.
? Access possible
API API
✓ ✓
BO ? BO
✓ ✓
Data Data
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.
Legend
✓ Access allowed
? Access possible
API BO Data
? ? ? ? ?
Customer App B App B
✓ ✓
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.
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.
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.
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.
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.
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.
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.
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
In the following sections, we’ll further explore package checks: what they are, how to
execute them, how to fix errors, and best practices.
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.
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.
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.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.
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.
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.
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
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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:
쐍 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.
In the following sections, we’ll take a deeper look into applying static code checks, met-
rics, and code coverage.
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
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.
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.
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.
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
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.
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.
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.
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.
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.
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.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
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.
쐍 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.
More breadth
More
depth
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.
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
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
The following sections contain notes on how you can contact us.
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].
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.
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.
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