url00:
https://github.com/SAP/styleguides/blob/master/clean-abap/CleanABAP_zh.md
*&--------------------------------------------------------------------*
CLEAN ABaP
Latest commit @ 14 days ago 4299 lines (3056 sloc) 165 KB
ABaP 整洁之道
中文 · English · Français · Deutsch · 日本語
本指南针对 ABP 改编自 Robert C. Martin 所著的 Clean Code。
【CleanABaP目录】
避免干扰词,如 "data"、"info"、"object"
INSERT INTO TABLE 优于 APPEND TO
LINE_EXISTS 优于 READ TABLE 或 LOOP AT
使用 ABAP_TRUE 和 ABAP_FALSE 进行比较
缺省情况下为 PRIVATE,仅在需要时为 PROTECTED
如果全局类为 CREATE PRIVATE,则保留 CONSTRUCTOR 为公有
单独使用 RETURNING 或 EXPORTING 或 CHANGING,而不要组合使用
RAISE EXCEPTION NEW 优于 RAISE EXCEPTION TYPE
做法:
ABAP 整洁之道 > 目录 > 本节
整洁代码入门之法
ABAP 整洁之道 > 目录 > 做法 > 本节
如果您初识整洁代码,应首先阅读 Robert C. Martin 所著的 Clean Code。
借助 Clean Code Developer initiative,您可以从头学起,循序渐进地对该主题有一般性的了解。
建议从容易理解且广为接受的方面入手,如布尔值、条件和 If 语句。
您可能将会从方法一节获得最大受益,特别是做且仅做一件事,把它做好和方法精简,因为这些会极大地改善代码的总体结构。
对于有行事经验但初识整洁代码的团队,本文的某些主题可能会引起团队内激烈的讨论;这些主题绝对“有益健康”,但人们可能刚开始不太适应。
后面会再继续探讨这些颇具争议的主题,特别是注释、名称和格式化,它们可能会引起孜孜不倦的争论,只有认识到整洁代码积极效应的团队才知道它的好处。
旧代码重构之法
ABAP 整洁之道 > 目录 > 做法 > 本节
如果正在遗留项目上工作,其中含有大量无法或不想更改的代码,因为它们可以无冲突地运行在新代码环境,这种情况下, 更改布尔值、条件、If 语句和方法方面的主题最有价值。
对于遗留项目而言, 名称 主题改进太费劲了,它可能会在新旧代码之间产生差异,在某种程度上,其中的诸如避免编码,特别是匈牙利表示法和前缀等节忽略为宜。
我们发现采用四步计划进行重构,结果比较好:
先让团队上道。沟通并解释新的风格,使项目团队的每个人对此达成一致意见。不用一下子就推行所有指导原则,只需从小部分没有争议的子集入手,然后由此拓展。
按照_童子军规则_开展每日的例行工作:每次修改代码都比原先更整洁。不要因此而困扰,好几个小时沉湎于“清理整个营地”,只需花几分钟,思考如何持续不断地改进。
构筑_整洁小岛_:时不时挑选小的对象或组件,试着进行全方位的清洁。这些小岛印证了现在所做事情的好处,为进一步重构形成了经得起考验的坚强堡垒。
谈经论道。不管是设立老派的范根代码评审,还是举办宣讲会,抑或是在自己喜爱的聊天工具中组建讨论板:需要讲出自己的经验和体会,以使团队逐渐达成共识。
自动检查之法
ABAP 整洁之道 > 目录 > 做法 > 本节
没有一整套全面的静态代码检查方法可以自动检测本文所述的我们这里所描述的反面模式。
ABAP 测试主控室、代码分析器、扩展检查和检查管理器提供了一些检查方法,这些方法可能有助于发现某些问题。
abapOpenChecks 是一个开源的代码分析器检查集,也涵盖了所述的某些反面模式。
abaplint 是 一个ABAP 解析器的开源的实现重写。它不需要SAP系统就可以运行,旨在用 abapGit 使代码串行化。它提供了多个集成(GitHub Actions、Jenkins、文本编辑器...),涵盖了某些反面模式,也可用来检查格式化和代码规范。
与其他指南互通之法
ABAP 整洁之道 > 目录 > 做法 > 本节
本指南秉承整洁代码的_精神_,这意味着我们对 ABAP 编程语言进行了一些调整,例如,针对可管理的异常抛出 CX_STATIC_CHECK。
某些论据来自 ABAP Programming Guidelines 与本指南大多是兼容的;背离之处予以指明,务求符合整洁代码的精神。
本指南也遵循 DSAG's Recommendations for ABAP Development,不过我们在大多数细节上更加精确。
表示异议之法
ABAP 整洁之道 > 目录 > 做法 > 本节
编写本风格指南的目标读者已通晓整洁代码或目前正致力于此,且对如何将整洁代码_具体应用于 ABAP_ 极为关注。
因此,请注意,我们没有以原书同样的篇幅和深度介绍所有概念及相关资源:那些内容仍值得一读,特别是,如果您只是因为我们没解释太详细而不同意本文的观点。可使用各节中的链接延伸阅读我们给出指导的背景。
您尽可以讨论文本讲述的任何内容并表示异议。整洁代码的支柱之一是_团队规则_。在您放弃异议之前,一定要给它们一个公平的机会。
CONTRIBUTING.md 就如何变通本指南或在小的细节上另辟蹊径,给出了建议。
名称
ABAP 整洁之道 > 目录 > 本节
使用描述性名称
ABAP 整洁之道 > 目录 > 名称 > 本节
使用可以传达事物内容和含义的名称。
CONSTANTS max_wait_time_in_seconds TYPE i ...
DATA customizing_entries TYPE STANDARD TABLE ...
METHODS read_user_preferences ...
CLASS /clean/user_preference_reader ...
不要只把注意力放在数据类型和技术编码上。它们对理解代码几乎没什么贡献。
" anti-pattern
CONSTANTS sysubrc_04 TYPE sysubrc ...
DATA iso3166tab TYPE STANDARD TABLE ...
METHODS read_t005 ...
CLASS /dirty/t005_reader ...
不要试图通过注释来弥补坏的名称。
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 2: Meaningful Names: Use Intention-Revealing Names。
首选解决方案域和问题域术语
ABAP 整洁之道 > 目录 > 名称 > 本节
在解决方案域(即计算机科学术语,如 "queue" 或 "tree")和问题域(即业务领域术语,如 "account" 或 "ledger")中搜索好的名称。
按问题域命名时,业务层的命名最好听。对于采用域驱动设计而设计的组件(如 API 和业务对象)尤为如此。
按解决方案域命名时,提供大多数技术功能(如工厂类和抽象算法)层的命名最好听。
在任何情况下都不要试图加进自己的语言。需能够在开发人员、产品负责人、合作伙伴和客户之间交换信息,因此要选择所有人不用查定制词典就能理解的名称。
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 2: Meaningful Names: Use Solution Domain Names and [...]: > Use Problem Domain Names。
使用复数形式
ABAP 整洁之道 > 目录 > 名称 > 本节
在 SAP 有一种传统习惯,那就是用单数形式命名事物的表,例如,country 表示“国家表”。外界普遍倾向于使用复数形式表示事物的列表。因此,建议最好改用 countries。
这条建议主要针对诸如变量和属性等事物。> 对于开发对象,可能存在同样> 也有意义的模式,例如,有一种广泛使用的规范,> 以单数形式命名数据库表(“透明表”)。
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 2: Meaningful Names: Use Intention-Revealing Names。
使用能读出来的名称
ABAP 整洁之道 > 目录 > 名称 > 本节
关于对象会有很多思考和讨论,因此要使用能读出来的名称,例如,detection_object_types 优于诸如 dobjt 这种晦涩的名称。
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 2: Meaningful Names: Use Pronounceable Names。
避免缩写
ABAP 整洁之道 > 目录 > 名称 > 本节
如果有足够空间,那就完整地写出名称。仅当超过长度限制时才使用缩写。
如果不得不缩写,首先考虑_不重要_的词。
采用缩写,可能第一眼看起来很高效,但很快就会变得含糊不清。例如,cust 中的 "cust" 究竟是指 "customizing"、"customer" 还是 "custom"?三者在 SAP 应用程序中都很常见。
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 2: Meaningful Names: Make Meaningful Distinctions。
在各处使用相同缩写
ABAP 整洁之道 > 目录 > 名称 > 本节
人们会搜索关键字来查找相关代码。为此,应对相同事物使用相同缩写。例如,始终将 "detection object type" 缩写为 "dobjt",而不是混合使用 "dot"、"dotype"、"detobjtype" 等等。
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 2: Meaningful Names: Use Searchable Names。
用名词表示类而用动词表示方法
ABAP 整洁之道 > 目录 > 名称 > 本节
使用名词或名词词组命名类、接口和对象:
CLASS /clean/account
CLASS /clean/user_preferences
INTERFACE /clean/customizing_reader
使用动词或动词词组命名方法:
METHODS withdraw
METHODS add_message
METHODS read_entries
用诸如 is_ 和 has_ 之类的动词作为布尔方法的开头,读起来会很流畅:
IF is_empty( table ).
建议也像方法一样给函数命名:
FUNCTION /clean/read_alerts
避免干扰词,如 "data"、"info"、"object"
ABAP 整洁之道 > 目录 > 名称 > 本节
省略干扰词
account " instead of account_data
alert " instead of alert_object
或将其替换为某些确实更有价值的特定字眼
user_preferences " instead of user_info
response_time_in_seconds " instead of response_time_variable
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 2: Meaningful Names: Make Meaningful Distinctions
每个概念选取一个词
ABAP 整洁之道 > 目录 > 名称 > 本节
METHODS read_this.
METHODS read_that.
METHODS read_those.
为一个概念选择一个术语并坚持使用;不要混合使用其他同义词。同义词会使读者浪费时间查找本不存在的差异。
" anti-pattern
METHODS read_this.
METHODS retrieve_that.
METHODS query_those.
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 2: Meaningful Names: Pick One Word per Concept
仅在本意如此时使用模式名称
ABAP 整洁之道 > 目录 > 名称 > 本节
不要对类和接口使用软件设计模式的名称,除非本意真的如此。例如,不要将类称为 file_factory,除非它的确实施了工厂设计模式。最常见的模式包括:singleton、factory、facade、composite、decorator、iterator、observer 和 strategy。
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 2: Meaningful Names: Avoid Disinformation
避免编码,特别是匈牙利表示法和前缀
ABAP 整洁之道 > 目录 > 名称 > 本节
鼓励丢掉_所有_编码前缀。
METHOD add_two_numbers.
result = a + b.
ENDMETHOD.
而不是毫无必要地加长
METHOD add_two_numbers.
rv_result = iv_a + iv_b.
ENDMETHOD.
Avoid Encodings > 深入介绍了这样做的理由。
语言
ABAP 整洁之道 > 目录 > 本节
顾及传统
ABAP 整洁之道 > 目录 > 语言 > 本节
如果是针对较早的 ABAP 版本进行编码,则应谨慎采纳本指南中的建议:下文的许多建议利用了相对较新的语法和结构,这些在较早的 ABAP 版本中可能不受支持。在必须支持的最早版本上验证欲遵循的指导原则。不要简单地整个抛弃整洁代码 - 绝大多数规则(例如,命名、注释)在_任何_ ABAP 版本中都行得通。
顾及性能
ABAP 整洁之道 > 目录 > 语言 > 本节
如果是为高性能组件编码,则应谨慎采纳本指南中的建议:整洁代码在某些方面可能会降低速度(更多方法调用)或消耗更多内存(更多对象)。ABAP 的某些特点可能会加剧这种情况,例如,在调用方法时,它会比较数据类型,这样一来,将单个大方法拆分成多个子方法,可能会降低代码速度。
然而,强烈建议不要因为模糊的恐惧就过早地悲观失望。绝大多数规则(例如,命名、注释)根本不会产生任何负面影响。尽力采用整洁的面向对象的方式做事情。如果有什么过慢,就做一个性能测量。只有这样做之后,才应根据事实作出决策,放弃所选规则。
一些更深入的思考,部分取自 Martin Fowler 所著的 Refactoring 中的第 2 章:
在典型的应用程序中,大部分运行时间都花在很小比例的代码中。小到 10% 的代码会占到 90% 的运行时间,特别是在 ABAP 中,很大比例的运行时间可能都是数据库时间。
因此,花大力气试图使_所有_代码都一直保持超高效率,并非最好的资源安排方式。不主张忽视性能,但在初始开发阶段,应该更关注代码的整洁性和条理分明的程度,然后使用剖析器找出关键区域进行优化。
事实上,我们有理由证明,这种方式对性能的正面影响更大,因为优化努力更有针对性,更容易找出性能瓶颈,而且条理分明的代码更容易进行重构和调优。
面向对象编程优于过程式编程
ABAP 整洁之道 > 目录 > 语言 > 本节
面向对象的程序(类、接口)比过程式代码(函数、程序)分段更清晰,并且可以更加容易地进行重构和测试。尽管在某些情况下必须提供过程式对象(对 RFC 用函数、对事务用程序),但这些对象除了调用提供实际功能的相应类之外,不应该再干别的:
FUNCTION check_business_partner [...].
DATA(validator) = NEW /clean/biz_partner_validator( ).
result = validator->validate( business_partners ).
ENDFUNCTION.
Function Groups vs. Classes > 详细描述了两者的差异。
函数式语言结构优于过程式语言结构
ABAP 整洁之道 > 目录 > 语言 > 本节
它们通常更加简短,而且更容易为现代程序员所接受。
DATA(variable) = 'A'.
" MOVE 'A' TO variable.
DATA(uppercase) = to_upper( lowercase ).
" TRANSLATE lowercase TO UPPER CASE.
index += 1. " >= NW 7.54
index = index + 1. " < NW 7.54
" ADD 1 TO index.
DATA(object) = NEW /clean/my_class( ).
" CREATE OBJECT object TYPE /dirty/my_class.
result = VALUE #( FOR row IN input ( row-text ) ).
" LOOP AT input INTO DATA(row).
" INSERT row-text INTO TABLE result.
" ENDLOOP.
DATA(line) = value_pairs[ name = 'A' ].
" READ TABLE value_pairs INTO DATA(line) WITH KEY name = 'A'.
DATA(exists) = xsdbool( line_exists( value_pairs[ name = 'A' ] ) ).
IF line_exists( value_pairs[ name = 'A' ] ).
" READ TABLE value_pairs TRANSPORTING NO FIELDS WITH KEY name = 'A'.
" DATA(exists) = xsdbool( sy-subrc = 0 ).
下文的许多详细规则只不过是具体重申了这条通用的建议。
避免过时语言元素
ABAP 整洁之道 > 目录 > 语言 > 本节
在升级 ABAP 版本时,务必要检查是否有过时的语言元素,避免再使用它们。
例如,以下语句中 @ 转义的 "host" 变量更清楚地表明了什么是程序变量、什么是数据库中的列,
SELECT *
FROM spfli
WHERE carrid = @carrid AND
connid = @connid
INTO TABLE @itab.
相较于过时的转义形式
SELECT *
FROM spfli
WHERE carrid = carrid AND
connid = connid
INTO TABLE itab.
较新的可选方案倾向于提高代码的可读性,减少与现代编程范式的设计冲突,这样切换到这些方案时就会自动使代码更整洁。
如果继续使用旧代码编写方式,过时元素可能在处理速度和内存消耗方面无法再从优化中受益。
使用现代语言元素,可以更轻松地将年轻的 ABAP 程序员带上道,由于在 SAP 的培训中不再教授过时内容,他们可能不再熟悉过时的结构。
SAP NetWeaver 文档固定包含一部分,其中列出了过时的语言元素,例如,NW 7.50、NW 7.51、NW 7.52、NW 7.53。
明智地使用设计模式
ABAP 整洁之道 > 目录 > 语言 > 本节
仅在合适且有明显好处的地方使用。不要为了使用而到处用设计模式。
常量
ABAP 整洁之道 > 目录 > 本节
使用常量而非幻数
ABAP 整洁之道 > 目录 > 常量 > 本节
IF abap_type = cl_abap_typedescr=>typekind_date.
在清晰方面好于
" anti-pattern
IF abap_type = 'D'.
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 17: Smells and Heuristics: G25: > Replace Magic Numbers with Named Constants。
枚举类优于常量接口
ABAP 整洁之道 > 目录 > 常量 > 本节
CLASS /clean/message_severity DEFINITION PUBLIC ABSTRACT FINAL.
PUBLIC SECTION.
CONSTANTS:
warning TYPE symsgty VALUE 'W',
error TYPE symsgty VALUE 'E'.
ENDCLASS.
或
CLASS /clean/message_severity DEFINITION PUBLIC CREATE PRIVATE FINAL.
PUBLIC SECTION.
CLASS-DATA:
warning TYPE REF TO /clean/message_severity READ-ONLY,
error TYPE REF TO /clean/message_severity READ-ONLY.
" ...
ENDCLASS.
而不是将不相关的东西混在一起
" anti-pattern
INTERFACE /dirty/common_constants.
CONSTANTS:
warning TYPE symsgty VALUE 'W',
transitional TYPE i VALUE 1,
error TYPE symsgty VALUE 'E',
persisted TYPE i VALUE 2.
ENDINTERFACE.
Enumerations > 描述了常见的枚举模式> 并讨论了它们的优缺点。
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 17: Smells and Heuristics: J3: Constants versus Enums。
如果不使用枚举类,则对常量进行分组
ABAP 整洁之道 > 目录 > 常量 > 本节
如果以松散方式集合常量,例如,在接口中,则应将其分组:
CONSTANTS:
BEGIN OF message_severity,
warning TYPE symsgty VALUE 'W',
error TYPE symsgty VALUE 'E',
END OF message_severity,
BEGIN OF message_lifespan,
transitional TYPE i VALUE 1,
persisted TYPE i VALUE 2,
END OF message_lifespan.
使关系更清晰,好于:
" Anti-pattern
CONSTANTS:
warning TYPE symsgty VALUE 'W',
transitional TYPE i VALUE 1,
error TYPE symsgty VALUE 'E',
persisted TYPE i VALUE 2,
利用组还可以成组进行访问,例如,进行输入验证:
DO number_of_constants TIMES.
ASSIGN COMPONENT sy-index OF STRUCTURE message_severity TO FIELD-SYMBOL(<constant>).
IF <constant> = input.
is_valid = abap_true.
RETURN.
ENDIF.
ENDWHILE.
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 17: Smells and Heuristics: G27: Structure over Convention。
变量
ABAP 整洁之道 > 目录 > 本节
内联声明优于最前声明
ABAP 整洁之道 > 目录 > 变量 > 本节
如果遵循本文的指导原则,在首次出现的地方内联式声明变量显得更加自然,方法体也会变得很精短(3-5 条语句)。
METHOD do_something.
DATA(name) = 'something'.
DATA(reader) = /clean/reader=>get_instance_for( name ).
result = reader->read_it( ).
ENDMETHOD.
好过在方法开头单独的 DATA 部分声明变量
" anti-pattern
METHOD do_something.
DATA:
name TYPE seoclsname,
reader TYPE REF TO /dirty/reader.
name = 'something'.
reader = /dirty/reader=>get_instance_for( name ).
result = reader->read_it( ).
ENDMETHOD.
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 5: Formatting: Vertical Distance: Variable Declarations。
勿在可选分支中内联声明
ABAP 整洁之道 > 目录 > 变量 > 本节
" anti-pattern
IF has_entries = abap_true.
DATA(value) = 1.
ELSE.
value = 2.
ENDIF.
这样可以正常运行,因为 ABAP 会像声明位于方法开头那样来处理内联式声明。然而,这会令读者感到极其迷惑,特别是方法体较长而又没当场发现声明的话。在此情况下,不要使用内联式声明而将声明放在最前面:
DATA value TYPE i.
IF has_entries = abap_true.
value = 1.
ELSE.
value = 2.
ENDIF.
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 5: Formatting: Vertical Distance: Variable Declarations。
勿用链式最前声明
ABAP 整洁之道 > 目录 > 变量 > 本节
DATA name TYPE seoclsname.
DATA reader TYPE REF TO /dirty/reader.
链式处理主张在逻辑层级关联定义的变量。为了一致性,必须确保所有链式变量结成一体,要添加变量,就得另外引入链组。尽管这种方法可行,但通常不值得花这个功夫。
另外,链式处理也毫无必要地使重新格式化和重构变得复杂,因为每行看起来都不同,改起来需要四处挪动冒号、句号和逗号,根本不值得花功夫。
" anti-pattern
DATA:
name TYPE seoclsname,
reader TYPE REF TO /dirty/reader.
另请参阅 Don't align type clauses 如果使用链式数据声明,则每组结成一体的变量各用一个链。
REF TO 优于 FIELD-SYMBOL
ABAP 整洁之道 > 目录 > 变量 > 本节
LOOP AT components REFERENCE INTO DATA(component).
而非等效形式
" anti-pattern
LOOP AT components ASSIGNING FIELD-SYMBOL(<component>).
需要指针的地方除外
ASSIGN generic->* TO FIELD-SYMBOL(<generic>).
ASSIGN COMPONENT name OF STRUCTURE structure TO FIELD-SYMBOL(<component>).
ASSIGN (class_name)=>(static_member) TO FIELD-SYMBOL(<member>).
从代码评审的经验表明人们往往会随心所欲地做出选择:“就是因为”、“因为我们总是采用那种循环方式”,或者“没特殊原因”。随意选择会令读者把时间浪费在毫无意义的问题上:为什么用这个而不用那个,因此应代之以有理有据、准确无误的决策。我们的建议基于这种理由:
指针能做一些引用做不了的事情,比如动态访问结构的组成部分。同样,引用也能做指针做不了的事情,比如构造动态类型的数据结构。总之,单独指望一个是不行的。
在面向对象的 ABAP 中,引用到处都有并且无法避免,因为任何对象皆是 REF TO <class-name>。相反,指针仅在涉及动态类型的少数特殊情况下才绝对需要。因此,引用自然成为任何面向对象程序中的首选。
指针比引用短,但结果节省的内存却微不足道,尽可以忽略不计。同样,速度也不是问题。因此,在性能方面没理由厚此薄彼。
更多信息参阅 > ABAP Programming Guidelines 中的篇章 Accessing Data Objects Dynamically。
表
ABAP 整洁之道 > 目录 > 本节
使用恰当的表类型
ABAP 整洁之道 > 目录 > 表 > 本节
HASHED 表通常用来表示单步填充、永不修改且常按键值读取的大表。其固有的内存和处理开销使得散列表仅在数据量很大且读访问次数很多的情况下才有价值。每次对表内容进行更改,均需要大量重新计算散列值,因此修改过于频繁的表不要使用此种类型。
SORTED 表通常用于表示需要时时排序、逐位填充或需要修改并且常按一个或多个完整或部分键值读取或以某种特定顺序处理的大表。添加、更改或移除内容,需要找到恰当的插入点,但不需要调整表索引的其余部分。仅对读访问次数很多的情况,有序表才有价值。
STANDARD 表用于表示索引开销大于索引受益的小表,以及或是毫不在乎行顺序或是就想完全按追加顺序进行处理的**“数组”**。另外,也适用于需要对表进行不同访问的情况,例如,通过 SORT 和 BINARY SEARCH 进行索引访问和排序访问。
这些只是粗略的指导原则。> 更多细节参见 ABAP Language Help 中的篇章 Selection of Table Category。
避免 DEFAULT KEY
ABAP 整洁之道 > 目录 > 表 > 本节
" anti-pattern
DATA itab TYPE STANDARD TABLE OF row_type WITH DEFAULT KEY.
添加缺省键值常常只是为了让具有较新功能的语句得以正常工作。事实上,这些键值本身通常是多余的,除了耗费资源,别无它用。由于它们会忽略数值数据类型,因此甚至可能会导致隐蔽的错误。不含显式字段列表的 SORT 和 DELETE ADJACENT 语句将会转而采用内部表的主键,在使用 DEFAULT KEY 的情况下,这可能会导致十分意想不到的结果,例如,当以数值字段作为键值的分量时,特别是当与 READ TABLE ... BINARY 等结合使用时。
要么显式指定键值
DATA itab2 TYPE STANDARD TABLE OF row_type WITH NON-UNIQUE KEY comp1 comp2.
如果根本不需要键值的话,则采用 EMPTY KEY。。
DATA itab1 TYPE STANDARD TABLE OF row_type WITH EMPTY KEY.
参照 Horst Keller 的博客文章 Internal Tables with Empty Key **注意:**具有 EMPTY KEY 的内部表上的 SORT 根本不会进行排序,> 但假如能静态确定键值为空,就会发出语法警告。
INSERT INTO TABLE 优于 APPEND TO
ABAP 整洁之道 > 目录 > 表 > 本节
INSERT VALUE #( ... ) INTO TABLE itab.
INSERT INTO TABLE 对所有表和键值类型都起作用,因而更便于在性能需求发生变化时重构表的类型和键值定义。
仅当以类似数组的方式使用 STANDARD 表时才使用 APPEND TO,如果想要强调所添加的条目应为最后一行的话。
LINE_EXISTS 优于 READ TABLE 或 LOOP AT
ABAP 整洁之道 > 目录 > 表 > 本节
IF line_exists( my_table[ key = 'A' ] ).
更清楚简洁地表明意图,好于
" anti-pattern
READ TABLE my_table TRANSPORTING NO FIELDS WITH KEY key = 'A'.
IF sy-subrc = 0.
或者甚至是
" anti-pattern
LOOP AT my_table REFERENCE INTO DATA(line) WHERE key = 'A'.
line_exists = abap_true.
EXIT.
ENDLOOP.
READ TABLE 优于 LOOP AT
ABAP 整洁之道 > 目录 > 表 > 本节
READ TABLE my_table REFERENCE INTO DATA(line) WITH KEY key = 'A'.
更清楚简洁地表明意图,好于
" anti-pattern
LOOP AT my_table REFERENCE INTO DATA(line) WHERE key = 'A'.
EXIT.
ENDLOOP.
或者甚至是
" anti-pattern
LOOP AT my_table REFERENCE INTO DATA(line).
IF line->key = 'A'.
EXIT.
ENDIF.
ENDLOOP.
LOOP AT WHERE 优于嵌套式 IF
ABAP 整洁之道 > 目录 > 表 > 本节
LOOP AT my_table REFERENCE INTO DATA(line) WHERE key = 'A'.
更清楚简洁地表明意图,好于
LOOP AT my_table REFERENCE INTO DATA(line).
IF line->key = 'A'.
EXIT.
ENDIF.
ENDLOOP.
避免不必要的表读取
ABAP 整洁之道 > 目录 > 表 > 本节
若你_预期_某一行就在表里,那就读取一次并对异常作出处理就够了,
TRY.
DATA(row) = my_table[ key = input ].
CATCH cx_sy_itab_line_not_found.
RAISE EXCEPTION NEW /clean/my_data_not_found( ).
ENDTRY.
而不是用两次读取打乱并减慢主控制流
" anti-pattern
IF NOT line_exists( my_table[ key = input ] ).
RAISE EXCEPTION NEW /clean/my_data_not_found( ).
ENDTRY.
DATA(row) = my_table[ key = input ].
除了提高性能以外,这还是更一般性的关注愉快路径或错误处理,但非两者兼顾的一种特殊变化形式。
字符串
ABAP 整洁之道 > 目录 > 本节
使用 ` 定义文字
ABAP 整洁之道 > 目录 > 字符串 > 本节
CONSTANTS some_constant TYPE string VALUE `ABC`.
DATA(some_string) = `ABC`. " --> TYPE string
避免使用 ',因为它会增加多余的类型转换,并且会令读者困惑于处理的究竟是 CHAR 还是 STRING:
" anti-pattern
DATA some_string TYPE string.
some_string = 'ABC'.
| 一般都适用,但无法用于 CONSTANTS,而且在指定固定值时会增加不必要的开销:
" anti-pattern
DATA(some_string) = |ABC|.
使用 | 汇集文本
ABAP 整洁之道 > 目录 > 字符串 > 本节
DATA(message) = |Received HTTP code { status_code } with message { text }|.
字符串模板更加突出地表明何为文字、何为变量,特别是如果在文本中嵌入多个变量的话。
" anti-pattern
DATA(message) = `Received an unexpected HTTP ` && status_code && ` with message ` && text.
布尔值
ABAP 整洁之道 > 目录 > 本节
明智地使用布尔值
ABAP 整洁之道 > 目录 > 布尔值 > 本节
经常会遇到下面这种情况,布尔值似乎是自然的选择
" anti-pattern
is_archived = abap_true.
而换个视角才发现本应选择枚举
archiving_status = /clean/archivation_status=>archiving_in_process.
一般来说,用布尔值区分事物的类型是一种坏的选择,因为几乎总会遇到并非彼此排斥的情况
assert_true( xsdbool( document->is_archived( ) = abap_true AND
document->is_partially_archived( ) = abap_true ) ).
此外,拆分方法而非使用布尔输入参数还解释了为何应始终回避布尔参数。
更多信息参阅 1
用 ABAP_BOOL 表示布尔值
ABAP 整洁之道 > 目录 > 布尔值 > 本节
DATA has_entries TYPE abap_bool.
不要使用普通类型 char1。尽管在技术上兼容,但它会掩盖处理的是布尔变量这个事实。
也要避免其他布尔类型,因为它们常常会产生奇怪的副作用,例如,boolean 支持第三个值 "undefined",它会导致难以觉察的编程错误。
在某些情况下,例如,对于 DynPro 字段,可能需要数据字典元素。此时无法使用 abap_bool,因为它是在类型池 abap 中而不是在数据字典中定义的。在此情况下,转而采用 boole_d 或 xfeld。如果需要自定义描述,那就创建自己的数据元素。
ABAP 可能是唯一不带通用布尔数据类型的编程语言。然而,设立一个是大势所趋。本建议基于 ABAP Programming Guidelines。
使用 ABAP_TRUE 和 ABAP_FALSE 进行比较
ABAP 整洁之道 > 目录 > 布尔值 > 本节
has_entries = abap_true.
IF has_entries = abap_false.
不要使用等效字符 'X' 和 ' ' 或 space;用它们很难看出这是一个布尔表达式:
" anti-pattern
has_entries = 'X'.
IF has_entries = space.
避免与 INITIAL 进行比较 - 这会迫使读者去回想 abap_bool 的缺省值为 abap_false:
" anti-pattern
IF has_entries IS NOT INITIAL.
ABAP 可能是唯一不带表示真假的内置“常量”的编程语言。然而,设立它们是大势所趋。本建议基于 ABAP Programming Guidelines。
使用 XSDBOOL 设置布尔变量
ABAP 整洁之道 > 目录 > 布尔值 > 本节
DATA(has_entries) = xsdbool( line IS NOT INITIAL ).
等效的 IF-THEN-ELSE 除了长得多之外,别无它用:
" anti-pattern
IF line IS INITIAL.
has_entries = abap_false.
ELSE.
has_entries = abap_true.
ENDIF.
xsdbool 是最合乎本来目的的方法,因为它直接产生 char1,该类型最适合布尔类型 abap_bool。等效函数 boolc 和 boolx 会产生不同的类型并增加不必要的隐式类型转换。
我们同意名称 xsdbool 不巧会产生误导;毕竟,我们对 "xsd" 前缀暗示的 "XML Schema Definition" 部分毫无兴趣。
xsdbool 的一种可行的备选方案是 COND 三元形式。其语法直接明了,但是有点长,因为它会不必要地重复 THEN abap_true 段,而且还需要知道隐式缺省值 abap_false - 这就是为什么我们建议只将其作为第二解决方案。
DATA(has_entries) = COND abap_bool( WHEN line IS NOT INITIAL THEN abap_true ).
条件
ABAP 整洁之道 > 目录 > 本节
尽量使条件为正
ABAP 整洁之道 > 目录 > 条件 > 本节
IF has_entries = abap_true.
反之,比较时看看同样的语句会变得多难理解:
" anti-pattern
IF has_no_entries = abap_false.
节标题中的“尽量”意味着事先不用强行这样做,直到在某一点要以诸如空的 IF 分支之类的语句结束时才应如此:
" anti-pattern
IF has_entries = abap_true.
ELSE.
" only do something in the ELSE block, IF remains empty
ENDIF.
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 17: Smells and Heuristics: G29: Avoid Negative Conditionals。
IS NOT 优于 NOT IS
ABAP 整洁之道 > 目录 > 条件 > 本节
IF variable IS NOT INITIAL.
IF variable NP 'TODO*'.
IF variable <> 42.
否定在逻辑上是等效的,但需要“脑筋转弯”,从而加大了理解难度。
" anti-pattern
IF NOT variable IS INITIAL.
IF NOT variable CP 'TODO*'.
IF NOT variable = 42.
尽量使条件为正的一个更加具体的变化形式。另请参见 ABAP programming guidelines 中的 Alternative Language Constructs 一节。
考虑分解复杂条件
ABAP 整洁之道 > 目录 > 条件 > 本节
将条件分解成若干基本组成部分,条件就会变得更加简单:
DATA(example_provided) = xsdbool( example_a IS NOT INITIAL OR
example_b IS NOT INITIAL ).
DATA(one_example_fits) = xsdbool( applies( example_a ) = abap_true OR
applies( example_b ) = abap_true OR
fits( example_b ) = abap_true ).
IF example_provided = abap_true AND
one_example_fits = abap_true.
而不是全都掺和在一起:
" anti-pattern
IF ( example_a IS NOT INITIAL OR
example_b IS NOT INITIAL ) AND
( applies( example_a ) = abap_true OR
applies( example_b ) = abap_true OR
fits( example_b ) = abap_true ).
使用 ABAP 开发工具的快速修复功能,可以很快提取条件并创建如上所示的变量。
考虑提炼复杂条件
ABAP 整洁之道 > 目录 > 条件 > 本节
将复杂条件提炼成各自的方法是一个好主意:
IF is_provided( example ).
METHOD is_provided.
DATA(is_filled) = xsdbool( example IS NOT INITIAL ).
DATA(is_working) = xsdbool( applies( example ) = abap_true OR
fits( example ) = abap_true ).
result = xsdbool( is_filled = abap_true AND
is_working = abap_true ).
ENDMETHOD.
If 语句
ABAP 整洁之道 > 目录 > 本节
无空的 IF 分支
ABAP 整洁之道 > 目录 > If 语句 > 本节
IF has_entries = abap_false.
" do some magic
ENDIF.
更加简明,好于
" anti-pattern
IF has_entries = abap_true.
ELSE.
" do some magic
ENDIF.
对于多个备选条件,CASE 优于 ELSE IF
ABAP 整洁之道 > 目录 > If 语句 > 本节
CASE type.
WHEN type-some_type.
" ...
WHEN type-some_other_type.
" ...
WHEN OTHERS.
RAISE EXCEPTION NEW /clean/unknown_type_failure( ).
ENDCASE.
采用 CASE 更容易看出来是一组互斥的选择。它比一连串 IF 执行起来更快,因为它可以转化为另一种不同的微处理器命令,而不是一连串顺序评估的条件。不必到处重复判别变量,就可以快速引入新的情况。该语句甚至可以防止无意中嵌套 IF-ELSEIF 时可能出现的一些错误。
" anti-pattern
IF type = type-some_type.
" ...
ELSEIF type = type-some_other_type.
" ...
ELSE.
RAISE EXCEPTION NEW /dirty/unknown_type_failure( ).
ENDIF.
保持低嵌套深度
ABAP 整洁之道 > 目录 > If 语句 > 本节
" ani-pattern
IF <this>.
IF <that>.
ENDIF.
ELSE.
IF <other>.
ELSE.
IF <something>.
ENDIF.
ENDIF.
ENDIF.
嵌套的 IF 不仅难于快速理解,而且需要指数级的测试用例才能完全覆盖。
通常可以通过形成子方法并引入辅助布尔变量来拆分决策树。
其他情况可以通过合并 IF 进行简化,比如
IF <this> AND <that>.
而不是毫无必要地嵌套
" anti-pattern
IF <this>.
IF <that>.
正则表达式
ABAP 整洁之道 > 目录 > 本节
较简单的方法优于正则表达式
ABAP 整洁之道 > 目录 > 正则表达式 > 本节
IF input IS NOT INITIAL.
" IF matches( val = input regex = '.+' ).
WHILE contains( val = input sub = 'abc' ).
" WHILE contains( val = input regex = 'abc' ).
正则表达式难以快速理解。没有它们,简单情况通常反而更加容易。
正则表达式通常也会消耗更多内存和处理时间,因为需要将其解析成表达式树并在运行时编译成可执行的匹配程序。直接使用循环和临时变量,简单就可以解决。
基本检查优于正则表达式
ABAP 整洁之道 > 目录 > 正则表达式 > 本节
CALL FUNCTION 'SEO_CLIF_CHECK_NAME'
EXPORTING
cls_name = class_name
EXCEPTIONS
...
而不用费事改成
" anti-pattern
DATA(is_valid) = matches( val = class_name
pattern = '[A-Z][A-Z0-9_]{0,29}' ).
当正则表达式无处不在时,,对不重复自己 (DRY) 的原则视而不见似乎变成一种自然的倾向,请对照 Robert C. Martin 所著的 Clean Code 中的 Chapter 17: Smells and Heuristics: General: G5: Duplication。
考虑汇集复杂的正则表达式
ABAP 整洁之道 > 目录 > 正则表达式 > 本节
CONSTANTS class_name TYPE string VALUE `CL\_.*`.
CONSTANTS interface_name TYPE string VALUE `IF\_.*`.
DATA(object_name) = |{ class_name }\|{ interface_name }|.
有一些复杂的正则表达式,当您向读者展示它们是如何从更基本的片段构成时,就会变得更加容易。
类
ABAP 整洁之道 > 目录 > 本节
类:面向对象
ABAP 整洁之道 > 目录 > 类 > 本节
对象优于静态类
ABAP 整洁之道 > 目录 > 类 > 类:面向对象 > 本节
首先,静态类失去了面向对象所具备的全部优势。特别是,有了它们,几乎无法在单元测试中用测试替身替换生产中的相关依赖。
如果您在考虑是否该使类或方法变成静态的,答案几乎总是:不。
对于这条规则,有一种例外情况可以接受,那就是简单的实用工具类。其方法使其更容易与某些 ABAP 类型进行交互。它们不仅完全无态,而且相当初级,看起来就像是 ABAP 语句或内置函数。辨别因素是,其调用 者会将它们紧紧捆绑到各自的代码中,从而真的没法在单元测试中对其进行模拟。
CLASS /clean/string_utils DEFINITION [...].
CLASS-METHODS trim
IMPORTING
string TYPE string
RETURNING
VALUE(result) TYPE string.
ENDCLASS.
METHOD retrieve.
DATA(trimmed_name) = /clean/string_utils=>trim( name ).
result = read( trimmed_name ).
ENDMETHOD.
组合优于继承
ABAP 整洁之道 > 目录 > 类 > 类:面向对象 > 本节
避免构建具有继承性的类层次结构,应该选择组合。
很难设计出完美的继承,因为需要遵守规则,如 Liskov substitution principle。另外,也很难理解,因为人们需要认识并领会层次结构背后的指导原则。继承会降低重用性,因为方法往往仅对子类才可用。它还会使重构复杂化,因为移动或更改成员往往需要对整个层次结构树进行更改。
组合意味着要设计小的独立对象,每个对象只服务于一个特定目的。通过简单的代理和外观模式,就可以将这些对象重新组合成更复杂的对象。组合可能会产生更多的类,但除此之外再无其他缺点。
莫因这条规则而丧失在恰当之处使用继承的信心。有一些应用场合很适合使用继承,例如,Composite design pattern。只需中肯地问问自己,在所处情况下,继承是否确实利大于弊。如有怀疑,一般来说,选择组合更稳妥。
Interfaces vs. abstract classes 对此做了一些详细比较。
勿在同一个类中混用有态和无态
ABAP 整洁之道 > 目录 > 类 > 类:面向对象
不要在同一个类中混用无态和有态编程范式。
在无态编程中,方法获取输入并产生输出,而不会有任何副作用,因此无论何时、以何顺序调用,方法都会产生相同的结果。
CLASS /clean/xml_converter DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS convert
IMPORTING
file_content TYPE xstring
RETURNING
VALUE(result) TYPE /clean/some_inbound_message.
ENDCLASS.
CLASS /clean/xml_converter IMPLEMENTATION.
METHOD convert.
cl_proxy_xml_transform=>xml_xstring_to_abap(
EXPORTING
xml = file_content
ext_xml = abap_true
svar_name = 'ROOT_NODE'
IMPORTING
abap_data = result ).
ENDMETHOD.
ENDCLASS.
在有态编程中,通过对象的方法操控其内部状态,这意味着_满是副作用_。
CLASS /clean/log DEFINITION PUBLIC CREATE PUBLIC.
PUBLIC SECTION.
METHODS add_message IMPORTING message TYPE /clean/message.
PRIVATE SECTION.
DATA messages TYPE /clean/message_table.
ENDCLASS.
CLASS /clean/log IMPLEMENTATION.
METHOD add_message.
INSERT message INTO TABLE messages.
ENDMETHOD.
ENDCLASS.
两种范式都不错,有各自的应用场合。然而,在同一个对象中_混用_会使代码难以理解,并且由于携带着的隐蔽错误以及同步性问题,注定会失败。切勿这样做。
作用域
ABAP 整洁之道 > 目录 > 类 > 本节
缺省情况下为全局,仅在适当位置为局部
ABAP 整洁之道 > 目录 > 类 > 作用域 > 本节
默认情况下运用全局类。只有在适当位置使用局部类。
全局类在数据字典中可见。局部类存在于另一个开发对象的 include 内,仅对这个另外的对象可见。
局部类适用
用于非常特定的私有数据结构,例如全局类数据的迭代器,仅此处需要这些数据结构,
用于提取复杂的私有部分算法,例如从其余类代码算法中提取出特殊用途的多方法的排序聚合算法,
用于模拟全局类的特定方面,例如,通过将所有数据库访问提取到可在单元测试中使用测试替身替换的单独局部类。
局部类将阻碍重用,因为它们无法在其他位置使用。尽管局部类易于提取,但人们通常甚至无法找到它们,从而导致不希望的代码重复。在极长的局部类中进行定向、导航和调试非常乏味且令人讨厌。由于 ABAP 锁是在包含文件级别上的,人们将无法同时在本地包含文件的不同部分上工作(只有在它们是不同的全局类的情况下,才能执行此操作)。
在以下情况下,重新考虑局部类的使用:
您的本地包含文件可以包含数十个类和数千行代码,
您将全局类视为包含其他类的“包”,
您的全局类退化为空壳,
您发现单独的本地包含文件中有重复代码,
您的开发人员开始互相锁定,无法并行工作,
由于您的团队无法理解彼此的本地子树,因此您的工作项估计会变得很多。
若非为继承而设计则为 FINAL
ABAP 整洁之道 > 目录 > 类 > 作用域 > 本节
将并非针对继承而明确设计的类构建为 FINAL。
在设计类的合作能力时,您的首选应该是组合而不是继承。实现继承不是一件容易的事,因为需要您考虑 PROTECTED 与 PRIVATE 等属性以及 Liskov substitution principle,并且冻结了许多设计内部功能。如果您在类设计中没有考虑这些问题,那么应该通过将类构建为 FINAL 来防止意外继承。
当然,继承_有_一些很好的应用程序,例如设计模式复合。通过允许使用子类,业务加载项也可以变得更加有用,客户能够重用大多数原始代码。但是,请注意,所有这些情况下,从一开始就通过设计内置了继承。
未实施接口的不整洁类应保持非 FINAL,这样使用者才能在单元测试中对其进行模拟。
缺省情况下为 PRIVATE,仅在需要时为 PROTECTED
ABAP 整洁之道 > 目录 > 类 > 作用域 > 本节
默认情况下,将属性、方法和其他类成员设置为 PRIVATE。
只有在您要启用子类覆盖它们时才将它们设置为 PROTECTED。
只有需要的情况下,才应让类的内部元素供其他成员或程序使用。这不仅包括外部调用者,还包括子类。信息过度可用可能会因意外重新定义而导致细微错误,并阻碍重构,因为外部调用将冻结原本应流动的成员。
考虑使用不可变对象而非 getter
ABAP 整洁之道 > 目录 > 类 > 作用域 > 本节
不可变对象是在构造后永不改变的对象。对于此类对象,请考虑使用公有只读属性而不是 getter 方法。
CLASS /clean/some_data_container DEFINITION.
PUBLIC SECTION.
METHODS constructor
IMPORTING
a TYPE i
b TYPE c
c TYPE d.
DATA a TYPE i READ-ONLY.
DATA b TYPE c READ-ONLY.
DATA c TYPE d READ-ONLY.
ENDCLASS.
而不是
CLASS /dirty/some_data_container DEFINITION.
PUBLIC SECTION.
METHODS get_a ...
METHODS get_b ...
METHODS get_c ...
PRIVATE SECTION.
DATA a TYPE i.
DATA b TYPE c.
DATA c TYPE d.
ENDCLASS.
警告:对于具有变化值的对象,请勿使用公有只读属性。否则,此属性必须始终保持最新状态,无论其他任何代码是否需要它们的值。
保守地使用 READ-ONLY
ABAP 整洁之道 > 目录 > 类 > 作用域 > 本节
许多现代编程语言(尤其是 Java)建议尽量将类成员设置为只读,以防止产生意外的不良影响。
尽管 ABAP _确实_为数据声明提供了 READ-ONLY 加载项,但我们建议您谨慎使用。
首先,仅在 PUBLIC SECTION 中提供了加载项,从而大大降低了其适用范围。您既不能将其添加到受保护的成员或私有成员,也不能将其添加到方法中的局部变量。
其次,加载项的执行结果与人们对其他编程语言的期望行为略有不同:仍然可以通过类本身、其友元类及其子类中的任何方法自由地修改 READ-ONLY 数据。这与其他语言中普遍采用的“一次写入,永远不会修改”行为相矛盾。这种差异可能会导致令人惊讶的意外。
为了避免误解:保护变量以防意外修改是一种很好的做法。如果是一个合适的语句,我们也建议将其应用于 ABAP中。
构造函数
ABAP 整洁之道 > 目录 > 类 > 本节
NEW 优于 CREATE OBJECT
ABAP 整洁之道 > 目录 > 类 > 构造函数 > 本节
DATA object TYPE REF TO /clean/some_number_range.
object = NEW #( '/CLEAN/CXTGEN' )
...
DATA(object) = NEW /clean/some_number_range( '/CLEAN/CXTGEN' ).
...
DATA(object) = CAST /clean/number_range( NEW /clean/some_number_range( '/CLEAN/CXTGEN' ) ).
而不是毫无必要地加长
" anti-pattern
DATA object TYPE REF TO /dirty/some_number_range.
CREATE OBJECT object
EXPORTING
number_range = '/DIRTY/CXTGEN'.
当然,除非需要动态类型
CREATE OBJECT number_range TYPE (dynamic_type)
EXPORTING
number_range = '/CLEAN/CXTGEN'.
如果全局类为 CREATE PRIVATE,,则保留 CONSTRUCTOR 为公有
ABAP 整洁之道 > 目录 > 类 > 构造函数 > 本节
CLASS /clean/some_api DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION.
METHODS constructor.
我们同意这是自相矛盾的。但根据文章 ABAP 帮助的 Instance Constructor,需要在 PUBLIC SECTION 中指定 CONSTRUCTOR 以确保正确的编译和语法验证。
这仅适用于全局类。在局部类中,应将构造函数设置为私有。
多个静态创建方法优于可选参数
。。。。。。。。
2020 GiHu
*&--------------------------------END------------------------------------*
【Origin Url】