ABAP 整洁之道


ABAP 整洁之道

本指南针对 ABAP 改编自 Robert C. Martin 所著的 Clean Code

目录

做法

ABAP 整洁之道 > 目录 > 本节

整洁代码入门之法

ABAP 整洁之道 > 目录 > 做法 > 本节
如果您初识整洁代码,应首先阅读 Robert C. Martin 所著的 Clean Code。借助 Clean Code Developer initiative,您可以从头学起,循序渐进地对该主题有一般性的了解。

建议从容易理解且广为接受的方面入手,如布尔值条件If 语句

您可能将会从方法一节获得最大受益,特别是做且仅做一件事,把它做好方法精简,因为这些会极大地改善代码的总体结构。

对于有行事经验但初识整洁代码的团队,本文的某些主题可能会引起团队内激烈的讨论;这些主题绝对“有益健康”,但人们可能刚开始不太适应。

后面会再继续探讨这些颇具争议的主题,特别是注释名称格式化,它们可能会引起孜孜不倦的争论,只有认识到整洁代码积极效应的团队才知道它的好处。

旧代码重构之法

ABAP 整洁之道 > 目录 > 做法 > 本节
如果正在遗留项目上工作,其中含有大量无法或不想更改的代码,因为它们可以无冲突地运行在新代码环境,这种情况下, 更改布尔值条件If 语句方法方面的主题最有价值。

对于遗留项目而言, 名称 主题改进太费劲了,它可能会在新旧代码之间产生差异,在某种程度上,其中的诸如避免编码,特别是匈牙利表示法和前缀等节忽略为宜。

我们发现采用四步计划进行重构,结果比较好:

  1. 先让团队上道。沟通并解释新的风格,使项目团队的每个人对此达成一致意见。不用一下子就推行所有指导原则,只需从小部分没有争议的子集入手,然后由此拓展。

  2. 按照_童子军规则_开展每日的例行工作:每次修改代码都比原先更整洁。不要因此而困扰,好几个小时沉湎于“清理整个营地”,只需花几分钟,思考如何持续不断地改进。

  3. 构筑_整洁小岛_:时不时挑选小的对象或组件,试着进行全方位的清洁。这些小岛印证了现在所做事情的好处,为进一步重构形成了经得起考验的坚强堡垒。

  4. 谈经论道。不管是设立老派的范根代码评审,还是举办宣讲会,抑或是在自己喜爱的聊天工具中组建讨论板:需要讲出自己的经验和体会,以使团队逐渐达成共识。

自动检查之法

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,除非它的确实施了工厂设计模式。最常见的模式包括:singletonfactoryfacadecompositedecoratoriteratorobserverstrategy

更多信息参阅 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.50NW 7.51NW 7.52NW 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 表用于表示索引开销大于索引受益的小表,以及或是毫不在乎行顺序或是就想完全按追加顺序进行处理的**“数组”**。另外,也适用于需要对表进行不同访问的情况,例如,通过 SORTBINARY SEARCH 进行索引访问和排序访问。

这些只是粗略的指导原则。> 更多细节参见 ABAP Language Help 中的篇章 Selection of Table Category

避免 DEFAULT KEY

ABAP 整洁之道 > 目录 > > 本节

" anti-pattern
DATA itab TYPE STANDARD TABLE OF row_type WITH DEFAULT KEY.

添加缺省键值常常只是为了让具有较新功能的语句得以正常工作。事实上,这些键值本身通常是多余的,除了耗费资源,别无它用。由于它们会忽略数值数据类型,因此甚至可能会导致隐蔽的错误。不含显式字段列表的 SORTDELETE 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_dxfeld。如果需要自定义描述,那就创建自己的数据元素。

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。等效函数 boolcboolx 会产生不同的类型并增加不必要的隐式类型转换。

我们同意名称 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

在设计类的合作能力时,您的首选应该是组合而不是继承。实现继承不是一件容易的事,因为需要您考虑 PROTECTEDPRIVATE 等属性以及 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 以确保正确的编译和语法验证。

这仅适用于全局类。在局部类中,应将构造函数设置为私有。

多个静态创建方法优于可选参数

ABAP 整洁之道 > 目录 > > 构造函数 > 本节

CLASS-METHODS describe_by_data IMPORTING data TYPE any [...]
CLASS-METHODS describe_by_name IMPORTING name TYPE any [...]
CLASS-METHODS describe_by_object_ref IMPORTING object_ref TYPE REF TO object [...]
CLASS-METHODS describe_by_data_ref IMPORTING data_ref TYPE REF TO data [...]

ABAP 不支持过载。使用名称变式而不是可选参数来实现所需的语义。

" anti-pattern
METHODS constructor
  IMPORTING
    data       TYPE any OPTIONAL
    name       TYPE any OPTIONAL
    object_ref TYPE REF TO object OPTIONAL
    data_ref   TYPE REF TO data OPTIONAL
  [...]

拆分方法而非添加 OPTIONAL 参数通用指南介绍了根本原因。

考虑使用构建器设计模式将复杂构造解析为多步构造。

用描述性名称表示多个创建方法

ABAP 整洁之道 > 目录 > > 构造函数 > 本节
用于创建方法的有效单词为 new_create_construct_。人们凭直觉就可以将它们与对象构造联系起来。还可以将这些单词连用构成动词短语,如 new_from_templatecreate_as_copycreate_by_name

CLASS-METHODS new_describe_by_data IMPORTING p_data TYPE any [...]
CLASS-METHODS new_describe_by_name IMPORTING p_name TYPE any [...]
CLASS-METHODS new_describe_by_object_ref IMPORTING p_object_ref TYPE REF TO object [...]
CLASS-METHODS new_describe_by_data_ref IMPORTING p_data_ref TYPE REF TO data [...]

而不是无意义的名称,例如

" anti-pattern
CLASS-METHODS create_1 IMPORTING p_data TYPE any [...]
CLASS-METHODS create_2 IMPORTING p_name TYPE any [...]
CLASS-METHODS create_3 IMPORTING p_object_ref TYPE REF TO object [...]
CLASS-METHODS create_4 IMPORTING p_data_ref TYPE REF TO data [...]

仅在多实例无意义的情况下变成单例

ABAP 整洁之道 > 目录 > > 构造函数 > 本节

METHOD new.
  IF singleton IS NOT BOUND.
    singleton = NEW /clean/my_class( ).
  ENDIF.
  result = singleton.
ENDMETHOD.

在您的面向对象的设计中提到第二个实例没有意义的情况下应用单例模式。该模式可确保每个使用者都以相同的状态和相同的数据处理相同的内容。

不要出于习惯或者因为某些性能规则的评分而使用单例模式。该模式最容易被过度使用和错误应用,这会产生意想不到的交叉影响,并为测试增加不必要的复杂性。如果单一对象没有设计驱动原因,可由使用者自己做决定,他仍然可以通过构造函数之外的方式(例如使用工厂)达到相同目的。

方法

ABAP 整洁之道 > 目录 > 本节
这些规则可应用于类和功能模块中的方法。

调用

ABAP 整洁之道 > 目录 > 方法 > 本节

函数式调用优于过程式调用

ABAP 整洁之道 > 目录 > 方法 > 调用 > 本节

modify->update( node           = /clean/my_bo_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields ).

而不是毫无必要地加长

" anti-pattern
CALL METHOD modify->update
  EXPORTING
    node           = /dirty/my_bo_c=>node-item
    key            = item->key
    data           = item
    changed_fields = changed_fields.

如果动态类型禁止函数调用,请使用过程式调用

CALL METHOD modify->(method_name)
  EXPORTING
    node           = /clean/my_bo_c=>node-item
    key            = item->key
    data           = item
    changed_fields = changed_fields.

以下许多详细规则只是此建议的更具体变式。

省略 RECEIVING

ABAP 整洁之道 > 目录 > 方法 > 调用 > 本节

DATA(sum) = aggregate_values( values ).

而不是毫无必要地加长

" anti-pattern
aggregate_values(
  EXPORTING
    values = values
  RECEIVING
    result = DATA(sum) ).

省略可选关键字 EXPORTING

ABAP 整洁之道 > 目录 > 方法 > 调用 > 本节

modify->update( node           = /clean/my_bo_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields ).

而不是毫无必要地加长

" anti-pattern
modify->update(
  EXPORTING
    node           = /dirty/my_bo_c=>node-item
    key            = item->key
    data           = item
    changed_fields = changed_fields ).

在单参数调用中省略参数名称

ABAP 整洁之道 > 目录 > 方法 > 调用 > 本节

DATA(unique_list) = remove_duplicates( list ).

而不是毫无必要地加长

" anti-pattern
DATA(unique_list) = remove_duplicates( list = list ).

但是,在某些情况下,仅方法名称还不够清楚,重复参数名称可能会更易于理解:

car->drive( speed = 50 ).
update( asynchronous = abap_true ).

在调用实例方法时省略自我引用 me

ABAP 整洁之道 > 目录 > 方法 > 调用 > 本节
由于自我引用 me-> 是由系统隐式设置的,因此可在调用实例方法时将其省略

DATA(sum) = aggregate_values( values ).

而不是毫无必要地加长

" anti-pattern
DATA(sum) = me->aggregate_values( values ).

方法:面向对象

ABAP 整洁之道 > 目录 > 方法 > 本节

实例优于静态方法

ABAP 整洁之道 > 目录 > 方法 > 方法:面向对象 > 本节
缺省情况下,方法应为实例成员。实例方法可以更好地反映类的“对象本质”。在单元测试中可以更轻松地模拟这些方法。

METHODS publish.

方法仅在特殊情况下才是静态的,例如静态创建方法。

CLASS-METHODS create_instance
  RETURNING
    VALUE(result) TYPE REF TO /clean/blog_post.

公共实例方法应为接口的一部分

ABAP 整洁之道 > 目录 > 方法 > 方法:面向对象 > 本节
公有实例方法应始终是接口的一部分。这样可以解耦相关性,并简化单元测试中的模拟过程。

METHOD /clean/blog_post~publish.

在面向整洁对象的方法中,公开没有接口的方法没有多大意义,枚举类等少数方法除外,这些方法永远不会有备选实施,也永远不会在测试用例中进行模拟。

接口与抽象类描述了为什么这也适用于覆盖继承方法的类。

参数数目

ABAP 整洁之道 > 目录 > 方法 > 本节

力图减少 IMPORTING 参数,最好少于三个

ABAP 整洁之道 > 目录 > 方法 > 参数数目 > 本节

FUNCTION seo_class_copy
  IMPORTING
    clskey      TYPE seoclskey
    new_clskey  TYPE seoclskey
    config      TYPE class_copy_config
  EXPORTING
    ...

整洁程度优于

" anti-pattern
FUNCTION seo_class_copy
  IMPORTING
    clskey                 TYPE seoclskey
    new_clskey             TYPE seoclskey
    access_permission      TYPE seox_boolean DEFAULT seox_true
    VALUE(save)            TYPE seox_boolean DEFAULT seox_true
    VALUE(suppress_corr)   TYPE seox_boolean DEFAULT seox_false
    VALUE(suppress_dialog) TYPE seox_boolean DEFAULT seox_false
    VALUE(authority_check) TYPE seox_boolean DEFAULT seox_true
    lifecycle_manager      TYPE REF TO if_adt_lifecycle_manager OPTIONAL
    lock_handle            TYPE REF TO if_adt_lock_handle OPTIONAL
    VALUE(suppress_commit) TYPE seox_boolean DEFAULT seox_false
  EXPORTING
    ...

输入参数过多会大幅增加方法的复杂性,因为方法需要处理指数级的组合。有多个参数就说明该方法做了超过一件事。

您可以通过结构和对象将参数组合为有意义的集合,从而减少参数的数目。

拆分方法而非添加 OPTIONAL 参数

ABAP 整洁之道 > 目录 > 方法 > 参数数目 > 本节

METHODS do_one_thing IMPORTING what_i_need TYPE string.
METHODS do_another_thing IMPORTING something_else TYPE i.

为了实现ABAP不支持但又需要的语义过载

" anti-pattern
METHODS do_one_or_the_other
  IMPORTING
    what_i_need    TYPE string OPTIONAL
    something_else TYPE i OPTIONAL.

可选参数使调用者感到困惑:

  • 真正需要哪些参数?
  • 哪些参数组合有效?
  • 哪些参数互相排斥?

按特定的参数拆分成多个方法可以给人明确的指引,从而避免了这种混淆。

保守地使用 PREFERRED PARAMETER

ABAP 整洁之道 > 目录 > 方法 > 参数数目 > 本节
额外定义PREFERRED PARAMETER 让人很难知道实际需要提供什么样的参数,理解代码也变得更困难。将参数数目减至最少,尤其是可选参数的数目,可以自动减少对 PREFERRED PARAMETER 的需求。

RETURN、EXPORT 或 CHANGE 恰有一个参数

ABAP 整洁之道 > 目录 > 方法 > 参数数目 > 本节
好的方法只做_一件事_,而这也应该反映在方法恰好只返回一个值。如果方法的输出参数相互之间_没有_联系,则说明您的方法做了多件事,则应该对其进行拆分。

在某些情况下,输出是由多件事组成的逻辑实体。通过返回结构或对象可以很容易表示:

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
  RETURNING
    VALUE(result)     TYPE check_result.

而不是

" anti-pattern
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.

特别是与多个 EXPORTING 参数相比,在该方法中人们可以使用函数式调用,您可以不考虑 IS SUPPLIED,并且避免出现人们意外忘记检索重要的 ERROR_OCCURRED 信息的情况。

不使用,而是考虑根据有意义的调用模式拆分方法去替代多个可选的输出参数:

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
  IMPORTING
    business_partners TYPE business_partners
  RETURNING
    VALUE(result)     TYPE result_type.
METHODS check_and_report
  IMPORTING
    business_partners TYPE business_partners
  RETURNING
    VALUE(result)     TYPE check_result.

参数类型

ABAP 整洁之道 > 目录 > 方法 > 本节

RETURNING 优于 EXPORTING

ABAP 整洁之道 > 目录 > 方法 > 参数类型 > 本节

METHODS square
  IMPORTING
    number        TYPE i
  RETURNING
    VALUE(result) TYPE i.
DATA(result) = square( 42 ).

而不是毫无必要地加长

" anti-pattern
METHODS square
  IMPORTING
    number TYPE i
  EXPORTING
    result TYPE i.
square(
  EXPORTING
    number = 42
  IMPORTING
    result = DATA(result) ).

RETURNING 不仅可以使调用更短,还允许使用方法链并防止相同输入和输出错误

RETURNING 大表通常没有问题

ABAP 整洁之道 > 目录 > 方法 > 参数类型 > 本节
尽管 ABAP 语言文档和性能指南有不一样的说法,但我们很少遇到在 VALUE 参数中传递大表或深度嵌套表_确实_导致性能问题的情况。因此,我们建议正常使用

METHODS get_large_table
  RETURNING
    VALUE(result) TYPE /clean/some_table_type.
METHOD get_large_table.
  result = me->large_table.
ENDMETHOD.
DATA(my_table) = get_large_table( ).

只有在您的个别情况有实际证据(= 不良的性能衡量)时,您才应该使用更繁琐的过程式调用

" anti-pattern
METHODS get_large_table
  EXPORTING
    result TYPE /dirty/some_table_type.
METHOD get_large_table.
  result = me->large_table.
ENDMETHOD.
get_large_table( IMPORTING result = DATA(my_table) ).

本节与 ABAP Programming Guidelines 和代码分析器检查相矛盾,二者都建议应通过引用导出大表,以避免性能下降。但我们始终未能重现任何性能下降和内存不足情况,也未收到有关内核优化(通常可提高 RETURNING 性能)的通知。

单独使用 RETURNING 或 EXPORTING 或 CHANGING,而不要组合使用

ABAP 整洁之道 > 目录 > 方法 > 参数类型 > 本节

METHODS copy_class
  IMPORTING
    old_name      TYPE seoclsname
    new name      TYPE secolsname
  RETURNING
    VALUE(result) TYPE copy_result
  RAISING
    /clean/class_copy_failure.

而不是混合使用,例如

" anti-pattern
METHODS copy_class
  ...
  RETURNING
    VALUE(result)      TYPE vseoclass
  EXPORTING
    error_occurred     TYPE abap_bool
  CHANGING
    correction_request TYPE trkorr
    package            TYPE devclass.

不同种类的输出参数表明该方法做了多件事。这使读者感到困惑,并使调用该方法变得不必要的复杂。

此规则的可接受的例外情况是使用他们的输入构建他们的输出:

METHODS build_tree
  CHANGING
    tokens        TYPE tokens
  RETURNING
    VALUE(result) TYPE REF TO tree.

然而,可以把输入对象化参数使这些内容更加清晰:

METHODS build_tree
  IMPORTING
    tokens        TYPE REF TO token_stack
  RETURNING
    VALUE(result) TYPE REF TO tree.

在合适时保守地使用 CHANGING

ABAP 整洁之道 > 目录 > 方法 > 参数类型 > 本节
CHANGING 应预留给以下情况:现有局部变量已填充仅在某些位置需要更新:

METHODS update_references
  IMPORTING
    new_reference TYPE /bobf/conf_key
  CHANGING
    bo_nodes      TYPE root_nodes.
METHOD update_references.
  LOOP AT bo_nodes REFERENCE INTO DATA(bo_node).
    bo_node->reference = new_reference.
  ENDLOOP.
ENDMETHOD.

不要强迫调用者仅仅为了提供 CHANGING 参数而引入不必要的局部变量。不要使用 CHANGING 参数来初始填充先前为空的变量。

拆分方法而非使用布尔输入参数

ABAP 整洁之道 > 目录 > 方法 > 参数类型 > 本节
布尔输入参数通常表示一个方法做_两_件事,而不是一件。

" anti-pattern
METHODS update
  IMPORTING
    do_save TYPE abap_bool.

同样,使用单个(因此未命名的)布尔参数的方法调用往往会混淆参数的含义。

" anti-pattern
update( abap_true ).  " what does 'true' mean? synchronous? simulate? commit?

拆分方法可以简化方法的代码并更好地描述不同的意图

update_without_saving( ).
update_and_save( ).

普遍认为,对布尔变量使用 setter 是可以的:

METHODS set_is_deleted
  IMPORTING
    new_value TYPE abap_bool.

有关详细信息,请参阅123

参数名称

ABAP 整洁之道 > 目录 > 方法 > 本节

考虑调用 RETURNING 参数 RESULT

ABAP 整洁之道 > 目录 > 方法 > 参数名称 > 本节
好的方法名称通常可以让RETURNING 参数不需要自己的名称这样的好效果。参数名只需要模仿方法名称或重复一些显而易见的内容。

重复成员名称甚至可能产生冲突,需要添加多余的 me-> 才能解决。

" anti-pattern
METHODS get_name
  RETURNING
    VALUE(name) TYPE string.
METHOD get_name.
  name = me->name.
ENDMETHOD.

在此类情况下,只需把参数命名为 RESULT,或者类似于 RV_RESULT 的名称(如果您喜欢用匈牙利表示法)。

例如在针对方法链返回 me 的方法中,或在创建某些对象但不返回创建的实体而仅返回其键值的方法中,如果参数代表的含义_不_明确,则需要为RETURNING参数起个名字。

参数初始化

ABAP 整洁之道 > 目录 > 方法 > 本节

清除或覆盖 EXPORTING 引用参数

ABAP 整洁之道 > 目录 > 方法 > 参数初始化 > 本节
引用参数是指可预先填充的现有内存区域。清除或覆盖它们以提供可靠的数据:

METHODS square
  EXPORTING
    result TYPE i.
" clear
METHOD square.
  CLEAR result.
  " ...
ENDMETHOD.
" overwrite
METHOD square.
  result = cl_abap_math=>square( 2 ).
ENDMETHOD.

代码分析器和检查管理器会指出从EXPORTING 变量未写入值。使用这些静态检查来避免这个可能相当模糊的错误源。

如果输入和输出可能相同则要当心

ABAP 整洁之道 > 目录 > 方法 > 参数初始化 > 本节
通常,在方法体里,类型和数据声明之后第一件事把参数清空是一个好主意。这使该语句易于辨别,并避免了后续语句意外使用原来的值。

但是,某些参数配置可能会使用相同的变量作为输入和输出。在这种情况下,之前的 CLEAR 语句会在使用输入值之前将其删除,从而产生错误的结果。

" anti-pattern
DATA value TYPE i.
square_dirty(
  EXPORTING
    number = value
  IMPORTING
    result = value ).
METHOD square_dirty.
  CLEAR result.
  result = number * number.
ENDMETHOD.

考虑用 RETURNING 替换 EXPORTING 来重新设计此类方法。还可以考虑在单个结果计算语句中覆盖 EXPORTING 参数。如果都不适合,只能稍后执行 CLEAR

勿清除 VALUE 参数

ABAP 整洁之道 > 目录 > 方法 > 参数初始化 > 本节
通过 VALUE 传递的参数会开辟新的独立内存,这些内存区域定义时为空。不要再清除这些区域:

METHODS square
  EXPORTING
    VALUE(result) TYPE i.
METHOD square.
  " no need to CLEAR result
ENDMETHOD.

RETURNING 参数始终是 VALUE 参数,因此您永远不需要清除这些参数:

METHODS square
  RETURNING
    VALUE(result) TYPE i.
METHOD square.
  " no need to CLEAR result
ENDMETHOD.

方法体

ABAP 整洁之道 > 目录 > 方法 > 本节

做且仅做一件事,把它做好

ABAP 整洁之道 > 目录 > 方法 > 方法体 > 本节
一个方法应该做一件事,且只能做一件事。应该采用最好的方法做这件事。

如果满足以下条件,一个方法可能做一件事:

关注愉快路径或错误处理,但非两者兼顾

ABAP 整洁之道 > 目录 > 方法 > 方法体 > 本节
由于做且仅做一件事,把它做好规则的专业化要求,方法应该遵循其建立的愉快路径,或在无法建立愉快路径的情况下采用其他错误处理方式,但也可能出现第三种情况。

" anti-pattern
METHOD append_xs.
  IF input > 0.
    DATA(remainder) = input.
    WHILE remainder > 0.
      result = result && `X`.
      remainder = remainder - 1.
    ENDWHILE.
  ELSEIF input = 0.
    RAISE EXCEPTION /dirty/sorry_cant_do( ).
  ELSE.
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
ENDMETHOD.

可以分解为

METHOD append_xs.
  validate( input ).
  DATA(remainder) = input.
  WHILE remainder > 0.
    result = result && `X`.
    remainder = remainder - 1.
  ENDWHILE.
ENDMETHOD.
METHOD validate.
  IF input = 0.
    RAISE EXCEPTION /dirty/sorry_cant_do( ).
  ELSEIF input < 0.
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
ENDMETHOD.

或者,强调验证部分

METHOD append_xs.
  IF input > 0.
    result = append_xs_without_check( input ).
  ELSEIF input = 0.
    RAISE EXCEPTION /dirty/sorry_cant_do( ).
  ELSE.
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
ENDMETHOD.
METHOD append_xs_without_check.
  DATA(remainder) = input.
  WHILE remainder > 0.
    result = result && `X`.
    remainder = remainder - 1.
  ENDWHILE.
ENDMETHOD.

将抽象降一级

ABAP 整洁之道 > 目录 > 方法 > 方法体 > 本节
方法中的语句应处于方法本身抽象级别的下一级。相应地,这些语句都应处于相同的抽象级别。

METHOD create_and_publish.
  post = create_post( user_input ).
  post->publish( ).
ENDMETHOD.

而不是混合使用低级(trimto_upper、…)和高级(publish、…)概念,例如

" anti-pattern
METHOD create_and_publish.
  post = NEW blog_post( ).
  DATA(user_name) = trim( to_upper( sy-uname ) ).
  post->set_author( user_name ).
  post->publish( ).
ENDMETHOD.

找出正确抽象级别的可靠方法是:让该方法的作者用简短的几个单词来解释该方法的功能,而不需要查看代码。他列的功能项就应该是方法应调用的子方法或应执行的语句。

保持方法精简

ABAP 整洁之道 > 目录 > 方法 > 方法体 > 本节
方法应少于 20 条语句,最好为 3 至 5 条语句。

METHOD read_and_parse_version_filters.
  DATA(active_model_version) = read_random_version_under( model_guid ).
  DATA(filter_json) = read_model_version_filters( active_model_version-guid ).
  result = parse_model_version_filters( filter_json ).
ENDMETHOD.

仅通过下面的 DATA 声明就足以看出相关的方法不止做一件事:

" anti-pattern
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,
  clsdeferrd      TYPE vseocdefer,
  intdeferrd      TYPE vseoidefer,
  new_clskey_save TYPE seoclskey.

当然,在某些情况下,进一步缩小较大的方法没有任何意义。这是完全可以的,只要该方法始终专注于一件事

METHOD decide_what_to_do.
  CASE temperature.
    WHEN burning.
      result = air_conditioning.
    WHEN hot.
      result = ice_cream.
    WHEN moderate.
      result = chill.
    WHEN cold.
      result = skiing.
    WHEN freezing.
      result = hot_cocoa.
  ENDCASE.
ENDMETHOD.

但是,验证冗长的代码是否隐藏了更合适的模式仍然有意义:

METHOD decide_what_to_do.
  result = VALUE #( spare_time_activities[ temperature = temperature ] OPTIONAL ).
ENDMETHOD.

将方法切割过小可能会对性能产生不良影响,因为这会增加方法调用的次数。_顾及性能_一节提供了有关如何平衡整洁代码和性能的指南。

控制流

ABAP 整洁之道 > 目录 > 方法 > 本节

快速失败

ABAP 整洁之道 > 目录 > 方法 > 控制流 > 本节
尽早验证并处理失败情景:

METHOD do_something.
  IF input IS INITIAL.
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
  DATA(massive_object) = build_expensive_object_from( input ).
  result = massive_object->do_some_fancy_calculation( ).
ENDMETHOD.

后面再进行验证更难以辨别和理解,并且可能已经浪费了很多资源。

" anti-pattern
METHOD do_something.
  DATA(massive_object) = build_expensive_object_from( input ).
  IF massive_object IS NOT BOUND. " happens if input is initial
    RAISE EXCEPTION cx_sy_illegal_argument( ).
  ENDIF.
  result = massive_object->do_some_fancy_calculation( ).
ENDMETHOD.

CHECK 对 RETURN

ABAP 整洁之道 > 目录 > 方法 > 控制流 > 本节
如果输入不符合预期,是否应使用 CHECKRETURN 退出方法,人们对此并未达成共识。

尽管 CHECK 显式提供了较短的语法,

METHOD read_customizing.
  CHECK keys IS NOT INITIAL.
  " do whatever needs doing
ENDMETHOD.

该语句的名称未能说明条件失败时会发生什么情况,因此长格式可能更易于人们理解:

METHOD read_customizing.
  IF keys IS INITIAL.
    RETURN.
  ENDIF.
  " do whatever needs doing
ENDMETHOD:

通过反向验证并采用单向控制流,完全可以避免此类问题

METHOD read_customizing.
  IF keys IS NOT INITIAL.
    " do whatever needs doing
  ENDIF.
ENDMETHOD:

无论如何,请考虑不返回任何内容是否真的合适。方法应该提供有意义的结果,即已填充的返回参数或异常。在许多情况下,不返回任何内容都类似于返回 null,应该避免这种情况。

ABAP Programming Guidelines 中的_退出过程_一节建议在此实例中使用 CHECK。社区讨论表明该语句如此不清晰,许多人无法理解程序的行为。

避免在其他位置使用 CHECK

ABAP 整洁之道 > 目录 > 方法 > 控制流 > 本节
不要在方法的初始化部分以外使用 CHECK。该语句在不同位置的行为方式不同,可能会造成不明确、意想不到的影响。

例如,LOOP 中的 CHECK 结束当前迭代并继续下一个;人们原本可能期望它结束方法或退出循环。

基于 ABAP Programming Guidelines 中的_退出过程_一节。请注意,这与循环中 CHECK 的关键字引用相矛盾。

错误处理

ABAP 整洁之道 > 目录 > 本节

消息

ABAP 整洁之道 > 目录 > 错误处理 > 本节

使消息易于查找

ABAP 整洁之道 > 目录 > 错误处理 > 消息 > 本节
要使通过事务 SE91 的使用位置搜索更容易找到消息,请使用以下模式:

MESSAGE e001(ad) INTO DATA(message).

如果不需要变量 message,请添加编译指示 ##NEEDED

MESSAGE e001(ad) INTO DATA(message) ##NEEDED.

避免使用以下模式:

" anti-pattern
IF 1 = 2. MESSAGE e001(ad). ENDIF.

这是一种反面模式,因为:

  • 其中包含不可达代码。
  • 其测试的条件永远不可能真正实现对等。

返回代码

ABAP 整洁之道 > 目录 > 错误处理 > 本节

异常优于返回代码

ABAP 整洁之道 > 目录 > 错误处理 > 返回代码 > 本节

METHOD try_this_and_that.
  RAISE EXCEPTION NEW cx_failed( ).
ENDMETHOD.

而不是

" anti-pattern
METHOD try_this_and_that.
  error_occurred = abap_true.
ENDMETHOD.

与返回代码相比,异常有许多优点:

  • 异常能保持方法签名的干净整洁:以 RETURNING 参数形式返回方法结果,同时仍抛出异常。返回代码使用其他参数进行错误处理,会污染您的签名。

  • 调用者不必立即做出反应,只需按愉快的路径写下代码。异常处理 CATCH 可在方法的末尾执行,也可以完全在外部执行。

  • 异常可以通过其属性和方法提供有关错误的详细信息。而返回代码则要求您自行设计另一个解决方案,例如还要返回日志。

  • 开发环境通过语法错误提醒调用者处理异常。而在没有任何人注意的情况下,返回代码可能会被不小心忽略。

别让故障溜走

ABAP 整洁之道 > 目录 > 错误处理 > 返回代码 > 本节
如果确实必须使用返回代码,例如因为调用了不在自己控制范围内的函数和较旧的代码,务必确保别让故障溜走。

DATA:
  current_date TYPE string,
  response     TYPE bapiret2.
CALL FUNCTION 'BAPI_GET_CURRENT_DATE'
  IMPORTING
    current_date = current_date
  CHANGING
    response     = response.
IF response-type = 'E'.
  RAISE EXCEPTION NEW /clean/some_error( ).
ENDIF.

异常

ABAP 整洁之道 > 目录 > 错误处理 > 本节

异常针对的是错误,而不是正常情况

ABAP 整洁之道 > 目录 > 错误处理 > 异常 > 本节

" anti-pattern
METHODS entry_exists_in_db
  IMPORTING
    key TYPE char10
  RAISING
    cx_not_found_exception.

如果情况是正常、有效的,应该使用常规的结果参数进行处理。

METHODS entry_exists_in_db
  IMPORTING
    key           TYPE char10
  RETURNING
    VALUE(result) TYPE abap_bool.

异常应该反映错误情况,留到您不希望看到的情况下使用。

METHODS assert_user_input_is_valid
  IMPORTING
    user_input TYPE string
  RAISING
    cx_bad_user_input.

误用异常会误导读者认为实际上一切都顺利的地方出了问题。异常也比正常代码慢得多,因为它们需要进行构建,往往要收集大量上下文信息。

使用基于类的异常

ABAP 整洁之道 > 目录 > 错误处理 > 异常 > 本节

TRY.
    get_component_types( ).
  CATCH cx_has_deep_components_error.
ENDTRY.

过时的非基于类的异常与返回代码具有相同的功能,不应再使用。

" anti-pattern
get_component_types(
  EXCEPTIONS
    has_deep_components = 1
    OTHERS              = 2 ).

抛出

ABAP 整洁之道 > 目录 > 错误处理 > 本节

使用各自的超类

ABAP 整洁之道 > 目录 > 错误处理 > 抛出 > 本节

CLASS cx_fra_static_check DEFINITION ABSTRACT INHERITING FROM cx_static_check.
CLASS cx_fra_no_check DEFINITION ABSTRACT INHERITING FROM cx_no_check.

考虑为应用程序的每种异常类型创建抽象超类,而不是直接对基础类进行子类化。允许您对_自己的_所有异常执行 CATCH。使您可以向所有异常添加通用功能,例如特殊文本处理。ABSTRACT 防止人们意外地直接使用这些非描述性错误。

抛出一种类型的异常

ABAP 整洁之道 > 目录 > 错误处理 > 抛出 > 本节

METHODS generate
  RAISING
    cx_generation_error.

在绝大多数情况下,抛出多种类型的异常没有任何用处。调用者通常既不感兴趣,也没法区分错误情况,因此经常会以相同的方式处理它们——既然如此,为什么一开始要区分它们呢?

" anti-pattern
METHODS generate
  RAISING
    cx_abap_generation
    cx_hdbr_access_error
    cx_model_read_error.

识别不同错误情况的更好解决方案是使用一种异常类型,但添加允许(但不要求)对个别错误情况做出反应的子类,如使用子类以便调用者能够区分错误情况中所述。

使用子类以便调用者能够区分错误情况

ABAP 整洁之道 > 目录 > 错误处理 > 抛出 > 本节

CLASS cx_bad_generation_variable DEFINITION INHERITING FROM cx_generation_error.
CLASS cx_bad_code_composer_template DEFINITION INHERITING FROM cx_generation_error.
TRY.
    generator->generate( ).
  CATCH cx_bad_generation_variable.
    log_failure( ).
  CATCH cx_bad_code_composer_template INTO DATA(bad_template_exception).
    show_error_to_user( bad_template_exception ).
  CATCH cx_generation_error INTO DATA(other_exception).
    RAISE EXCEPTION NEW cx_application_error( previous =  other_exception ).
ENDTRY.

如果有许多不同的错误情况,则改用错误代码:

CLASS cx_generation_error DEFINITION ...
  PUBLIC SECTION.
    TYPES error_code_type TYPE i.
    CONSTANTS:
      BEGIN OF error_code_enum,
        bad_generation_variable    TYPE error_code_type VALUE 1,
        bad_code_composer_template TYPE error_code_type VALUE 2,
        ...
      END OF error_code_enum.
    DATA error_code TYPE error_code_type.
TRY.
    generator->generate( ).
  CATCH cx_generation_error INTO DATA(exception).
    CASE exception->error_code.
      WHEN cx_generation_error=>error_code_enum-bad_generation_variable.
      WHEN cx_generation_error=>error_code_enum-bad_code_composer_variable.
      ...
    ENDCASE.
ENDTRY.

针对可应对的异常抛出 CX_STATIC_CHECK

ABAP 整洁之道 > 目录 > 错误处理 > 抛出 > 本节
如果预期会出现异常并可由接收者合理处理,则抛出继承自 CX_STATIC_CHECK 的可控异常:用户输入验证失败,缺少存在后备的资源,等等。

CLASS cx_file_not_found DEFINITION INHERITING FROM cx_static_check.
METHODS read_file
  IMPORTING
    file_name_enterd_by_user TYPE string
  RAISING
    cx_file_not_found.

这种异常类型_必须_在方法签名中给出,并且_必须_被捕获或转发以避免语法错误。这样,用户便能够清楚看到这种异常类型,确保其不会因意外的异常而感到惊讶,并负责对错误情况做出反应。

这与 ABAP Programming Guidelines 一致,但与 Robert C. Martin 所著的 Clean Code(其中建议优先使用不可控异常)相矛盾;异常说明了其中的原因。

针对通常不可恢复的情况抛出 CX_NO_CHECK

ABAP 整洁之道 > 目录 > 错误处理 > 抛出 > 本节
如果异常严重到使接收端不太可能恢复正常工作,则使用 CX_NO_CHECK:无法读取必备资源,无法解决请求的依赖项等。

CLASS cx_out_of_memory DEFINITION INHERITING FROM cx_no_check.
METHODS create_guid
  RETURNING
    VALUE(result) TYPE /bobf/conf_key.

CX_NO_CHECK _不能_在方法签名中声明,因此它的出现会使用户感到意外。在无法恢复的情况下,这是可以接受的,因为用户无论如何都无法采取有效的操作。

但是,在某些情况下,用户实际上_可能_希望识别并应对这种故障。例如,如果依赖的管理器无法为请求的接口提供一个实现,则会抛出 CX_NO_CHECK,因为常规应用程序代码将无法继续执行。但是,可能某个测试报告试图实例化所有事物,以查看其是否有效,并且会简单地将失败报告为列表中的红色条目——该服务应该能够捕获并忽略异常,而不是被强制转储。

针对可避免的异常考虑 CX_DYNAMIC_CHECK

ABAP 整洁之道 > 目录 > 错误处理 > 抛出 > 本节
CX_DYNAMIC_CHECK 的用例很少见,通常我们建议使用其他异常类型。但是,如果调用者对是否可能发生异常完全自主控制,您可能要考虑使用这种异常来代替 CX_STATIC_CHECK

DATA value TYPE decfloat.
value = '7.13'.
cl_abap_math=>get_db_length_decs(
  EXPORTING
    in     = value
  IMPORTING
    length = DATA(length) ).

例如,使用 cl_abap_math 类的 get_db_length_decs 方法,它告诉您十进制浮点数的位数和小数位数。如果输入参数未反映十进制浮点数,此方法会引发动态异常 cx_parameter_invalid_type。通常,将为完全静态类型的变量调用此方法,以便开发人员知道该异常是否会发生。在这种情况下,动态异常能够让调用者省略不必要的 CATCH 子句。

针对完全不可恢复的情况进行转储

ABAP 整洁之道 > 目录 > 错误处理 > 抛出 > 本节
如果情况严重到可以完全确定接收者不太可能从中恢复,或者清楚地表明了编程错误,请转储而不是抛出异常:获取内存失败,对必须填充的表读取索引失败等。

RAISE SHORTDUMP TYPE cx_sy_create_object_error.  " >= NW 7.53
MESSAGE x666(general).                           " < NW 7.53

这种行为将阻止任何类型的用户事后执行任何有用的操作。请仅在确定时使用此功能。

RAISE EXCEPTION NEW 优于 RAISE EXCEPTION TYPE

ABAP 整洁之道 > 目录 > 错误处理 > 抛出 > 本节
注:自 NW 7.52 起可用。

RAISE EXCEPTION NEW cx_generation_error( previous = exception ).

通常短于毫无必要加长的

RAISE EXCEPTION TYPE cx_generation_error
  EXPORTING
    previous = exception.

但是,如果大量添加 MESSAGE,可能需要坚持使用 TYPE 变式:

RAISE EXCEPTION TYPE cx_generation_error
  EXPORTING
    previous = exception
  MESSAGE e136(messages).

捕获

ABAP 整洁之道 > 目录 > 错误处理 > 本节

包裹外来异常而非任其侵入代码

ABAP 整洁之道 > 目录 > 错误处理 > 捕获 > 本节

METHODS generate RAISING cx_generation_failure.
METHOD generate.
  TRY.
      generator->generate( ).
    CATCH cx_amdp_generation_failure INTO DATA(exception).
      RAISE EXCEPTION NEW cx_generation_failure( previous = exception ).
  ENDTRY.
ENDMETHOD.

得墨忒耳律建议将事物解耦,而转发来自其他组件的异常是违反了这一原则的。通过捕获这些异常并将其封装在自己的异常类型中,使自己独立于外部代码。

" anti-pattern
METHODS generate RAISING cx_sy_gateway_failure.
METHOD generate.
  generator->generate( ).
ENDMETHOD.

注释

ABAP 整洁之道 > 目录 > 本节

用代码表达自己而不是靠注释

ABAP 整洁之道 > 目录 > 注释 > 本节

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.

而不是

" anti-pattern
" correct e.g. 29.02. in non-leap years as well as result of a date calculation would be
" something like e.g. the 31.06. that example has to be corrected to 30.06.
METHOD fix_day_overflow.
  DO 3 TIMES.
    " 31 - 28 = 3 => this correction is required not more than 3 times
    lv_dummy = cv_date.
    " lv_dummy is 0 if the date value is a not existing date - ABAP specific implementation
    IF ( lv_dummy EQ 0 ).
      cv_date+6(2) = cv_date+6(2) - 1. " subtract 1 day from the given date
    ELSE.
      " date exists => no correction required
      EXIT.
    ENDIF.
  ENDDO.
ENDMETHOD.

整洁代码_并不是_禁止您为代码写注释,而是鼓励您想出_更好的_替代方法。只有想不出替代方法时才使用注释。

从性能的角度来看,这个例子受到了质疑,因为将方法缩减到如此之短会严重降低性能。样本测量表明,在运行速度上,重构代码要比原始的脏代码慢 2.13 倍。整洁代码修复输入 31-02-2018 需要 9.6 微秒,而脏代码只需 4.5 微秒。当频繁在高性能应用程序中运行此方法时,可能会对性能造成影响;但对于常规用户输入验证,应该是可以接受的。请参阅顾及性能一节以处理整洁代码和性能问题。

注释绝非坏名称的借口

ABAP 整洁之道 > 目录 > 注释 > 本节

DATA(input_has_entries) = has_entries( input ).

与其解释那些坏名称的真正含义或者您选择坏名称的原因,不如去实际改良名称。

" anti-pattern
" checks whether the table input contains entries
DATA(result) = check_table( input ).

使用方法而非注释来对代码分段

ABAP 整洁之道 > 目录 > 注释 > 本节

DATA(statement) = build_statement( ).
DATA(data) = execute_statement( statement ).

这样不但能够更加清晰地体现代码的意图、结构和依赖关系,同时还能避免在块与块之间因临时变量未清空引起的错误。

" anti-pattern
" -----------------
" Build statement
" -----------------
DATA statement TYPE string.
statement = |SELECT * FROM d_document_roots|.
" -----------------
" Execute statement
" -----------------
DATA(result_set) = adbc->execute_sql_query( statement ).
result_set->next_package( IMPORTING data = data ).

写注释是要解释为什么而非是什么

ABAP 整洁之道 > 目录 > 注释 > 本节

" can't fail, existence of >= 1 row asserted above
DATA(first_line) = table[ 1 ].

没人需要用自然语言重复代码

" anti-pattern
" select alert root from database by key
SELECT * FROM d_alert_root WHERE key = key.

设计应放到设计文档里而不是代码里

ABAP 整洁之道 > 目录 > 注释 > 本节

" anti-pattern
" This class serves a double purpose. First, it does one thing. Then, it does another thing.
" It does so by executing a lot of code that is distributed over the local helper classes.
" To understand what's going on, let us at first ponder the nature of the universe as such.
" Have a look at this and that to get the details.

没人会认真读——真的。如果人们需要阅读教科书才能使用代码,这可能说明您的代码存在严重的设计问题,应通过其他方式解决。有些代码_确实_需要解释,而不仅仅是一行注释;在这种情况下,请考虑链接设计文档。

用 " 而非 * 加注释

ABAP 整洁之道 > 目录 > 注释 > 本节
加引号的注释及其注释语句一同缩进

METHOD do_it.
  IF input IS NOT INITIAL.
    " delegate pattern
    output = calculate_result( input ).
  ENDIF.
ENDMETHOD.

加星号的注释往往造成缩进异常

" anti-pattern
METHOD do_it.
  IF input IS NOT INITIAL.
* delegate pattern
    output = calculate_result( input ).
  ENDIF.
ENDMETHOD.

将注释放在与其相关的语句前面

ABAP 整洁之道 > 目录 > 注释 > 本节

" delegate pattern
output = calculate_result( input ).

整洁性强于

" anti-pattern
output = calculate_result( input ).
" delegate pattern

且唐突的程度低于

output = calculate_result( input ).  " delegate pattern

删除代码而非将其注释掉

ABAP 整洁之道 > 目录 > 注释 > 本节

" anti-pattern
* output = calculate_result( input ).

当您发现类似内容时,请将其删除。这里显然不需要代码,因为应用程序顺利运行并且所有测试都通过了。以后可根据版本历史记录再生删除的代码。如果需要永久保留某一段代码,请将其复制到文件或 $TMPHOME 对象中。

使用 FIXME、TODO 和 XXX 并添加自己的标识

ABAP 整洁之道 > 目录 > 注释 > 本节

METHOD do_something.
  " XXX FH delete this method - it does nothing
ENDMETHOD.
  • FIXME 指向内部事件正在形成的过小或过大的错误。
  • TODO 是您要在不久之后编写代码的地方。
  • XXX 标记出有效但还可以进一步优化的代码。

输入这类注释时,请添加昵称、姓名缩写或用户,这样可方便共同开发者与您联系并可以在不清楚注释的意图时向您询问问题。

勿添加方法签名和注释结尾

ABAP 整洁之道 > 目录 > 注释 > 本节
方法签名注释对任何人都没有帮助。

" anti-pattern
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Static Public Method CALIBRATION_KPIS=>CALCULATE_KPI
* +-------------------------------------------------------------------------------------------------+
* | [--->] STRATEGY_ID                 TYPE        STRATEGY_ID
* | [--->] THRESHOLD                   TYPE        STRATEGY_THRESHOLD
* | [--->] DETECTION_OBJECT_SCORE      TYPE        T_HIT_RESULT
* | [<---] KPI                         TYPE        T_SIMULATED_KPI
* +--------------------------------------------------------------------------------------</SIGNATURE>

几十年前,当在检查代码或处理数十页的打印输出内容时,如果看不到方法签名,这些注释可能会对您大有帮助。但现在,所有 ABAP IDE(SE24、SE80、ADT)都可以轻松显示方法签名,因此这些注释只是干扰而已。

在基于表单的编辑器 SE24/SE80 中,按_签名_按钮。在 ABAP 开发工具中,选中方法名称然后按 F2,或将 _ABAP 元素信息_视图添加到您的透视图中。
同样,注释结尾也是多余的。几十年前,当程序和函数以及内部嵌套的 IF 长度达到数百行代码时,这些注释可能很有用。但如今的编码风格发生了变化,方法非常简短,可以轻松看到 ENDIFENDMETHOD 属于哪个开头语句:

" anti-pattern
METHOD get_kpi_calc.
  IF has_entries = abap_false.
    result = 42.
  ENDIF.  " IF has_entries = abap_false
ENDMETHOD.   " get_kpi_calc

勿复制消息文本作为注释

ABAP 整洁之道 > 目录 > 注释 > 本节

" anti-pattern
" alert category not filled
MESSAGE e003 INTO dummy.

消息独立于您的代码而变化,没有人会记得调整注释,这样注释将很快过时甚至变得有误导性,却没有引起任何人注意。

在现代 IDE 中,您可以轻松查看消息背后的文本,例如在 ABAP 开发工具中,选中消息标识并按 Shift+F2。

如果您希望消息更准确,请考虑将消息提取到其自身的方法。

METHOD create_alert_not_found_message.
  MESSAGE e003 INTO dummy.
ENDMETHOD.

ABAP 文档仅适用于公共 API

ABAP 整洁之道 > 目录 > 注释 > 本节
编写 ABAP 文档来记录公共 API,这意味着这些 API 可供其他团队或应用程序的开发人员使用。不要为内部内容编写 ABAP 文档。

ABAP 文档与所有注释一样都有相同的弱点,也就是说,它很快会过时,然后会变得有误导性。因此,您应该只在有意义的情况下使用,而不要为一切内容强制编写 ABAP 文档。

更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 4: Good Comments: Javadocs in Public APIsChapter 4: Bad Comments: Javadocs in Nonpublic Code

编译指示优于伪注释

ABAP 整洁之道 > 目录 > 注释 > 本节
优先使用编译指示而不是伪注释来抑制 ATC 识别的无关警告和错误。伪注释大部分已过时,并已替换为编译指示。

" pattern
MESSAGE e001(ad) INTO DATA(message) ##NEEDED.
" anti-pattern
MESSAGE e001(ad) INTO DATA(message). "#EC NEEDED

使用 ABAP_SLIN_PRAGMAS 程序或 SLIN_DESC 表查找过时伪注释与已替换这些伪注释的编译指示之间的映射。

格式化

ABAP 整洁之道 > 目录 > 本节
下面的建议为阅读而不是书写进行优化。由于 ABAP 的格式优化器没有涵盖它们,其中的部分建议会产生额外的人工工作,以在名称长度等发生变化时重新格式化语句;如果要避免这种情况,请考虑放弃这些规则,例如对齐同一对象而非不同对象的赋值

保持一致

ABAP 整洁之道 > 目录 > 本节
以相同的方式格式化项目的所有代码。让所有团队成员使用相同的格式化风格。

如果要编辑外来代码,请遵循该项目的格式化风格,而不要坚持自己的个人风格。

如果要随时间更改格式化规则,请使用重构最佳实践随时间更新代码。

为阅读而不是书写进行优化

ABAP 整洁之道 > 目录 > 格式化 > 本节
开发人员花费大量时间_阅读_代码。实际上,一天中_编写_代码所占的比例要小得多。

因此,应针对读取和调试(而非编写)来优化代码格式。

例如,应该优先采用

DATA:
  a TYPE b,
  c TYPE d,
  e TYPE f.

而不是

" anti-pattern
DATA:
  a TYPE b
  ,c TYPE d
  ,e TYPE f.

激活前使用格式优化器

ABAP 整洁之道 > 目录 > 格式化 > 本节
在激活对象前应用格式优化器——SE80、SE24 和 ADT 中的 Shift+F1。

如果修改一个大型未格式化旧代码库,可能需要仅对选定行应用格式优化器,以避免产生大量的变更项和传输依赖项。请考虑在单独的传输请求或注释中整齐打印完整的开发对象。

更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 5: Formatting: Team Rules

使用格式优化器团队设置

ABAP 整洁之道 > 目录 > 格式化 > 本节
始终使用团队设置。在_菜单_ > 实用程序 > 设置 … > ABAP 编辑器 > _格式优化器_下进行指定。

按照团队的协商设置_缩进_和_转换大写/小写_ > 大写关键字

大写字母与小写字母解释了为什么我们没有为关键字的大小写提供明确的指导。
更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 5: Formatting: Team Rules

每行只有一条语句

ABAP 整洁之道 > 目录 > 格式化 > 本节

DATA do_this TYPE i.
do_this = input + 3.

即使某些情况可能使您误以为这是可读的:

" anti-pattern
DATA do_this TYPE i. do_this = input + 3.

恪守合理的行长度

ABAP 整洁之道 > 目录 > 格式化 > 本节
遵守最多 120 个字符的行长度。

如果行距不是很宽,人眼可以更舒适地阅读文字——请在UI 设计师或眼动研究人员建议下做出您的选择。在调试或比较相邻的两行源代码时,如果代码行更窄一些,您会心生感激之情的。

老的终端设备的 80 个甚至 72 个字符的限制太过严格了。虽然通常建议使用 100 个字符(这是一个可行的选择),但对于 ABAP 来说,最好使用 120 个字符,这可能是因为这种语言一般较为冗长。

提醒一下,您可以在 ADT 中将打印边距配置为 120 个字符,然后在代码视图中将其显示为垂直线。在_菜单_ > 窗口 > 首选项 > 常规 > 编辑器 > _文本编辑器_下进行配置。

紧缩代码

ABAP 整洁之道 > 目录 > 格式化 > 本节

DATA(result) = calculate( items ).

而不是添加不必要的空格

" anti-pattern
DATA(result)        =      calculate(    items =   items )   .

添加单一空行来分隔内容,而不要添加多行

ABAP 整洁之道 > 目录 > 格式化 > 本节

DATA(result) = do_something( ).
DATA(else) = calculate_this( result ).

强调这两条语句做的是不同的事情。但没必要

" anti-pattern
DATA(result) = do_something( ).
DATA(else) = calculate_this( result ).

添加分隔空行可能表明您的方法没有在做一件事

勿因分隔空行产生困扰

ABAP 整洁之道 > 目录 > 格式化 > 本节

METHOD do_something.
  do_this( ).
  then_that( ).
ENDMETHOD.

没有理由养成用空行将代码分开的坏习惯

" anti-pattern
METHOD do_something.
  do_this( ).
  then_that( ).
ENDMETHOD.

空行实际上仅在您有跨越多行的语句时才有意义

METHOD do_something.
  do_this( ).
  then_that(
    EXPORTING
      variable = 'A'
    IMPORTING
      result   = result ).
ENDMETHOD.

对齐同一对象而非不同对象的赋值

ABAP 整洁之道 > 目录 > 格式化 > 本节
为了强调这些事物在某种程度上是属于一起的

structure-type = 'A'.
structure-id   = '4711'.

或者这样更好

structure = VALUE #( type = 'A'
                     id   = '4711' ).

而对于那些彼此无关的事物,仍保留参差不齐的状态:

customizing_reader = fra_cust_obj_model_reader=>s_get_instance( ).
hdb_access = fra_hdbr_access=>s_get_instance( ).

更多信息参阅 Robert C. Martin 所著的 Clean Code 中的 Chapter 5: Formatting: Horizontal Alignment

在行尾关闭括号

ABAP 整洁之道 > 目录 > 格式化 > 本节

modify->update( node           = if_fra_alert_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields ).

而不是毫无必要地加长

" anti-pattern
modify->update( node           = if_fra_alert_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields
).

保持单参数调用于一行

ABAP 整洁之道 > 目录 > 格式化 > 本节

DATA(unique_list) = remove_duplicates( list ).
remove_duplicates( CHANGING list = list ).

而不是毫无必要地加长

" anti-pattern
DATA(unique_list) = remove_duplicates(
                           list ).
DATA(unique_list) = remove_duplicates(
                         CHANGING
                           list = list ).

保持参数在调用后面

ABAP 整洁之道 > 目录 > 格式化 > 本节

DATA(sum) = add_two_numbers( value_1 = 5
                             value_2 = 6 ).

如果这造成行很长,可以换行,将参数转到下一行:

DATA(sum) = add_two_numbers(
                   value_1 = round_up( input DIV 7 ) * 42 + round_down( 19 * step_size )
                   value_2 = VALUE #( ( `Calculation failed with a very weird result` ) ) ).

如果换行,则在调用下缩进参数

ABAP 整洁之道 > 目录 > 格式化 > 本节

DATA(sum) = add_two_numbers(
                   value_1 = 5
                   value_2 = 6 ).

在其他地方对齐参数将导致很难发现它们所属的对象:

DATA(sum) = add_two_numbers(
    value_1 = 5
    value_2 = 6 ).

但是,如果要避免因名称长度更改而破坏格式,这就是最佳模式了。

将多个参数换行

ABAP 整洁之道 > 目录 > 格式化 > 本节

DATA(sum) = add_two_numbers( value_1 = 5
                             value_2 = 6 ).

是的,这浪费了空间。但是如果不这样,就很难确定一个参数在哪里结束而下一个在哪里开始:

" anti-pattern
DATA(sum) = add_two_numbers( value_1 = 5 value_2 = 6 ).

对齐参数

ABAP 整洁之道 > 目录 > 格式化 > 本节

modify->update( node           = if_fra_alert_c=>node-item
                key            = item->key
                data           = item
                changed_fields = changed_fields ).

边距参差不齐,使得很难看到参数的结束位置以及参数值的开始位置:

" anti-pattern
modify->update( node = if_fra_alert_c=>node-item
                key = item->key
                data = item
                changed_fields = changed_fields ).

如果要避免因名称长度更改而破坏格式,这就是另一种最佳模式了。

如果调用行过长则将其换行

ABAP 整洁之道 > 目录 > 格式化 > 本节

DATA(some_super_long_param_name) =
  if_some_annoying_interface~add_two_numbers_in_a_long_name(
      value_1 = 5
      value_2 = 6 ).

缩进并卡到制表位

ABAP 整洁之道 > 目录 > 格式化 > 本节
将参数关键字缩进 2 个空格,并将参数缩进 4 个空格:

DATA(sum) = add_two_numbers(
              EXPORTING
                value_1 = 5
                value_2 = 6
              CHANGING
                errors  = errors ).

如果没有关键字,则将参数缩进 4 个空格。

DATA(sum) = add_two_numbers(
                value_1 = 5
                value_2 = 6 ).

使用 Tab 键缩进。就算多加了一个空格也没有关系。(如果左侧 DATA(sum) = 部分的字符数非偶数,则会发生这种情况。)

如同方法调用那样缩进内联声明

ABAP 整洁之道 > 目录 > 格式化 > 本节
按照与方法调用相同的方式,使用 VALUE 或 NEW 缩进内联声明:

DATA(result) = merge_structures( a = VALUE #( field_1 = 'X'
                                              field_2 = 'A' )
                                 b = NEW /clean/structure_type( field_3 = 'C'
                                                                field_4 = 'D' ) ).

勿对齐类型子句

ABAP 整洁之道 > 目录 > 格式化 > 本节

DATA name TYPE seoclsname.
DATA reader TYPE REF TO /clean/reader.

变量及其类型属于同一类,因此从视觉上来说应将其分为一组。将 TYPE 子句对齐会使人们的分心,并建议这些变量形成一个垂直组,而它们的类型形成一个垂直组。对齐还会产生不必要的编辑开销,当最长变量名的长度发生变化时,需要调整所有的缩进。

" anti-pattern
DATA name   TYPE seoclsname.
DATA reader TYPE REF TO /clean/reader.

测试

ABAP 整洁之道 > 目录 > 本节

原则

ABAP 整洁之道 > 目录 > 测试 > 本节

编写可测试的代码

ABAP 整洁之道 > 目录 > 测试 > 原则 > 本节
编写的所有代码应该允许您以自动方式测试。

如果需要重构您的代码,那就重构。并在开始添加其他功能之前重构。

如果添加到的旧代码结构过于混乱而无法测试,那么至少要对其进行重构到您新添加部分能够测试。

让他人能够进行模拟

ABAP 整洁之道 > 目录 > 测试 > 原则 > 本节
如果要编写供其他人使用的代码,请让他们能够为自己的代码编写单元测试,例如通过在所有对外交互的位置添加接口,提供有助于促进集成测试的测试替身,或应用依赖倒置使他们能够用测试配置替代生产配置。

可读性规则

ABAP 整洁之道 > 目录 > 测试 > 原则 > 本节
让您的测试代码比生产代码更具可读性。您可以通过有效的测试来处理糟糕的生产代码,但是,可能您甚至还未进行测试,就已经把自己弄懵了。

保持您的测试代码保持简单、傻瓜,这样您一年后仍然能够理解它。

遵守标准和模式,使您的同事能够快速了解代码。

勿制作副本或写测试报告

ABAP 整洁之道 > 目录 > 测试 > 原则 > 本节
不要通过制作一个开发对象的 $TMP 副本并反复研究来处理开发项。其他人不会注意到这些对象,因此不会知道您的工作状态。您可能会在一开始就浪费大量的时间来制作工作副本,之后您也可能会忘记删除副本,这些副本对于系统和依赖项来说是没用的垃圾。(不相信?立刻看看开发系统并检查一下 $TMP。)

另外,不要一开始就编写以特定方式调用某些内容的测试报告,并重复此操作以验证您在运行代码时是否一切正常。这是糟糕的手动测试:手动重复测试报告,目视验证一切是否正常。往前迈一步,在单元测试中自动执行此报告,使用自动断言告诉您代码是否仍然正常运行。这样的好处有,,您不需要进行之后必须编写单元测试的工作。其次,您可以节省进行手动重复所用的大量时间,另外还可以避免感到无聊和疲劳。

测试公共项而非私有内部项

ABAP 整洁之道 > 目录 > 测试 > 原则 > 本节
类的公共项部分,尤其是它们实现的接口,相当稳定并且不太可能发生更改。让您的单元测试仅验证公共项,使其稳定可靠,并最大程度地减少重构类时所需的工作量。相比之下,受保护的和私有的内部项可能会通过重构而快速变化,这样,每次重构都会不必要地中断您的测试。

测试私有方法或受保护方法的迫切需求可能是几个早期设计缺陷的警告信号。问问您自己以下问题:

  • 您是否意外地在您的类中埋没了一个概念,这个概念本该公开到其自己的类并使用其专用测试套件?

  • 您是否忘记将域逻辑与粘合代码分开?例如,直接在类中实施作为操作、确定或验证插入到 BOPF 的域逻辑,或者由 SAP Gateway 作为 *_DPC_EXT 数据提供者生成的域逻辑,可能不是上策。

  • 接口是不是太过复杂、请求过多无关的数据或者无法轻松进行模拟?

勿困扰于覆盖范围

ABAP 整洁之道 > 目录 > 测试 > 原则 > 本节
代码覆盖范围可以帮助您找到忘记测试的代码,而不是满足某些随机的 KPI:

不要仅为了达到覆盖范围而在包含或不含虚拟断言的情况下编写测试。最好保留未经测试的内容,以表明您不能安全地进行重构。您可以拥有小于 100% 的覆盖范围,并且仍然可以进行完美的测试。在某些情况下,例如在构造函数中使用 IF 插入测试替身时,可能无法达到 100%。好的测试往往会针对不同的分支和条件多次覆盖同一条语句。实际上,这些测试的假想覆盖率大于 100%。

测试类

ABAP 整洁之道 > 目录 > 测试 > 本节

按用途调用局部测试类

ABAP 整洁之道 > 目录 > 测试 > 测试类 > 本节

CLASS ltc_unit_tests DEFINITION FOR TESTING ... .
CLASS ltc_integration_tests DEFINITION FOR TESTING ... .
CLASS ltc_unit_tests_with_mocks DEFINITION FOR TESTING ... .

良好的名称可以揭示测试的级别以及公有设置部分。

" anti-patterns
CLASS ltc_fra_online_detection_api DEFINITION FOR TESTING ... . " We know that's the class under test - why repeat it?
CLASS ltc_test DEFINITION FOR TESTING ....                      " Of course it's a test, what else should it be?

将测试放在局部类

ABAP 整洁之道 > 目录 > 测试 > 测试类 > 本节
将单元测试放入被测类的局部测试包含文件中。这样可以确保人们在重构该类时能够找到这些测试,并允许他们通过按一次按键运行所有相关的测试,如如何执行测试类中所述。

将组件测试、集成测试和系统测试放入包含单独全局类的局部测试中。它们与被测的单个类没有直接关系,因此不应随意将它们放在某个相关类中,而是应放在一个单独的类中。将此全局测试类标记为 FOR TESTINGABSTRACT,以避免在生产代码中意外地引用该类。将测试放到其他类中存在这样的危险,即人们在重构所涉及的类时忽略并忘记运行这些测试。

因此,使用测试关系来记录已测试的对象是有好处的。在下面的例子中,可以在类 recrutingcandidate 中或通过快捷键 Shift-Crtl-F12 (Windows) 或 Cmd-Shift-F12 (macOS) 执行测试类 hiring_test

"! @testing recruting
"! @testing candidate
class hiring_test definition
  for testing risk level dangerous duration medium
  abstract.
  ...
endclass.

将帮助方法放在帮助类

ABAP 整洁之道 > 目录 > 测试 > 测试类 > 本节
将若干测试类使用的帮助方法放在帮助类中。通过继承(关系)或委托(具有关系)使帮助方法可用。

" inheritance example
CLASS lth_unit_tests DEFINITION ABSTRACT FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.
  PROTECTED SECTION.
    CLASS-METHODS assert_activity_entity
      IMPORTING
        actual_activity_entity TYPE REF TO zcl_activity_entity
        expected_activity_entity TYPE REF TO zcl_activity_entity.
    ...
ENDCLASS.
CLASS lth_unit_tests IMPLEMENTATION.
  METHOD assert_activity_entity.
    ...
  ENDMETHOD.
ENDCLASS.
CLASS ltc_unit_tests DEFINITION INHERITING FROM lth_unit_tests FINAL FOR TESTING
  DURATION SHORT
  RISK LEVEL HARMLESS.
  ...
ENDCLASS.

如何执行测试类

ABAP 整洁之道 > 目录 > 测试 > 测试类 > 本节
在 ABAP 开发工具中,按 Ctrl+Shift+F10 运行某个类中的所有测试。按 Ctrl+Shift+F11 以包括覆盖范围测量。按 Ctrl+Shift+F12 还可运行作为测试关系维护的其他类中的测试。

在 macOS 中,使用 Cmd 而不是 Ctrl

被测代码

ABAP 整洁之道 > 目录 > 测试 > 本节

赋予被测代码有意义的名称,或使用缺省名称 CUT

ABAP 整洁之道 > 目录 > 测试 > 被测代码 > 本节
为被测代码的变量提供一个有意义的名称:

DATA blog_post TYPE REF TO ...

不要只使用类名称的所有无价值的命名空间和前缀来重复类名称:

" anti-pattern
DATA clean_fra_blog_post TYPE REF TO ...

如果您具有不同的测试设置,且对描述对象的变化状态可能会有所帮助:

DATA empty_blog_post TYPE REF TO ...
DATA simple_blog_post TYPE REF TO ...
DATA very_long_blog_post TYPE REF TO ...

如果想不到起什么名称有意义,请使用缺省名称 cut。该缩写代表“code under test”。

DATA cut TYPE REF TO ...

特别是在不整洁且令人困惑的测试中,调用变量 cut 可以暂时帮助读者查看实际测试的内容。然而,从长远来看,整理测试才是真正有效的方法。

测试接口而非类

ABAP 整洁之道 > 目录 > 测试 > 被测代码 > 本节
测试公共项而非私有内部项的实际结果是,使用_接口_输入您的被测代码

DATA code_under_test TYPE REF TO some_interface.

而不是_类_

" anti-pattern
DATA code_under_test TYPE REF TO some_class.

将被测代码的调用提取到自身的方法

ABAP 整洁之道 > 目录 > 测试 > 被测代码 > 本节
如果要测试的方法需要大量参数或准备好的数据,有必要将对它的调用提取到它自己的帮助方法中,该方法预设了不那么重要的参数:

METHODS map_xml_to_itab
  IMPORTING
    xml_string TYPE string
    config     TYPE /clean/xml2itab_config DEFAULT default_config
    format     TYPE /clean/xml2itab_format DEFAULT default_format.
METHOD map_xml_to_itab.
  result = cut->map_xml_to_itab( xml_string = xml_string
                                 config     = config
                                 format     = format ).
ENDMETHOD.
DATA(itab) = map_xml_to_itab( '<xml></xml>' ).

直接调用原始方法会让您的测试陷入很多无意义的细枝末节:

" anti-pattern
DATA(itab) = cut->map_xml_to_itab( xml_string = '<xml></xml>'
                                   config     = VALUE #( 'some meaningless stuff' )
                                   format     = VALUE #( 'more meaningless stuff' ) ).

注入

ABAP 整洁之道 > 目录 > 测试 > 本节

使用依赖倒置注入测试替身

ABAP 整洁之道 > 目录 > 测试 > 注入 > 本节
依赖倒置意味着您将所有依赖项传递给构造函数:

METHODS constructor
  IMPORTING
    customizing_reader TYPE REF TO if_fra_cust_obj_model_reader.
METHOD constructor.
  me->customizing_reader = customizing_reader.
ENDMETHOD.

不要使用 setter 注入,这样做会以非预期方式使用生产代码:

" anti-pattern
METHODS set_customizing_reader
  IMPORTING
    customizing_reader TYPE REF TO if_fra_cust_obj_model_reader.
METHOD do_something.
  object->set_customizing_reader( a ).
  object->set_customizing_reader( b ). " would you expect that somebody does this?
ENDMETHOD.

不要使用 FRIENDS 注入,这会在替换生产依赖项之前对其进行初始化,产生意想不到的后果。当您重命名内部项后,它将立即中断。它还会绕过构造函数中的初始化。

" anti-pattern
METHOD setup.
  cut = NEW fra_my_class( ). " <- builds a productive customizing_reader first - what will it break with that?
  cut->customizing_reader ?= cl_abap_testdouble=>create( 'if_fra_cust_obj_model_reader' ).
ENDMETHOD.
METHOD constructor.
  customizing_reader = fra_cust_obj_model_reader=>s_get_instance( ).
  customizing_reader->fill_buffer( ). " <- won't be called on your test double, so no chance to test this
ENDMETHOD.

考虑使用 ABAP 测试替身工具

ABAP 整洁之道 > 目录 > 测试 > 注入 > 本节

DATA(customizing_reader) = CAST /clean/customizing_reader( cl_abap_testdouble=>create( '/clean/default_custom_reader' ) ).
cl_abap_testdouble=>configure_call( customizing_reader )->returning( sub_claim_customizing ).
customizing_reader->read( 'SOME_ID' ).

与自定义测试替身相比,更短、更容易理解:

" anti-pattern
CLASS /dirty/default_custom_reader DEFINITION FOR TESTING CREATE PUBLIC.
  PUBLIC SECTION.
    INTERFACES /dirty/customizing_reader.
    DATA customizing TYPE /dirty/customizing_table.
ENDCLASS.
CLASS /dirty/default_custom_reader IMPLEMENTATION.
  METHOD /dirty/customizing_reader~read.
    result = customizing.
  ENDMETHOD.
ENDCLASS.
METHOD test_something.
  DATA(customizing_reader) = NEW /dirty/customizing_reader( ).
  customizing_reader->customizing = sub_claim_customizing.
ENDMETHOD.

利用测试工具

ABAP 整洁之道 > 目录 > 测试 > 注入 > 本节
一般来说,整洁的编程风格可以让您使用标准 ABAP 单元测试和测试替身完成大部分工作。但是,有些工具可以让您以优雅的方式处理更棘手的情况:

  • 使用 CL_OSQL_REPLACE 服务测试复杂的 OpenSQL 语句,方法是将这些语句重定向到可在不影响系统其余部分的情况下填充测试数据的测试数据仓。

  • 使用 CDS 测试框架来测试您的 CDS 视图。

使用测试缝隙作为临时解决办法

ABAP 整洁之道 > 目录 > 测试 > 注入 > 本节
如果所有其他技术都失败了,或者处于旧代码可能无法正常运行的情况下,应避免使用测试缝隙来增加可测试性。

尽管第一眼看上去很舒服,但测试接缝是侵入性的,而且容易与私有依赖项纠缠在一起,从长远来看,它们很难保持活性和稳定性。

因此,我们建议避免仅将测试接缝作为一种临时解决方法以将代码重构为更易于测试的形式。

使用 LOCAL FRIENDS 访问依赖倒置的构造函数

ABAP 整洁之道 > 目录 > 测试 > 注入 > 本节

CLASS /clean/unit_tests DEFINITION.
  PRIVATE SECTION.
    DATA cut TYPE REF TO /clean/interface_under_test.
    METHODS setup.
ENDCLASS.
CLASS /clean/class_under_test DEFINITION LOCAL FRIENDS unit_tests.
CLASS unit_tests IMPLEMENTATION.
  METHOD setup.
    DATA(mock) = cl_abap_testdouble=>create( '/clean/some_mock' ).
    " /clean/class_under_test is CREATE PRIVATE
     " so this only works because of the LOCAL FRIENDS
    cut = NEW /clean/class_under_test( mock ).
  ENDMETHOD.
ENDCLASS.

勿滥用 LOCAL FRIENDS 侵入被测代码

ABAP 整洁之道 > 目录 > 测试 > 注入 > 本节
访问私有成员和受保护成员以插入模拟数据的单元测试很脆弱:当测试代码的内部结构发生变化时,它们会中断。

" anti-pattern
CLASS /dirty/class_under_test DEFINITION LOCAL FRIENDS unit_tests.
CLASS unit_tests IMPLEMENTATION.
  METHOD returns_right_result.
    cut->some_private_member = 'AUNIT_DUMMY'.
  ENDMETHOD.
ENDCLASS.

勿更改生产代码来使代码可测试

ABAP 整洁之道 > 目录 > 测试 > 注入 > 本节

" anti-pattern
IF me->in_test_mode = abap_true.

勿子类化来模拟方法

ABAP 整洁之道 > 目录 > 测试 > 注入 > 本节
不要在单元测试中通过子类化和覆盖方法来模拟方法。尽管这是可行的,但这样的方法很脆弱,在重构代码时测试很容易中断。而且真正的使用者还可能继承您的类,如果没有明确设计此功能,这可能会令您措手不及

" anti-pattern
CLASS unit_tests DEFINITION INHERITING FROM /dirty/real_class FOR TESTING [...].
  PROTECTED SECTION.
    METHODS needs_to_be_mocked REDEFINITION.

要获取旧被测代码,请改用测试接缝。测试接缝同样脆弱,但至少不会改变类的生产行为(如通过删除先前的 FINAL 标志或将方法的作用域从 PRIVATE 更改为 PROTECTED 来启用继承时可能发生的行为),仍不失为一种更为整洁的方式。

在编写新代码时,在设计类时应直接考虑此可测试性问题,并找到其他更好的方法。常见的最佳做法包括求助于其他测试工具并将问题方法提取到本身具有接口的单独类中。

勿更改生产代码来使代码可测试的更具体的变化形式。

勿模拟不需要的东西

ABAP 整洁之道 > 目录 > 测试 > 注入 > 本节

cut = NEW /clean/class_under_test( db_reader = db_reader
                                   config    = VALUE #( )
                                   writer    = VALUE #( ) ).

尽可能精确地定义给定条件:不要设置测试不需要的数据,也不要模拟永远不会调用的对象。这些内容会分散读者对真实运行情况的注意力。

" anti-pattern
cut = NEW /dirty/class_under_test( db_reader = db_reader
                                   config    = config
                                   writer    = writer ).

在某些情况下,根本不需要模拟某些内容,数据结构和数据容器通常就是这种情况。例如,您的单元测试可以使用 transient_log 生产版本正常运行,因为该版本只存储数据而不会产生其他影响。

勿构建测试框架

ABAP 整洁之道 > 目录 > 测试 > 注入 > 本节
与集成测试不同,单元测试涉及数据输入和数据输出,所有测试数据都是根据需要动态定义的。

cl_abap_testdouble=>configure_call( test_double )->returning( data ).

不要开始构建用于区分“测试案例标识”的框架来决定要提供的数据。生成的代码将会如此冗长而复杂,以至于您无法长期保留这些测试。

" anti-pattern
test_double->set_test_case( 1 ).
CASE me->test_case.
  WHEN 1.
  WHEN 2.
ENDCASE.

测试方法

ABAP 整洁之道 > 目录 > 测试 > 本节

测试方法名称:反映出设想和预期的情形

ABAP 整洁之道 > 目录 > 测试 > 测试方法 > 本节
好的名称可以反映出测试的设想内容和预期结果:

METHOD reads_existing_entry.
METHOD throws_on_invalid_key.
METHOD detects_invalid_input.

不恰当的名称则反映操作内容,重复无意义的事实或含糊不清:

" anti-patterns
" What's expected, success or failure?
METHOD get_conversion_exits.
" It's a test method, what else should it do but "test"?
METHOD test_loop.
" So it's parameterized, but what is its aim?
METHOD parameterized_test.
" What's "_wo_w" supposed to mean and will you still remember that in a year from now?
METHOD get_attributes_wo_w.

由于 ABAP 只允许方法名称使用 30 个字符,如果名称过短而无法表达足够的含义,那么添加注释是合理的。在 ABAP 文档中或在测试方法的第一行添加注释都是不错的选择。

如果有很多名称过长的测试方法,这可能意味着您应该将单个测试类拆分为多个测试类,并在类名称中的给定部分予以区别。

使用 given-when-then

ABAP 整洁之道 > 目录 > 测试 > 测试方法 > 本节
按照 given-when-then 范式组织测试代码:首先,初始化给定部分中的内容 (“given”),其次调用实际测试的内容 (“when”),再次验证结果 (“then”)。

如果 given 或 then 部分过长,您无法再从视觉上区分这三个部分,请提取子方法。空行或注释作为分隔符乍一看可能还不错,但并不能真正减少视觉混乱。尽管如此,空行或注释对于读者和测试新手区分这三个部分还是有所帮助的。

“When” 恰为一个调用

ABAP 整洁之道 > 目录 > 测试 > 测试方法 > 本节
确保测试方法的 “when” 部分仅包含对被测类的一次调用:

METHOD rejects_invalid_input.
  " when
  DATA(is_valid) = cut->is_valid_input( 'SOME_RANDOM_ENTRY' ).
  " then
  cl_abap_unit_assert=>assert_false( is_valid ).
ENDMETHOD.

调用多个对象表明该方法没有明确的焦点,测试内容过多。这使得测试失败时更难以找到原因:是第一次、第二次还是第三次调用导致了失败?这也使读者感到困惑,因为他不确定确切的被测功能是什么。

除非真正需要否则勿添加 TEARDOWN

ABAP 整洁之道 > 目录 > 测试 > 测试方法 > 本节
通常只需要使用 teardown 方法来清除数据库条目或集成测试中的其他外部资源。

重置测试类的成员(尤其是 cut 和所用的测试替身)是多余的操作;在启动下一个测试方法之前,这些成员就会由 setup 方法覆盖。

测试数据

ABAP 整洁之道 > 目录 > 测试 > 本节

使其易于辨明含义

ABAP 整洁之道 > 目录 > 测试 > 测试数据 > 本节
在单元测试中,您希望能够快速判断出哪些数据和替身是需要关注的内容,哪些内容的作用只是为了防止代码崩溃。通过为没有含义的内容起一个显眼的名称和值来支持此功能,例如:

DATA(alert_id) = '42'.                             " well-known meaningless numbers
DATA(detection_object_type) = '?=/"&'.             " 'keyboard accidents'
CONSTANTS some_random_number TYPE i VALUE 782346.  " revealing variable names

不要欺骗人们相信某些内容可以与真实的对象或真实的定制联系起来(如果没有联系):

" anti-pattern
DATA(alert_id) = '00000001223678871'.        " this alert really exists
DATA(detection_object_type) = 'FRA_SCLAIM'.  " this detection object type, too
CONSTANTS memory_limit TYPE i VALUE 4096.    " this number looks carefully chosen

使其易于辨明差异

ABAP 整洁之道 > 目录 > 测试 > 测试数据 > 本节

exp_parameter_in = VALUE #( ( parameter_name = '45678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789END1' )
                            ( parameter_name = '45678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789END2' ) ).

不要强迫读者去比较长且无意义的字符串来发现细微的差别。

使用常量描述测试数据的用途和重要性

ABAP 整洁之道 > 目录 > 测试 > 测试数据 > 本节

CONSTANTS some_nonsense_key TYPE char8 VALUE 'ABCDEFGH'.
METHOD throws_on_invalid_entry.
  TRY.
      " when
      cut->read_entry( some_nonsense_key ).
      cl_abap_unit_assert=>fail( ).
    CATCH /clean/customizing_reader_error.
      " then
  ENDTRY.
ENDMETHOD.

断言

ABAP 整洁之道 > 目录 > 测试 > 本节

少而精的断言

ABAP 整洁之道 > 目录 > 测试 > 断言 > 本节
仅使用少量断言,准确地断言测试方法的内容。

METHOD rejects_invalid_input.
  " when
  DATA(is_valid) = cut->is_valid_input( 'SOME_RANDOM_ENTRY' ).
  " then
  cl_abap_unit_assert=>assert_false( is_valid ).
ENDMETHOD.

断言过多表明该方法没有明确的重点。这会在很多地方将生产和测试代码耦合在一起:更改功能将需要重写大量的测试,尽管它们实际上并未涉及更改后的功能。各种各样的断言使读者感到困惑,难以辨别其中最重要的那个断言。

" anti-pattern
METHOD rejects_invalid_input.
  " when
  DATA(is_valid) = cut->is_valid_input( 'SOME_RANDOM_ENTRY' ).
  " then
  cl_abap_unit_assert=>assert_false( is_valid ).
  cl_abap_unit_assert=>assert_not_initial( log->get_messages( ) ).
  cl_abap_unit_assert=>assert_equals( act = sy-langu
                                      exp = 'E' ).
ENDMETHOD.

使用恰当的断言类型

ABAP 整洁之道 > 目录 > 测试 > 断言 > 本节

cl_abap_unit_assert=>assert_equals( act = table
                                    exp = test_data ).

断言的作用往往不止表面看到的那些,例如 assert_equals 包括类型匹配,如果值不同,还能提供准确的描述。使用错误的、过于常见的断言将迫使您立即进入调试器,而不是让您直接从错误消息中看到问题所在。

" anti-pattern
cl_abap_unit_assert=>assert_true( xsdbool( act = exp ) ).

断言内容而非数量

ABAP 整洁之道 > 目录 > 测试 > 断言 > 本节

assert_contains_exactly( actual   = table
                         expected = VALUE string_table( ( `ABC` ) ( `DEF` ) ( `GHI` ) ) ).

如果可以表达期望的实际内容,就不要编写幻数数量断言。尽管仍然可以达到预期,但数字可能会有所不同。相反,尽管内容完全出乎意料,但数字可能会匹配。

" anti-pattern
assert_equals( act = lines( log_messages )
               exp = 3 ).

断言质量而非内容

ABAP 整洁之道 > 目录 > 测试 > 断言 > 本节
如果您对结果的元质量感兴趣,但对实际内容本身不感兴趣,请使用合适的断言来表达:

assert_all_lines_shorter_than( actual_lines        = table
                               expected_max_length = 80 ).

断言精确的内容会掩盖您实际想要测试的内容。它还很脆弱,因为重构可能会产生一个不同但完全可以接受的结果,尽管它会中断所有过于精确的单元测试。

" anti-pattern
assert_equals( act = table
               exp = VALUE string_table( ( `ABC` ) ( `DEF` ) ( `GHI` ) ) ).

使用 FAIL 检查是否出现预期异常

ABAP 整洁之道 > 目录 > 测试 > 断言 > 本节

METHOD throws_on_empty_input.
  TRY.
      " when
      cut->do_something( '' ).
      cl_abap_unit_assert=>fail( ).
    CATCH /clean/some_exception.
      " then
  ENDTRY.
ENDMETHOD.

转发意外异常而非捕获就失败

ABAP 整洁之道 > 目录 > 测试 > 断言 > 本节

METHODS reads_entry FOR TESTING RAISING /clean/some_exception.
METHOD reads_entry.
  "when
  DATA(entry) = cut->read_something( ).
  "then
  cl_abap_unit_assert=>assert_not_initial( entry ).
ENDMETHOD.

与以下代码相比,您的测试代码始终专注于愉快路径,因此更易于阅读和理解:

" anti-pattern
METHOD reads_entry.
  TRY.
      DATA(entry) = cut->read_something( ).
    CATCH /clean/some_exception INTO DATA(unexpected_exception).
      cl_abap_unit_assert=>fail( unexpected_exception->get_text( ) ).
  ENDTRY.
  cl_abap_unit_assert=>assert_not_initial( entry ).
ENDMETHOD.

编写自定义断言以缩短代码和避免重复

ABAP 整洁之道 > 目录 > 测试 > 断言 > 本节

METHODS assert_contains
  IMPORTING
    actual_entries TYPE STANDARD TABLE OF entries_tab
    expected_key   TYPE key_structure.
METHOD assert_contains.
  TRY.
      actual_entries[ key = expected_key ].
    CATCH cx_sy_itab_line_not_found.
      cl_abap_unit_assert=>fail( |Couldn't find the key { expected_key }| ).
  ENDTRY.
ENDMETHOD.

而不是一遍又一遍地复制粘贴。


SAP-Garson
原文链接:https://blog.csdn.net/qq_36296311/article/details/128818808

文章来自于网络,如果侵犯了您的权益,请联系站长删除!

上一篇:酒店管理系统的设计与实现(任务书+中期检查+PPT+论文)
下一篇:电商的1000+篇文章总结
评论列表

发表评论

评论内容
昵称:
关联文章

ABAP 整洁
什么是整洁的架构
什么是整洁的架构?
ABAP 100 面试题
自学SAP三
ABAP
以“势、、术”法则剖析原型设计工具的产品形态
ABAP转载备用链接
SAP工具箱 ABAP2XLSX
ABAP数据定义DATA和TYPES
ABAP系统变量
SAPABAP简介
我的SAP ABAPer
ABAP声母韵母
ABAP 选择屏幕MODIF ID 的使用方式
ABAP-小工具Program的导出与导入
SAP ABAP教程 02 创建您的第一份 ABAP 报告 (教程含源码)
ABAP专家的养成
Jerry带您了解Restful ABAP Programming模型系列三:云端ABAP应用调试
ABAP的自学路 ,初步认识ABAP &lt;一&gt;

热门标签
CBP 问题处理 # ALV # 【SAP | 前世今生】 # 1.moonsec-2020-[持续更新] # ABAP # ABAP-接口 # abap学习路线 # ALV # AVRCP协议 # bdc # BMS项目实战记录 # BW # ClickHouse # crud 框架 (mybatis-plus/ jpa等) # dynpro # ERP # JCo3.0 # PyRFC # Python数据分析与机器学习 # SAP ABAP # SAP FICO # SAP FTP # SAP HANA # SAP MM # SAP-Restful # SAP消息号A类 # sap应用技巧 # 工具使用 # 数据库 # 网安神器篇 # 优化篇 # 语法 # 筑基08:渗透测试综合实验 (path.Combinee(rootDir, "nwrfcsdk", "icuuc50")) ,ides .NET .NET 6 .NET Core .NET Remoting和WebServices .net(C#) .NET/C# .netcore .NET技术 .NET连接SAP .UD选择集 /h /ui2/cl_json @click.prevent _E8_AE_BA_E6_96_87 ~ { ABAP} ~ ~{一起学ABAP}~ “SAP.Middleware.Connector.RfcConfigParameters”的类型初 《ABAP专栏》 《SAP ABAP基础通关百宝书》【从入门到精通】 《测绘程序设计精品案例合集》 《计算机网络自顶向下方法》学习笔记 【Azure 应用服务】 【SAP】ABAP-CDSVIEW 【速成之路】SQLserver 0.0-SAP BW学习 001-计算机基础 01检验类型 1 10.Abap 10.ABAP-CTS 102 1024程序员节 103 1155服务器装系统 12.SAP-SKILL 122 13台根服务器位置 15行 1809 1909 1核1g1m服务器相当于什么性能 2003服务器修改ftp密码 2010 2012服务器系统安装数据库 2012服务器系统安装数据库吗 2018年终总结 2019 2019java专科 2019年终总结之SAP项目实践篇 2022跨年烟花代码 2022年 2023云数据库技术沙龙 2023云数据库技术沙龙 “MySQL x ClickHouse” 专场 2-step picking 2-step拣配 2月一次的flyback 321 32位服务器系统安装教程 3D 40 408 408——计算机网络 408学习笔记 40位 478g+ 虚拟服务器 4hana 545移动类型 5G 6.824 60.技术开发 6------SAP 701 711 740新语法 7------SAP A a2dp AA AB01 ABAP ABAP 语法 ABAP AES加密解密 ABAP ALV abap alv 更改数据 abap alv新增行数据 ABAP AMDP abap bapi ABAP BAPI分享 ABAP BASE64加解密 ABAP BC400 ABAP CDS ABAP checkbox ABAP Dialog开发 ABAP DOI ABAP EXCEL ABAP Expression ABAP GUID ABAP Handy program abap hr ABAP IDOC abap java ABAP JSON ABAP JSON大小写 ABAP JSON驼峰 abap me21n增强 abap mm后台表 ABAP Modify 的用法 ABAP New ABAP REST API ABAP REST JSON ABAP RSA PSE ABAP RSA 加密解密 ABAP SAP ABAP SESSION传递 ABAP SMARTFORMS 默认 WORD 编辑 ABAP Table ABAP Toolbar ABAP tools ABAP wait abap xml 日期格式 ABAP 报错 ABAP 笔记 ABAP 常见错误 ABAP 程序开发 abap 程序模板 ABAP 初级技术 abap 创建出口历程 abap 调用java abap 发送json报文 ABAP 关键字 ABAP 基础知识 ABAP 技巧 ABAP 接口 ABAP 开发 ABAP 乱乱记 ABAP 内表 ABAP 内表 排序 abap 内表 条件查找 ABAP 配置相关 ABAP 批量创建货源清单 ABAP 屏幕开发激活显示 ABAP 人事模块 abap 上传excel数字去除千分符 ABAP 实用程序记录 ABAP 事务代码 ABAP 数据字典 ABAP 替换 ABAP 替换字符 ABAP 条件断点 DEBUG ABAP 未按大小排序 ABAP 销售模块 ABAP 新语法 ABAP 选择屏幕 ABAP 学习 ABAP 学习笔记 ABAP 一些常用技巧 ABAP 语法备忘 ABAP 增强 abap 指定长度服务器上传数据 ABAP 中级技术 abap 转换成字符串 ABAP 字符查找 abap 字符串操作 ABAP  屏幕流 ABAP 开发模块 ABAP/4 ABAP_01 ABAP_02 ABAP_BASIS ABAP_FUNCTION MODULE ABAP_OTHERS ABAP_SYNTAX ABAP_各路小技能 ABAP2XLSX ABAP4 ABAP7.50 ABAP740新语法 abapdata定义方法 abaper ABAP-FICO ABAP报表程序结构框架 ABAP报错 abap捕获当前功能键sy ABAP查找代码块 ABAP常用代码段 ABAP程序例子 ABAP初级 ABAP创建搜索帮助 ABAP打印 ABAP的BAPI ABAP调优 LOOP ABAP定时job abap动态变量 ABAP动态修改屏幕 abap读取sap服务器文件名 abap对接外围系统 abap分页 ABAP工具 ABAP关键字 ABAP函数 abap获取日期 ABAP基础 abap基础入门 ABAP基础语法 ABAP基础知识 ABAP技能树 ABAP技巧之游标 ABAP技术 abap技术栈 ABAP加密 ABAP-接口 ABAP开发 ABAP开发回顾总结 ABAP开发随便记录 ABAP开发学习 ABAP开发语言 abap开发注释快捷键 ABAP开源项目清单 ABAP快捷键 abap连接mysql ABAP模块 ABAP内表汇总 abap判断包含字符当中包含小数点 ABAP屏幕相关 ABAP其他增强 ABAP入门 ABAP时间戳 ABAP实例分享 ABAP使用技巧 abap视图字段限制 ABAP数据库删除 abap数据类型转换 ABAP四代增强 ABAP四舍五入 ABAP随笔 ABAP提取汉字 abap文件上传 abap文件下载导出 ABAP问题记录 abap系列 ABAP相关 ABAP小工具 ABAP小记 ABAP小技巧 ABAP校验时间日期格式 abap新语法 ABAP新语法汇总 ABAP新语法收集整理 ABAP修改删除数据 ABAP选择屏幕 ABAP选择屏幕开发 ABAP学习 ABAP学习记录 ABAP学习实用网址 abap语法 ABAP语法优化 ABAP语言 ABAP增强 ABAP知识点总结 ABAP指针 ABAP中RANGES的用法 ABAP中的同步和异步调用 abap字符串值变量 Abaqus ABLDT ABLDT_OI ABMA AC_DOCUMENT Account Group ACDOCA Activate ADD NEW FONT ADO.NET Adobe Form ADT AES AFAB/AFABN AFAMA AG1280 AirByte AJAB ajax AL11 ALE all in one Allocation Rule ALV ALV List ALV SEL_MODE alv 刷新 ALV报表 ALV横列单元格颜色 ALV模板 ALV鼠标右键 alv下拉 alv显示基础 ALV知识点 AMDP amp AMS系列产品 android android studio Android9设备打开WIFI热点 android不同版本风格 android模拟器 android热点流程 Android网络接入框架分析 Android系统开发 Angular angular.js ANSYS Ant Anywhere数据库监控 AO25 aof apache Apache DolphinScheduler API api document APM APO APO函数 APO开发 app App Service for Window application app测试 app服务器设计文档 app服务器数据库文件夹下 aps APT Architecture Archiving Area Menu arm arraylist ar路由器的虚拟服务器 ASAP asp.net asp.net MVC Assortment ATO Attribute AuCs authorization Automatic AutomaticScrg automation AVForamt AW01N Awesome Java awk awr AWS AWS SAP AWS SAP认证 aws认证 AWS战报 Azure Azure Storage B2B增长 Backflush BADI BANK Bank Account BAPI bapi sap 创建物料 BASE base64 bash BASIS Basis Consultant Questionnaire BASIS基础知识 BASIS模块 BASIS系统配置及操作 BASIS中遇到的问题 batch Batch Data Conversion BD87 BDC bdv021-clickHouse Beginning WF 4.0翻译 BGP路由器协议排错 bgRFC BI BI+BW+BO仓库管理 big data BigData ble bluetooth BO BOBF bom bom成本分析模型 bom更改编号 sap books bookv001——navigationing Boost完整实战教程 bootstrap BOPF BP BPC BPC开发 BP共用编码 BP和客商关联后台表 BP-客商 BP配置 bp配置 sap BP文档 break BRF+ BRFplus BSP BSTAT=U bt BTE BTEs BTP BUG BUG问题解决 BulkStorage BurpSuite插件 Business Suite BusinessPartner BUT000 BW BW/4 HANA BW4 bw4/ hana BW4/HANA BW4HANA BW报表使用操作手册 BW技术 BW建模 BW实施 ByteDance C# C# IO相关 C# sap集成 C# WPF C# 编程 C# 窗体应用 C# 读取txt文本数据 C# 读取文本每行每列数据 C# Stopwatch C#Winform C#编程 C#高级 C#格式转化 C#基础 C#基础知识 C#教程 C#入门经典 C#算法演义 c#学习 C#知识点笔记 C/4 C/4HANA c/c++ C++ C4C CA CS CO cad项目数据库服务器 Calculation CapacityCheck case when Cash Management cast CA周记 CBS CCNP题库 CDISC CDS CDS View CDS Views CDS视图 Cell Popin centos certificate CertificateType Change Log ChatGPT CHECK_ACCESS_KEYS CHECKBOX CheckBoxGroup Check按钮 chrome CI & CD CIO ci上传文件到不同服务器 cj20n sap 报错未知列的名称 CKM3 CKMLCP CL_GUI_ALV_GRID cl_ukm_facade Class ClickHouse clickhouse数据库 Client Copy CLIENTCOPY Cloud Cloud Native Cloud Platform CloudFoundry CMS CMU15-445 (Fall 2019) CO CO01 co88 sap 实际结算 COCA单词表 COCA高频单词 COCA核心词汇 COCA英语分频词汇 COCA英语语料库 CO-CCA CODE COGI COKEY Commerce Commvault Commvault技术知识点 Configuration connect_by_path ContentServer continue Control ControlQuantity CONV Conversion COPA COPC COPY来源 Cording Block Core Data Service(CDS View) CO控制 CO配置 CPI CPI技术小知识 CPLD CPM cpu CRM CRM系统 crm系统服务器要求 cross warehouse Crystal Reports CS CSharp CSI SAP2000 CSI SAP2000安装教程 css css3 CSV认证 CTCM ctf CTF-MISC CTF-Misc-wp CTS Customers CVI_CUST_LINK CVI_VEND_LINK C和C++Everything教程 C语言 C语言程序设计 Dapr Data Services Data sources database datagridview dataTable交换列 dataTable列操作 DATAX date DateNavigator DB DB LUW DB2 dba DBA 实战系列 DBCO DD08V DDIC DDS算法 debian debian云服务器项目 Debug debug方法 DEBUG改SAP表数据 Decal Decline demo DEMO程序 des DESADV DESTINATION DestinationProvider devexpress v22.1 devops DevSecOps DIalog Dictionary Encoding Diff discuz服务器系统 disk dms dns怎么修改默认服务器 docker docker容器 dom dont show this message again Driver E5调用API E5开发者 E5续订 EBS Ecc ECC_常用标准函数标准方法 ECC6 ECC6是否支持linux7 echarts eclips Eclipse eclipse报错 ECM ecmascript ECM企业管理 ecn EDI EDIT Ehancement EHP EHP4 EHP8 elasticsearch elementui ELT emqx English Enhancement enhancement MBCF0007 Enterprise Servers and Development Entity Linking Enumeration EOS空项目添加服务器 EPIC EPIC_PROC epoll EPPM erp erp oracle数据库连接失败 ERP 增强 erp5 ERP-SAP erp服务器系统分区多大 ERP供应链 ERP实施 erp无线架设服务器 ERP系统 erp系统 服务器在哪里的 ERP项目 ERP小讲堂 es6 esb ESP8266 esri ESXI ETBAS二次开发 eth节点计划服务器维护 ETL etl工程师 ETL工具 ETL开发规范 ETL社区版 ETL数据集成 ETO events EWM EWM模块 Example examples EXCEL Excel服务器数据库修改 Exception EXCLUDING express F.13 F-02 F110 F5080 FAA_CMP_LDT FAGL_FC_VAL FAGLGVTR FB05 FBB1 FBL1N ffmpeg FI FI01 FI12 FI12_HBANK FI-AA FICO fico bapi FICO Integration FICO-AA FICO模块 FICO-年结 FICO问题点 FICO-月结 FICO增强 field-symbols fifaol服务器不稳定 file Fine finereport FINSC_LEDGER Fiori fiori 2.0 fiori app configuration fiori launchpad Fiori-Web FIORI配置 Fixed point arithmetic FixedStorageBin FI财务 FI金额 FI配置 FLCU00 flex FLVN00 FM Focus FONT FONTS For FOR ALL ENTRIES IN FPGA fpga开发 FPGA项目例子总结 FPM framework freemarker Freight标签页 freshman to ABAP FS15会计科目扩充 FTP ftp 网页如何上传到服务器 ftp传输文件到其他服务器 ftp服务器存放文档 ftp服务器端文件大小设置 ftp服务器设置上文件大小 ftp服务器生成xml文件 FTP服务器收不到传送的文件 ftp服务器数据存放位置 ftp服务器文件路径怎么写 ftp服务器限制文件大小 function Function ALV Function Modules functional programming Functions Game Gartner Gateway GATEWAY100 GBase gdal GeneXus GeneXus 2021 gentoo 安装php7 GeoTools GET Parameter GIS Git github Gizmos gnu go google Google 微软 亚马逊 阿里 腾讯 字节跳动面试总结 GR GR Date GR/IR GR/IR余额清单 GRaph Process groovy GroupNumber gui GUI STATUS gui740的消息服务器 GUID GW100 H3c 服务器bmc管理芯片 h3c服务器 raid 型号 h3虚拟服务器 h5修改服务器数据 hadoop HAHA SQL halcon HANA HANA Advanced Data Modeling HANA Advanced Data Modeling 读书笔记 HANA DB HANA DBA hana s4 服务器 HANA SQL hana sql mysql oracle HANA SQLScript HANA Studio HANA VIEW hana vs oracle hana 表空间 hana 查看表字段 HANA 导入数据 hana 服务器性能测试 HANA Studio HANA安装 hana查询去重 HANA常用函数 hana抽数到mysql hana的date对应oracle日期 hana服务器销售资质 HANA进阶学习 hana生产系统服务器 HANA实战 hana数据库 hana数据库 字段长度 hana数据库导入mysql hana数据库导入到oracle hana数据库服务器文件丢失 hana数据库教程php hana数据库连接mysql hana数据库连接oracle hana数据库与mysql HANA信息建模 Hana性能优化 hana修改字段 HANA学习 hana语法 HANA在线日志 Hashid hash-identifier hbase HCM HCP HDI Container HEC hibernate hierarchy Hints his系统服务器数据存在哪里 His系统数据库服务器关系 hive HNUST湖南科技大学计科专业考试复习资料 hp380G5服务器系统安装 hp服务器产品文档 HR HR模块 HR薪资发放过账 HR增强 HTAP HTAP for MySQL html html5 HTML5/CSS/Bootstrap http http://95u.free.fr/index.php httpcompnents https https://mp.weixin.qq.com/s/keb HU Hybris I/F IBAN IBP ICF ID ide idea idea中项目如何上传到服务器中 IDES IDoc idoc java IDOC技术 IDT ifm_research_notes IFRS16 iis ftp服务器文件大小 ijkplayer IM image imessage IMG子菜单 import IM层面 Include Informatica inspection point intellij idea Inter-company Intergration Internal table Interview INVOIC ios iot IP ipad协议 ipfs存储服务器销售 IQ02 IQ09 IR IRPA ISO IS-RETAIL issue IT IT - Linux ITS ityangjia IT技术 IT企划 IT生涯 IT项目与团队 IT养家 j2ee J3RCALD jar Java java b1 b1 be a9 Java Connector java jco sap 重连 JAVA PI PO SOAP JAVA PO SOAP java sap总账凭证接口 java webservice调用sap Java Why java 访问hana java 薪水完爆abap JavaScript javaSE基础篇 Java并发 Java调用SAP java调用sap接口 JAVA调用SAP接口地址 java对接sap java更换sap配置不生效 Java工具类 JAVA工作日常 java函数调用报错 java获取hana接口数据 java获取sap数据 java开发 java连接hana java连接sap Java连接sap无明显报错信息 java实战 java项目所需服务器 JAVA学习 java云服务器怎么上传文件大小 java怎么安装apple JAVA重点部分的笔记 java转sap hybris方向 JCo jco.client.saprouter JCo3 JCO连接 jdbc JDBC连接 JDK jira JOC Join JOIN 内表 jpa jquery js json json 服务器 文件 js基础笔记 junit JVM jwt K3 kafka KANBAN KE24 kernel kettle KEY kohana KP06与KP26 KSU5 KSV5 kubernetes labview lambda lamp LAN leetcode LEFT DELETING LEADING LENGTH Leonardo less linq Linux linux 64位vcs linux hana linux hana 版本查询 linux 安装sap linux 划分两个VDisk linux 命令是 的sap linux64 solvers Linux查看hana数据库进程 linux登录Hana数据库 linux调用rfc函数配置 Linux开发分享 Linux启动SAP服务 linux如何查看MBFE版本信息 Linux网络 linux系统的服务器怎么重启 linux相关 linux中停sap服务 lisp list LISTING Lock Logic LogicSystem lpfs存储服务器怎样维护 LQ02 LSETBF01 LSMW LT23 LT41 LT42 LT45 LTMC LTMC和LSMW等 LTMOM LX03 LX09 LX10 LX11 LX12 LX29 LX39 M_MSEG_LGO mac mac os x macos Mail makefile Manage Banks manager mariadb Markdown mass MASTER DATA MAST表 matdoc Material Group Material Ledger MaterialSpec matplotlib matrix maven MaxDB MaxWeight MB04 MB51清单格式 MB5B MB5M MBSM MBST MBST冲销 mcu md01和md02区别 MD04 MD04中例外信息30 MDBS MDG MDG 2021 MDG 2022 MDG BP MDG顾问 MDG项目 ME me15 me21nme22nme23n增强ME_ ME22N ME57界面看到的供应源跟Source List主数据不一致 MEBV memcached MES Mesh Message Messages MetaERP Method List MF47和COGI MI10 MIBC microsoft Microsoft Access Microsoft Azure Microsoft365 E5 MIGO MIGO 241 migo 311 MIGO+201 migo初始化库存 s4 MIGO事务代码 MIGO增强 MIGO子功能 migration Migration cock MIRO MIRO发票校验 MIRO发票校验多采购订单选择 mkpf ml MM mm bapi MM/SD mm17 MM41创建的商品主数据 MM41创建商品主数据 MM60 MMBE MMPV MMSC MM-报表功能开发 MM-采购管理 MM-采购审批 MM常用BAPI MM-定价过程 MM更改物料类型 MM顾问 MM教程 MM模块 MM配置 MM物料管理 mobile MODIFY table MOVE TO movement type mp3 MP38 MPN MPN物料的采购初探 mps MQTT mqtt服务器数据存储位置 mqtt协议库服务器 MRP MRP标识 MRP处理代码 MRP过程 MRP组 MS SQL mseg mssql MTE MTO MTO/MTS MTS MTS/MTO/ATO/ETO MTS/MTO/ETO Mule ESB 开发 Mule ESB 社区版 实施 Mule ESB 实施 Mule ESB开发 Mule ESB社区版实施 Mule ESB实施 MultipleBOM MultipleSpecifications MultipleSpecs Muxer mvc MWSI mybatis mybatis-plus myeclipse mysql mysql 1060指定的服务未安装 mysql hana数据同步 mysql版本情况 Mysql等数据库 MySQL高级 mysql和hana mysql数据库停库停不下来 MZ SAP FICO精讲视频 MZ SAP那些事 nagios name_mappings Naming Convention NAST nas怎么备份服务器文件夹 NativeLibrary.Load nat服务器性能 nc 二次开发 NCO NCO3.0 nc文件服务器 数据库文件 NDSS NetSuite 案例 NetSuite新闻 Netweaver network New NineData nlp Node node.js nodejs nokia NoSQL NOTE npm null Number Range numbers numpy NW751 nwa key-storage NWBC NX文档服务器 o365 OA OAAQ OABL oa办公 OB07 OB08 OB13 OB52 OB62 OB74 OBBH OBJK ObjType OBR1 OBR2 OBR3 OBYC-DIF OBYC-PRD oceanbase ocx OData odbc odoo office OI-题解 olap OMIR OMSJ OMSY OMX6 Onenote_DB Onenote_Others onetime vendor On-premise OO OOALV OOALV进阶 OOALV增删改查 OPEN open item OPEN SQL Open Storage Opengauss openGauss核心技术 OPENSAP UI5 扫盲 OPENSQL Openui5 openwrt系统安装到云服务器异常 ops$ oracle数据库用户 ora 01005 linux Oracle oracle 60401 oracle clob minus oracle dba Oracle EBS oracle e-business suite 下载 Oracle ERP oracle ftp 文件乱码 oracle hana 字段长度 oracle logon 乱码 oracle nid ora 24324 oracle sap 备份 oracle sap金蝶 oracle set newpage Oracle Tuning oracle 抽数据到 hana oracle 创建一揽子协议 oracle 打开数据库三步 oracle 应用系统 oracle创建服务出错1073 oracle和netsuite培训 Oracle数据库 oracle数据库恢复版本不一致 oracle与用友的差别 OS other Others Outbound Overtime p2p PA PaaS PACKAGE SIZE Pandas parallel Parameter Partner payment Payment method Payment Terms PA认证 PB00 PBXX PC PC00_M99_CIPE PCo PCP0 PC安装服务器系统 PDA pdf performance PE安装服务器系统6 PFCG PGI Pharos(小白路标) php php功能函数 PHP开发erp功能模块 php连接sap hana数据库 php清理服务器文件大小 php与sap系统 php转行自学java PhysicalSamples PI PI/PO ping pip PIPO PIR PI接口常见问题处理 pi节点虚拟服务器怎么弄 Plant Group PLG PLG Application跳转传参 plm PLSQL PLSQL13 PLSQL弹出框 PM pmp pms PMW PO po 价格条件表 PO&amp poi PolarDB Popup Port Portal POS POS Interface PostgreSQL posting key postman Postman 接口测试 Power BI PowerBI PowerBuilder Powered by 金山文档 powerpoint PowerQuery&amp PO接口常见问题处理 PO中基于GR的IV清单 PP PP &amp PP Module PPM PP模块 pp模块常用表 sap PP生产订单 PP生产过程 PR PREPACK Pricing Print PROCEDURE Product Hierarchy project management PS PS模块 pu Purchase Purchase Order History Categor pyautogui pycharm python Python Golang 人工智能 机器学习 图像处理 Python场景积累 python获取sap数据 Python基础 PYTHON接口开发 python连接sap接口 python能连sap吗 python学习 python与sap QA08 QA11 QC51 QE01 QE23 QM QM Control Key QM采购质量管理 QM质量管理 QP01 qRFC QS28 QS61 qt qt5 Quality Certificate Quant QUERY R3 rabbitmq rac 服务器 修改时间 RadioButtonGroup Random react react.js READ receive idoc redhat redis REDUCE Reflex WMS REM REP Report ReRAM rest REST ADAPTER RESTful RETAIL ReturnDelivery RFC rfcv函数实现 RFC查询SAP数据库 rfc方式的集成 sap RFC封装WEBService RFC函数 rfc垮端口 sap RFSEPA02 RIGHT DELETING TRAILING Rollout project Routing RPA RPA机器人 RPA机器人流程自动化 RPA魔力象限 RPA资讯 RPC0 RSA RSA Encryption RSA PRIVATE KEY RSS RTMP协议云服务器 runtime rust RV_ORDER_FLOW RWBE r语言 R语言入门课 S/4 S/4 HANA S/4 HANA 1809 S/4HANA S/4HANA 2020 S/4HANA 2021 S/4HANA 2022 S/4HANA迁移 S/4补0 去0 s_alr_87013127 S_ALR_87013611 S_ALR_870136XX s2k S4 S4 CLOUD/ FIORI S4 CRM S4 HANA s4 hana ecc S4 HANA 功能变化清单 S4 HANA数据迁移工具 S4 HAVA S4 Kernel S4CRM S4H PA S4HANA S4HANA Conversion S4HC S4HC产品相关 S4新表ACDOCA S4新型数据导入工具 saas SAC Sales Area SALES PRICE SampleSize SAP sap abap SAP ABAP学习 SAP Basis SAP / 后台配置 SAP 1809 sap 46c oracle 从unix 迁移至 windows SAP ABAP SAP ABAP  Excel模板上传及Excel数据批导 SAP ABAP AES128 SAP ABAP AES256 SAP ABAP for HANA SAP ABAP HANA SAP ABAP Runtime Error SAP ABAP SHA512 SAP ABAP 编程教程 SAP ABAP 并发 SAP ABAP 核心代码 SAP ABAP 基础 学习 SAP ABAP 李斌的分享笔记本 SAP ABAP 问题整理 SAP ABAP 学习资料 SAP ABAP 增强 SAP ABAP(总结) sap abap接口篇 SAP ABAP开发 sap abap开发从入门到精通 SAP ABAP开发实战——从入门到精通 SAP ABAP开发问题记录 SAP ABAP开发专栏 SAP ABAP零碎知识 SAP ABAP浅尝截止 SAP ABAP实例大全 SAP ABAP性能优化 SAP ABAP增强 SAP ABAP自学教程 SAP Adapter SAP Adobe Form SAP AES加密解密 SAP ALE SAP ALV SAP Analytics Cloud sap and oracle SAP APO SAP APO 介绍 SAP Ariba SAP ARM SAP B1 SAP B1 License Serve SAP B1原创 SAP BAPI SAP Basis SAP Basis Tips SAP Basis 系统学习 SAP Basis&amp SAP BDC SAP BDC MODE SAP BDC模式 SAP BI on HANA SAP BO SAP BOBF/FPM/WEBDYNPRO SAP BOBJ SAP BOM反查 SAP BOM记录查询 SAP BOM修改记录 SAP BP SAP BTP SAP business one SAP Business One 二次开 SAP BW sap bw、echar、smart bi sap bw4 sap C/4HANA SAP C4C SAP CAR sap cds view SAP client2.0 download SAP Cloud SAP Cloud Platform SAP Cloud Platform Cockpit SAP CO SAP Consultancy SAP CP SAP CPI SAP CRM sap crm button SAP Data Service sap dbco访问oracle SAP DEMO数据增加 SAP Dialog调用 SAP Dialog开发 SAP Dialog学习 SAP ECC SAP ECC6 SAP ECC6 / CO SAP ECC6 / FI SAP EDI SAP EPIC SAP ERP SAP ERP系统 SAP EWM SAP excel数据导入 SAP FI sap fi  凭证跳号 SAP FI-AA SAP FICO SAP FICO 报错处理办法 SAP FICO 开发说明书03(源代码仅做参考) SAP FICO 系统配置 SAP FICO 资料免费分享 SAP FICO开发说明书_01(源代码仅作参考) SAP FICO开发说明书_02(源代码仅作参考) SAP Fiori SAP Fiori & SAP(open) UI5 SAP Fiori 开发实践 SAP FM SAP freelancer SAP Frori SAP Gateway SAP GUI sap gui script SAP GUI 登录不需要密码 SAP GUI 界面 SAP GUI 快捷方式密码 SAP GUI 密码保存 SAP GUI 免密登录 SAP GUI 主题 SAP GUI 主题切换 SAP GUI+WEBGUI SAP GUI界面切换 SAP GUI密码设定 SAP GUI切换 SAP HAN SAP HANA SAP HANA Hint sap hana oracle exadata SAP HANA SDI sap hana 迁移 oracle SAP HANA 数据库学习 SAP HANA  上云 SAP HANA2.0 SAP HANA总结 SAP HCM SAP HCM学习 SAP HR sap http SAP IBP SAP IDOC sap idoc java SAP INBOX SAP IRPA SAP ISSUE sap java客户端 sap java乱码 SAP JCO NCO SAP JCO 负载均衡 SAP License sap linux客户端 sap linux系统安装教程 sap linux下配置文件 SAP List Viewer(ALV) SAP LOGON SAP LSMW SAP LSMW教程 SAP LUW SAP MASS SAP material classification SAP MDG SAP ME sap me21n增强 sap me22n增强 sap me23n增强 sap mes java SAP MII SAP MM SAP MM BAPI SAP MM 对于MRKO事务代码的几点优化建议 SAP MM 后台配置 SAP MM 特殊库存之T库存初探 SAP MM 小贴士 SAP MM/SD 业务相关 SAP MM06 SAP MM基础配置 SAP MM模块面试 SAP MRP默认值 SAP MRP默认值设置 SAP MRP配置 sap mysql SAP Native SQL SAP Nco 3 Connector 连接SAP 并接收数据 SAP NetWeaver sap netweaver 7.02 sap netweaver application server java SAP NetWeaver RFC library SAP NWBC sap nwds as java SAP ODATA SAP OData 开发实战教程 - 从入门到提高 sap oracle client SAP PA证书 SAP PI SAP PI - 同步 vs. 异步 SAP PI PO 接口调用 SAP PI PO 接口问题 SAP PI SSL证书 SAP PI&amp SAP PI/PO SAP PI/PO 系统集成 SAP PI架构 SAP PLM SAP PM SAP PM 工厂维护 SAP PO SAP PO PI 系统接口集成 SAP PO SSL证书 SAP PO 导入SSL证书 SAP PO/PI接口 sap powerdesigner SAP PO安装 SAP PP SAP project SAP PS SAP QM sap query SAP R/3 SAP R3 SAP R3 ABAP4 SAP R3 主流系统EAI接口技术剖析 sap r3的lanuage 代码 SAP REST API SAP REST JSON SAP Retail SAP RFC SAP RFC 与 Web有啥区别 SAP ROUTRE SAP RSA 加密解密 SAP S/4 SAP S/4 HANA SAP S/4 HANA Cloud Sap S/4 Hana 和Sap ERP有什么不同 SAP S/4 HANA新变化-FI数据模型 SAP S/4 HANA新变化-MM物料管理 SAP S/4 HANA新变化-SD销售与分销 SAP S/4 HANA新变化-信用管理 SAP S/4 HANA新变化-主数据:物料主数据 SAP S/4 HANA新变化-主数据:业务伙伴之后台配置 SAP S/4 HANA与SAP Business Suit SAP S/4 MM SAP S/4HANA SAP S/4HANA表结构之变 SAP S4 SAP S4 HANA SAP S4 HANA CLOUD SAP S4  有用链接 SAP S4/Cloud应用 SAP S4/HANA FICO都有哪些改变? SAP S4HANA SAP S4HANA里委外加工采购功能的变化 SAP SBO9.1 SAP SBO重装 SAP SCM EWM SAP script SAP SD SAP SD MM PP FICO SAP SD 常用表 SAP SD 基础知识之定价配置(Pricing Confi SAP SD 基础知识之计划行类别(Schedule Lin SAP SD 基础知识之物料列表与物料排除 SAP SD 基础知识之行项目类别(Item Categor SAP SD 销售中的借贷项凭证 SAP SD 信贷管理的操作流程 sap sdi mysql SAP SD常用表 SAP SD基础知识之凭证流(Document Flow) SAP SD基础知识之输出控制(Output Control SAP SD模块 SAP SD模块-送达方和售达方的区别和联系 SAP SD微观研究 SAP SHIFT SAP SICF REST SAP smartforms乱码 SAP smartforms转pdf SAP smartforms转pdf乱码 SAP SQL sap srm SAP SRM 开发 SAP SRM  函数 sap strans解析json SAP TIPS SAP UI5 SAP UI5&amp SAP Variant 配置 SAP VC SAP Web Service SAP Web Service简介与配置方法 SAP Webservice SAP WM SAP WORKFLOW SAP XI/PI SAP 案例方案分享 sap 报错 注册服务器错误 SAP 报错集合大全 SAP 标准功能 SAP 标准教材和自学方法 sap 标准委外和工序委外 sap 查看服务器文件夹 SAP 常规 SAP 常用表 SAP 常用操作 sap 成本中心下的po SAP 成都研究院 SAP 导出 HTML sap 导出系统所有的单位 SAP 登录图片修改 SAP 顶级BOM查询 sap 订单状态修改时间 SAP 端口 SAP 发票合并与拆分 sap 发送mesage SAP 反查顶级BOM SAP 反查一级BOM sap 服务器信息 SAP 功能函数 sap 供应商表 SAP 顾问宝典 SAP 函数 SAP 后台表 SAP 后台配置 sap 计划订单 sap 假脱机请求 SAP 接口 SAP 接口测试 SAP 结账流程 sap 界面创建凭证 SAP 金税接口介绍 SAP 开发 sap 流程图 退货销售订单 sap 默认屏幕变式 SAP 配置 &amp SAP 批量创建货源清单 SAP 请求号 SAP 权限 SAP 权限配置 SAP 商超订单统一管理系统 SAP 商品主数据 SAP 数据库删除 SAP 数据字典 sap 双计量单位 sap 思维导图 SAP 锁机制认识 SAP 通用功能手册 SAP 透明表 SAP 图片修改 sap 文档服务器安装 SAP 问题以及报错 SAP 物料版次 SAP 物料不一致 SAP 物料删除标记 SAP 物料在启用序列号管理或者不启用序列号管理之间快速切换 SAP 系统 sap 消耗策略999 sap 消息服务器 bat sap 小技巧 sap 新建事务 sap 新增科目表 sap 修改服务器时间格式 sap 修改许可服务器 SAP 虚拟机配置1-FI SAP 虚拟机配置2-CO SAP 虚拟机配置3-MM SAP 虚拟机配置7-WM SAP 序列号与库存关联起来? SAP 选择屏幕 SAP 选择屏幕开发 SAP 演示数据增加 SAP 业务 SAP 业务顾问成长之路 sap 一代增强 SAP 银企直连 SAP 银企直联 SAP 银行对账 sap 用户权限表 SAP 语法(Syntax) SAP 员工主数据 SAP 原材料 SAP 云 SAP 杂项 SAP 增強 SAP 增强 SAP 之门 01 SAP 中国研究院 SAP 主题 SAP 字段增强 SAP 自动化 SAP  ERROR sap  hana SAP  MM知识点 SAP  PP SAP  配置 BOM SAP Enhancement SAP Migration SAP SD SAP STMS SAP&amp SAP* sap*账号 SAP,SD SAP/ABAP SAP/ABAP 相关汇总 SAP/ABAP记录 SAP/ERP SAP/FICO sap/hana SAP_ABAP SAP_ABAP知识点 SAP_BAPI SAP_BASIS SAP_FICO sap_mm SAP_PP SAP_SD SAP_Table SAP_TCODE SAP_モジュール_MM SAP_モジュール_SD SAP_常见问题集合 SAP_常用BAPI SAP_常用表 SAP_各路小技能 SAP_基本配置 SAP_接口 SAP_视图 SAP·SD SAP2000 sap2000学习笔记 SAPabap SAP-ABAP SAP-ABAP-Function SAP-ABAP基础语法 SAP-ABAP-基础知识 SAP-ABAP小白学习日常 SAP-ALL SAP-ALV SAPB1 SAP-BASIC SAP-Basis SAP-Bassic-基础知识 SAP-C01 SAP-CO SAPECC6.0 SAPFI SAP-FI SAP-FI/CO SAP-FICO SAP-FICO-CO SAP-Fiori SAP-GR SAPGUI SAPHANA SAP-HANA saphana服务器操作系统说明 saphana服务器硬件评估 SAP-IR sapjco SAPJCO3 sapjco配置文件下载 sapjoc3 SAPLINK SAP-MDG SAP-MDG-GEN SAP-MDG-HOWTO SAP-MDG-INTEGRATION SAPMM SAP-MM SAP--MM SAP-MM-采购管理 SAP-MM-后台 SAP-MM-前台 SAP-MM问题集锦 SAP-MM-问题记录 sapmto生产模式配置及操作详解 sapnco sapnco3 receive idoc sapnco3 接收 idoc sapnco3.0 SapNwRfc.dll SAPOSS SAP-Other SAP-PM SAP-PO SAPPP SAP-PP SAP-PP模块 SAP-PS SAP-QM SAP-RETAIL SAProuter SAP-RPA SAP-SD SAPUI5 SAP-UI5 SAPUI5核心内容 SAPUI5教程 SAP-WDA SAP-WM SAP案例教程 SAP宝典 SAP报表开发工具 Report Painter SAP边做边学(自学)-看看坚持多久 SAP标准工具程序 SAP表 SAP--表相关 sap采购订单更改记录 SAP采购订单增强 sap采购申请自动转采购订单 SAP仓储单位SU SAP-操作文档 SAP策略组 sap产品 sap产品图谱 - road to sap.pdf SAP常规功能 SAP-常见问题 SAP常用BAPI SAP常用表 SAP超时设置 sap成本流怎么看 SAP创建自定义权限 SAP呆滞库存的计算 SAP代码分享 SAP单链接 SAP的NOTE sap的pod确认 sap的工作日历 SAP的技术战略 SAP的竞争战略 sap的清账是什么意思 SAP调用 SAP队列 SAP访问本机虚拟机服务器 sap放弃java sap服务器安全证书 sap服务器查看系统日志目录 sap服务器出pdf文件 sap服务器迁移性能问题 sap服务器数据库配置文件 sap服务器文件上传 sap服务器怎么安装双系统 sap服务器之间文件复制 SAP改表 SAP--概念 SAP干货分享 SAP各种BOM汇总——含义解释 SAP更改物料类型 sap更改主题 SAP工具 SAP-工作 SAP公司 sap供应商更改组 sap固定资产号码范围 SAP顾问 SAP顾问进行时 SAP顾问那些事 SAP管理 SAP核心模块 SAP后台配置 sap后台配置原因代码 SAP环境配置 sap获取系统时间 SAP基本安装 sap基于mysql安装 SAP技巧 SAP技巧集 SAP技术 SAP技术端 SAP技术文档 SAP技术小知识 SAP技术总结 SAP加解密 SAP加密 SAP架构 SAP-架构 sap假脱机打印机设置 SAP监控 SAP监控常用TCODE sap脚本运行 SAP教程 SAP接口 SAP接口 证书和密钥 SAP接口编程 SAP接口常见问题处理 SAP接口开发 SAP接口数据库 SAP接口相关设置 SAP解密 SAP界面设置 SAP经验 SAP开发 SAP-开发 sap开发需要java吗 sap开发语言 sap可以指定应用服务器 SAP客户数据 SAP客户数据导出 sap客户信贷 sap客户主数据bapi SAP-跨模块知识 SAP零售 SAP零售行业 SAP密码过期设置 sap模糊搜索闪退 SAP模块 SAP模块知识 sap内部顾问 sap内部运维 sap培训 SAP培训机构 SAP配置 SAP批量打开工单 SAP批量导出客户 SAP批量导出客户数据 SAP批量修改 sap期初导资产代码 sap清账使用反记账 SAP请求传输 SAP取历史库存(可查询期初期末库存和指定日期之库存) SAP权限管理 sap权限激活 SAP认证 SAP如何发布webservice SAP入门 SAP软件 SAP删除物料 SAP上云 sap生产工单报工 SAP实施 SAP实施攻略 SAP实施知识 SAP使用技巧 sap事务代码 sap事务代码如何收藏 SAP视频 SAP视频教程 SAP视图 SAP视图批量维护 SAP视图维护 SAP数据表 SAP数据导入导出 SAP数据分析 SAP-数据库 sap税码配置 SAP索引不存在 SAP通用技能 sap外币重估流程图 SAP维护 SAP-未分类 sap未分摊差异怎么处理 sap文化 SAP文章 SAP问题处理记录 sap无法正常启动服务器配置文件 SAP物料classification SAP物料类型 SAP物料删除 SAP物料视图批量维护 SAP物料视图维护 SAP物料特性值 SAP物料主数据 SAP稀有模块 sap系统 SAP--系统 sap系统ftp服务器下文件 SAP系统-MM模块 sap系统搭建教程 sap系统登录时没有服务器 SAP系统管理 SAP系统界面 SAP系统配置 sap系统前台数据与后台表之间 SAP系统研究 sap系统中的batch sap相关知识 SAP项目 sap项目部署到服务器 SAP-项目经验 SAP项目实施 SAP-项目实施随笔小计 SAP项目问题 sap消息服务器错误 SAP--消息号 SAP消息监控器 SAP销售订单邮件 sap销售发货的流程 sap销售凭证流mysql表 sap销售维护 SAP销售员维护 SAP小问题 SAP写入mysql SAP心得 SAP新产品系统 SAP修改已经释放了的请求号 sap虚拟机 多个服务器 sap虚拟机作为服务器 SAP选择屏幕 SAP选择屏幕开发 SAP学习 SAP业务 SAP异常处理 SAP银企直连 SAP银企直联 SAP银行账户管理(BAM) sap应用服务器超载 SAP邮件发送 SAP邮件记录 SAP邮件记录查询 SAP云平台 SAP运维 SAP-运维记录 SAP杂谈 SAP-杂谈 SAP杂项 SAP在采购和销售中的税务处理-增值税 sap增加事务代码权限 SAP增强 SAP战报 SAP战略中的机器学习 SAP知多少 SAP知识点 SAP制造集成和智能 SAP智能云ERP SAP中CK11N成本估算 sap中re凭证是什么意思 SAP中s_p99_41000062查询物料价格数据库表 SAP中报表清单导出的常用方法 SAP中的client SAP中的贷项凭证、借项凭证 SAP中的移动类型 SAP中方会计凭证解决方案 sap中国 sap中文使用手册 模块指南 SAP中销项税MWSI和MWST有什么区别? SAP中执行没有权限的事务 SAP中自动登出 SAP转储订单(STO) SAP咨询公司 SAP资讯 sap字段及描述底表 sap自带samples sap自动化 SAP自习室 SAP组连接 SAP最大用户数设置 sara SAST SAT SBO开发 SCA scala SCC4 Schema schema增强 scipy scm SCP SCP Cockpit scpi Screen SCRIPTFORM scripting Tracker SD sd bapi SD Module SDI SD常用表 SD模块 SD销售 se09 SE11索引 SE16N SE16和SE16N修改后台表数据方法 SE37 SE38 se91 SE93 Search search help security segw SELECT Select Screens select sql Selenium SEN SER01 Serial  Numbers SERVER Serverless service servlet Set SET Parameter setting SFW5 ShaderGraph sharepoint Sharepoint Or Online shell SLD SLT SM02 sm36 SM37 SM50 SM59 smartbi问题 Smartform smartforms SNOR SNP BLUEFIELD SNP 中国数据转型公司 SNUM SOA soamanager soap SoapUI 接口测试 socket SOD Software Development Notes Sort and Filter Sotap Source Scan spa Hana SPAD Spartacus标准开发 Spartacus二次开发 SPC SPED SPOOL打印 spring Spring Boot SpringBoot SPRO spss打开oracle SQL SQL server SQL Trace sqlite Sqlmap使用教程 sql-sap SQLSERVER SQLSERVER内部研究 SqlSugar sql笔记 SQL语法 sqoop SR2 sRFC srm SSCRFIELDS ssh SSIS ssl SSL证书 ST05 ST12 START STE stm32 STO Stock Type stocktransfer Stopwatch StorageLocationControl StorageType StorageUnitType StorLocControl streamsets string SU20 SU21 SU24 Submission SUBMIT sudoku SUM Suport SUSE SUSE 11 SP4 SUSE Linux SU号码 SXI_MONITOR SXMB_MONI SXMSPMAST Sybase Sybase迁移数据到Oracle Sybase数据库迁移数据到Oracle SYSAUX Sysbase system System_failure s云服务器 网站群服 T184L T681 table TABLE FUNCTION Tableau Tabstrip TCode T-Code tcp/ip TCP/UDP Socket TCPH TCP客户端显示服务器图片 TDSQL-C TeamViewer Tech 专栏 TechArt Teradata Test Automation test-tools Textbox TH_POPUP TiDB TikTok tim发文件服务器拒绝 TITLE TM TMS TODO tomcat tomcat报错 ToPrintControl Tough tp5部署虚拟机服务器 tp5服务器信息 tp5网站 服务器部署 tp5项目链接服务器数据库端口888 TR TR LIST Trace Transact-SQL transformer tree control tRFC trigger TryHackMe typescript T公司 T库存 u3d微信小游戏 u8信息服务器 UB UB STO ubuntu UD udp UD配置 uefi ugui ui UI5 Uibot Uipath UI开发 UI控件 UI自动化 unicode unity Unity 100个实用技能 Unity UGUI Unity3D Unity开发 Unity日常开发小功能 Unity微信小游戏 unity项目部署到服务器上 unity游戏开发 Unity坐标转换 unix Url URP user Userid usual UUID ux U盘 U盘文件拷贝到服务器 VALUE VARIANT VariantBOM vasp计算脚本放在服务器的位置 vb.net VBA VBA开发专栏 VBFA v-bind vbs Vendor CoA VendorCOA VendorRebate Verilog-HDL veth vhm在服务器上创建虚拟机 v-html VIEW vim visual studio visualstudio vite VKM3 VKM4 VL02N VL04 VL10B VL31N VL32N VMware VN VOFM v-on VS Code vscode v-show Vue vue.js vue2 Vue3 基础相关 vue项目如何放到服务器上 VulnHub渗透测试 WA01 WA21 WBS WCF WCN WDA WDA的配置 wdb WE20 WeAutomate Web web app Web Dynpro web gui Web IDE Web Service WebDispather WEBGUI WEBI webm webrtc WebService WEBSOCKET webvervice webview web安全 Web安全攻防 web渗透工具 WF 4.0 while Wifi热点java win10服务器系统数据库 win7系统创建ftp服务器地址 win7系统数据库服务器 Window windows windows服务 windows服务器版本系列 windows系统部署git服务器 Windows系统电脑操作 winform wireshark wlan WM WMS WM仓库管理 WM层面盘点 WM模块 WM配置 WM移动类型 Work Work Flow workflow wpf wps WR60 WRMO wsdl xaf xml xp系统怎么上传到ftp服务器 XS HANA XS Job xsdbool yara规则 yqv001-navigation Y企业信息化集成 Zabbix ZIP zk zookeeper zypper in 安装下载不了 阿里云 阿明观察 埃森哲 X SAP:智慧转型高手论剑 安鸾靶场 安全 安全分析 安全工具 安全架构 安全手册 安全与测试 安阳虚拟服务器 安装 安装报错 安装服务器系统数据库服务器 安装数据库服务器需要的文件 安装完数据库服务器为空 安卓 安卓服务器文件 案例 案卓盒子建立文件服务器 靶机 百度 办公自动化 包含服务器数据库的聊天系统 保护交货计划 保留空格 报表 报表优化 报错 报工 贝叶斯 备份及容灾 备份文件到内网服务器 被合并的公司 笔记 笔记本通过服务器提升性能 币别转换 编程 编程技术 编程世界 编程语言 编程语言排名 编辑器 编辑器转换 变更物料类型 变化 变式物料 标题 标准 标准成本历史清单 标准价 标准价和移动平均价 标准解决方案 表白网站怎么上传到服务器 表关系 表维护生成器 博弈论 补丁 补货监控 不常用 不能从服务器上获取视频文件格式 不同系统可以用一个数据库服务器吗 布局 部署 部署网页到华为云服务器 部署系统时访问服务器 财务报表 财务报表版本 财务管理 财务会计 财务科目导入 财务凭证行项目 财务增强 财务账期 采购 采购订单 采购订单和内部订单对应关系清单 采购订单价格与发票价格差异 采购订单审批 采购订单收货和订单收货区别 采购订单修改触发重新审批 采购订单增强 采购订单状态标准查询配置 采购附加费 采购附加数据 采购合同与采购计划协议关联性 采购价格 采购凭证模板 采购申请 采购审批 采购审批过程 采购收货及发票校验记录清单 采购退货 采购退货操作 采购退货测试 采购退货流程 采购退货业务 采购退货移动类型 采购信息记录 采购组 踩坑 踩坑日记 菜根发展 菜鸟日记 菜鸟之家 参数文件 参与MRP 仓库 苍穹ERP 操作符 操作系统 测绘程序 测试 测试工程师 测试工具 测试环境 策略组 层级查询 查看ftp服务器里的文件 查看服务器上文件命令 查询分析器 查询服务器系统类型有哪些 查找代码段 查找增强点 差异 差异分析 产品 产品成本估算 产品成本核算号 产品创新 产品经理 产品驱动增长 产品运营 常见端口 常见问题 常用bapi 常用sql 常用函数 常用数据类型 常用问题收集 常用自建函数 超自动化 成本对象 成本分割 成本估价历史清单 成本估算 成本估算的取价逻辑 成本核算表计算间接费用 成本核算结构 成本核算中BOM和工艺路线 成本收集器 成本要素 成本要素不可更改 成本中心标准报表 成本中心实际/计划/差异报表 成都最稳定的dns服务器地址 程序/PROGRAM 程序导出 程序人生 程序人生 ABAPer 程序人生和职场发展 程序设计 程序下载 程序员 程序员职业发展 持久类 持续集成 冲销扣料 初级成本要素 初阶 初学 初学者 处理外向交货单 触发器 传媒 传输 传输层 传输请求 传输日期 串口通信 创建服务器共享文件夹 创建物料主数据时的视图状态 创新 创新案例 创新战略 垂直居中 磁盘管理虚拟磁盘服务器 次级成本要素 从u盘引导进入linux6 存储 错误处理 错误解决 达梦 打印 打印次数 打印机 大厂面试 大庆服务器维修 大数据 大数据分析 大数据工程师 大数据可视化 大小写 大型服务器安装什么系统 代码规范 代码片段 代码在哪用到了 带格式的邮件附件 带你走进SAP项目 单片机 单片机系列 单位 单文件 单元测试 弹出框问题 弹性计算 导出电子表格问题 导出内表数据至Excel文件中 导出期末或指定日期库存 导入 导入license 导入数据库显示服务器发生意外 倒冲 到期发票清单VF04功能 登陆语言 登录oa系统输入服务器地址 登录日志怎么实现 低代码 低功耗文件服务器 地球 递归 第三方 第三期间 第一个ABAP程序 点击ftp服务器的文件弹出登录界面 电话 电商 调试 调试器 调用sap接口 调用接口 调用子屏幕修主屏幕 调优 调制与编码策略 鼎信诺显示连接服务器失败 订单 定价 定价过程 定价例程 定价值 定时采用ajax方式获得数据库 定时器 定时任务 定时同步文件到ftp服务器 定义 定义详解 动态安全库存 动态获取字段名 动态类 动态属性和事件绑定 冻结功能 冻结库存 冻结库存转库 读取文件内表数据 端口 队列 队列末尾 对象 对象不支持属性或方法dbzz.html 多扣料冲销 多流 多人共用 不能访问目录 多送或者少送 多线程 多引擎数据库管理系统 多源异构数据汇聚平台 多重科目分配 俄罗斯报表 二代增强 二级标题-003-Pacemaker 发票处理系统 发票冻结原因 发票冻结原因及解除冻结 发票小金额差异 发票自动化 翻译 反冲 反记账 反记账数据转换 返工 泛微OA调用SAPwebservice详解 泛微OA开发 方便小函数 方格子无盘服务器怎么用 访问后台接口 非技术区 非技术文章 非限制库存 分包后续调整 分布式 分类 分类账 分配表 分配分摊 分三个屏幕的OOALV 分析云 分享学习 服务 服务类采购订单的收货审批确认 服务器 服务器 文件类型 服务器 稳定 重要性 服务器1g内存装什么系统 服务器cpu只显示一个核 服务器host文件目录 服务器raid1做系统 服务器vos系统怎么装 服务器安全证书登陆失败怎么办 服务器安装系统sles系统 服务器安装系统如何选择网关 服务器安卓系统安装教程 服务器被攻击 文件被删除 服务器比对数据库差异文件 服务器标识信息 服务器部署的参数文档 服务器操作系统套什么定额 服务器操作系统用什么好 服务器操作系统与数据库 服务器查看操作系统类型 服务器查看数据库日志文件 服务器查文件 服务器出生点配置文件 服务器传送过来的是什么信息 服务器搭建网站方案500字 服务器大内存系统吗 服务器的ftp数据库信息 服务器的参数配置文件 服务器的地址信息 服务器的共享文件地址 服务器的系统文件怎么恢复出厂设置密码 服务器登录需要信息吗 服务器定时任务系统 服务器读取不了文件 服务器放文件 服务器故障修复费用需要摊销吗 服务器光纤存储系统 服务器接入协议是什么 服务器快照能代替网站备份吗 服务器扩容文档说明 服务器链接数据库配置文件 服务器两个网站公用一个数据库 服务器默认文档 服务器内存扩展板位置 服务器内存条的种类文档 服务器内存性能好 服务器内存在哪个位置 服务器内核文件在哪 服务器迁移操作系统 服务器迁移需要哪些操作系统 服务器如何查看文件个数据库文件夹 服务器如何分多个文件 服务器设计虚拟内存 服务器设置上传文件大小 服务器适合安装深度系统deepin 服务器数据库查看版本信息 服务器数据库查看版本信息失败 服务器数据库的文件读取数据库 服务器数据库系统 服务器数据库协议 服务器数据库用什么系统 服务器数据系统 服务器网站关联数据库 服务器微端位置 服务器维护 吸尘器 服务器维护费入什么科目 服务器文件地址 服务器无盘镜像导入 服务器物理机部署 服务器物理内存只增不降 服务器物理组成 服务器系统安全方案 服务器系统安装ansys 服务器系统安装oracle数据库 服务器系统安装报价 服务器系统版本选择 服务器系统方案 服务器系统和数据库的用处 服务器系统架构讲解 服务器系统盘50g什么意思 服务器系统盘大文件检测指令 服务器系统盘分多少 服务器系统数据库安装 服务器系统性能灯 服务器系统有多大 服务器系统与数据库 服务器系统怎么恢复出厂设置 服务器修改mime类型 服务器修改密码规则 服务器虚拟化与企业私有云 服务器虚拟机的c盘怎么加 服务器选择系统版本 服务器与本地文件共享 服务器怎么清除日志文件 服务器只读团体字信息 服务器中文档存储在哪 服务器主板坏了怎么维修 服务器主板维修电子书 服务器装系统快吗 服务器装系统无显示屏 服务器租赁文档 服装信息化 浮点运算 福建工程学院计算机网络技术期末考试试卷 辅助线框 付款 付款流程 付款条款 付款信息 负号前置 负库存的相关设定 复合角色 复制创建采购申请 复制控制 复制文件到服务器 内容不足 概念整理 感悟 高级退货管理 高阶 高可用架构 高斯坐标 高性能服务器一体机 高性能有限元计算服务器 个人经历 个人开发 个税系统代理服务器参数是什么 个性化定制 给标准报表添加字段 给一个oracle账号密码是什么 更改成本要素类别 更改物料类型 更新服务器数据库文件位置 工厂 工厂管理 工厂内库存转移 工厂日历 工具 工具集锦 工具类 工具使用 工具使用指南 工具手册 工具系列 工业软件 工艺路线 工资发放和结算 工资计提 工作 工作笔记 工作量法 工作流程自动化 工作流自动化解决方案 工作杂记 工作总结 公式计算 公司财务系统html 公司代码货币 公司服务器可以查询员工哪些信息 公司间STO 公司间STO‘ 公司间过账 公有云-华为 功能 功能测试 功能开发说明书 供应链 供应链管理 供应商 供应商采购冻结 供应商评估 供应商清单输出 供应商子范围 沟通能力 购买云服务器配置项目 估价容差测试 固定点算术 固定资产 固定资产会计 固定资产折旧 固定资产折旧码 顾问之路 挂微群发软件需要什么服务器信 关闭 关系模型 关于R/3 关于赛锐信息 关于信用管理--信用更新 管理 管理数据库 广播 消息 没有服务器 归档 规格说明书 国产器件 国产软件 国产数据库 国科大学习 国内服务器内存缓冲芯片 国外服务器显示数据库 哈希算法 海康4200服务器进不去系统 海口服务器系统租用 海纳百川 含税价 邯郸虚拟服务器 函数 函数/FUNCTION 函数技巧 函数模块 函数式编程 好书推荐 合作案例 合作伙伴 和车神哥一起学 核心主数据 黑盒测试 黑名单 恨ta就教ta  SAP 红蓝攻防篇 后端 后端开发 后鸿沟时代 后台Job 后台表 后台导出表数据 后台服务器 后台开发 后台作业 胡思乱想 湖仓一体 互联网-开源框架 华为 华为2012服务器系统安装教程 华为hana服务器型号齐全 华为服务器gpu芯片 华为服务器raid1装系统 华为服务器安装2012系统怎么分区 华为服务器安装nas系统 华为服务器扩容内存进不去系统 华为服务器修改root密码 华为无线局域网 华为云 华为云服务器更换操作系统 华为云服务器还需要确定位置吗 华为云服务器系统备份 华为云服务器自己维护吗 华为怎么安装服务器系统版本 环境搭建 缓存 汇率维护 汇率转换 汇总 会计 会计分录 会计基础资料 会计科目 会计科目表 会计科目删除 会计凭证批量导出 会计凭证清账 会计凭证替代 会计凭证中的注释项目 会用到的 绘图 绘图工具 惠普服务器G8系列做raid 活动 伙伴功能 货币过期 货币类型 货币停用 货源清单 获取窗体下的所有控件 获取汇率 机器人流程自动化 机器学习 鸡肋 积累 基本单位 基本配置 基础 基础模块 基础入门 基于收货的发票校验配置过程 基准日期 集成 集团货币 集中采购 己建立BOM清单 计划策略 计划策略40 计划订单 计划时界应用 计划时界应用测试 计划数量小于收货或发票数量 计划协议 计划行类别 计划行类别中请求/装配 计划行统计清单 计量单位 计入物料成本 计算步骤 计算机 计算机毕业设计 计算机基础 计算机基础知识 计算机科学分成什么模块 计算机体系 计算机图书 计算机网络 计算机网络 王道 计算机网络rip路由表题目 计算机网络理论概述 计算机网络原理(谢希仁第八版) 计算机网络远程管理作业答案 计算机维护 计算机信息管理自考-04741计算机网络原理 计算机自学考试 记录问题 记账冻结 记账码 技能 技巧 技术 技术分享 技术干货 技术交流 技术类 技术沙龙 技术渗透 技术文档 技术总结 寄售 寄售交货 寄售结算规则 寄售模式 加密 加密算法 加前导零 加速器 价格修改历史 架构 架构设计 架设企业文件服务器 假期日历 监控 监控服务器系统备份 监控服务器系统密码忘了怎么办 监控平台 监控事件 监控系统 监控系统里服务器 监控系统是否要服务器 减值准备 检验点 检验计划 检验类型 检验类型89 检验批 检验批系统状态 简单窗体实现 简单的数据库管理系统 用什么云服务器 简述客户 服务器系统的组成 建议组件分配到BOM 渐变色UI描边 将服务器上数据库复制到本地文件 将已有项目转移到云服务器 交互 交货单 交货计划固定 交货计划期间保护 角色 角色继承 角色设计 教程 教育电商 阶梯价格 接管日期 接口 接口测试 接口方式 接口问题处理 接口-银企直连 结算会计年度 截取年月日在hana中怎么写 解决方案 界面 借贷 金丹期 金蝶 金蝶 系统服务器繁忙 金蝶K3 金蝶二次开发好跳槽吗 金蝶服务器维护 金蝶云星空操作手册 金蝶中间件部署报栈溢出 金额转换 金税接口 仅在总账中过账 仅装配 仅组件 进口采购 进入文档服务器不能输入密码 进销存 进销存报表 进销存系统怎么部署到自己服务器 经历 经验 经验分享 经验总结 精诚MES 精诚智慧工厂 精选 境外服务器稳定 镜像 玖章算术 就是玩儿 矩阵 聚合函数 聚集函数 开发 开发笔记 开发工具 开发管理报表 开发环境 开发平台 开发语言 开发者 开发知识点 开源 开源ERP 开源-JDK-镜像 开源系列谈 开源项目 看板 考试 考试复习 考研 科技 科技公司 科目行项目不显示 可配置物料 客供料 客户 客户冻结 客户端往服务器写文件 客户端修改opc服务器的数据 客户服务 客户-服务器数据库系统举例 客户服务器系统的特点是 客户关系处理能力 客户关系管理 客户贸易伙伴 客户信贷管理解析 客户主数据 课程 课程笔记 课堂笔记 空调控制系统节点服务器 空间管路 口碑效应 库存地点MRP 库存地点权限控制 库存管理 库存决定 库存批次 库存需求天数关系 库龄 跨公司STO 跨国跨公司间转储 块设备驱动 快捷 快捷键 快手服务器协议 快速定制 框架 鲲鹏服务器系统重装 扩充存储地点 扩展 扩展知识 来也科技 蓝桥杯 蓝牙 蓝牙A2dp 浪点服务器芯片 乐鑫 类型强转 理解 历史库存sap 利润表 利用云服务器传递信息 连接 链表 良仓太炎共创 两步法拣配 料主数据中的屏幕字段 列表 列存索引 列存引擎 零基础快速学习 ABAP 零散知识 零售 零售行业 零碎(凑数)的算法[题] 零停机 流程自动化 流水号 流水码 流星的程序集 漏洞预警 录屏 录像机显示服务器 乱码 论文 论文阅读笔记 蚂蚁无线管理器服务器 买个服务器来挂协议 买了一个服务器修改密码 漫谈计算机网络 贸易伙伴的应用 没有MANDT字段 没有中间凭证冲销 媒体 每日摸鱼新闻 门店视图 门店主数据 免费流量获取 免关税 面试 面向对象编程 面向对象方法 敏捷 敏捷开发 命名规范 模板语法 模块 模块测试 莫队 莫队算法 目标跟踪 内表 内表类型 内表字段 内部订单 内部订单清单 内部订单删除问题 内部订单月结差异 内存管理 内存数据库 内存图片 内核 内核驱动 内核驱动开发记录 内嵌Excel 内容服务 内容服务平台 内容服务软件 内容库 内外码转换 内网 内网渗透 内向交货单 那个网站的服务器不限制内容 能不能用pe安装服务器系统安装系统 能力建设 能源 年结 爬虫 排行榜 排序算法 盘点 盘点流程 培训 配额协议 配置 配置SAP服务器外网登陆以及网络故障解决示例 配置笔记 配置高性能文件服务器方案 批次 批次拆分 批次管理 批次号 批次确定 批次特定单位 批次特性 批导程序模板 批导模板下载 批量采购冻结 批量导出表数据 批量更改会计凭证文本 批量维护 批量用户账户锁定 平行记账 凭证冲销的种类和处理逻辑 凭证打印 凭证流 凭证状态 凭证状态S 屏幕(Dialog)开发 屏幕SCREEN字段属性 屏幕程序 屏幕设计 破坏式创新 破解 期初库存金额 期初资产数据导入 期刊阅读 期末不挂科 期末复习 期末库存金额 其他 其他应付款-代扣代缴 其他知识点 奇技淫巧 麒麟服务器数据库协议 企业/办公/职场 企业安全 企业服务器文件管理 企业管理软件 企业级应用 企业解决方案 企业内部控制 企业内容管理 企业软件 企业微信 企业文件服务器备份 企业系统 企业信息化 企业信息化前沿 企业资源计划 启用WEBGUI服务 迁移驾驶舱 前端 前端基础练手小项目 前端架构 前端开发 前端开发相关 前端框架 前后端 前台操作 嵌入式 嵌入式开发 嵌入式学习--STM32 嵌入式硬件 清软英泰plm服务器安装文档 清帐 清账 清账凭证 请求 请求传输再还原 请求号 区块链 区块链技术 区域菜单 驱动开发 取价逻辑 取消审批 取样策略 取值相关 去前导零 全角半角转换 全球最大sap hana系统建立在以下哪个厂商的服务器产品上 全球最大的采购服务平台 权限 权限对象 权限管理 权限合规检查系统 权限控制 権限 缺料提醒及警报 热点开启 流程 人工智能 日常ABAP开发记录 日常Bug 日常工作 日常记录 日常学习工作经验分享 日常知识分享 日记 日历 日期 日期函数 容器 容器服务 容灾 如何安装华为服务器系统软件 如何把项目部署到内网服务器 如何传输本地文件到服务器 如何从服务器上更新文件 如何导出序时账 如何读取服务器文件数据 如何复制服务器数据库文件大小 如何将CRM系统上传到服务器 如何将hana数据同步到oracle 如何设置sap生产订单自动关闭 如何统计输出条目数量 如何修改服务器root密码 如何知道有哪些物料存在BOM 入后在服务器修改数据库 入库 入门 入侵一个网站的服务器拿数据 入行SAP咨询 入职甲方 软件 软件安全 软件部署 软件测试 软件测试知识 软件程序 软件工程 软件教程视频集合 软件开发 软件生态 软件下载 软件显示未找到服务器 软考 软实力 软硬件运维 赛锐信息 三代增强 扫描代码 删除 删除记录 商城小程序买哪种服务器 商品主数据 商务智能 商业软件 商业智能 上传 上传附件出错 上传图片 上传文件到云服务器存储路径 上架策略B 上架策略C 上架策略P 上线 上云 设备维修 设计模式 设计与维护类 设置参数缺省值 社保管理系统连接不上服务器 社区活动 深度学习 深度优先 深澜系统服务器架构 审计导出表数据 审计序时账 审批策略 审批代码 渗透 渗透笔记 渗透测试 渗透测试自学日志之基础原理篇 渗透工具之信息收集 升级 生产版本 生产版本排序规则 生产版本选择规则 生产版本选择逻辑 生产版本选择顺序 生产版本优先顺序 生产成本收集 生产排程 生产系统服务器主机名怎么看 生活 生活感悟 什么情况使用一次性供应商及客户 什么是BAPI 什么是序时账 时间比较 时间对象 时序数据库 实施 实施SAP 实施项目 实时集成变式 实时库存 实体服务器怎么配置文件 实习 实习生 实战SAP程序开发 使用感受 使用决策 事务代码 事务代码LX04 事务代码WRCR 事务技术名称的显示与隐藏 事务码/TCODE 视觉语言导航 视频 视频处理 视频监控选择服务器的配置文件 视图 收货冲销 收货处理 手动加载ICU库 手机主服务器怎么配置文件 售后管理 输入历史记录 暑假复习 树查询 树莓派 数独 数据安全 数据仓库 数据仓库学习分享 数据从hana倒回Oracle的方法 数据导入 数据导入和处理 数据分析 数据分析 + 机器学习 数据分页 数据服务器 操作系统 数据服务器什么系统软件 数据服务器文件夹 数据服务器与文件服务器 数据格式 数据湖 数据结构 数据结构与算法 数据科学入门 数据可视化 数据库 数据库备份到文件服务器 数据库表字段 数据库操作 数据库的文件服务器配置 数据库服务器部署文档 数据库服务器网页 数据库服务器系统 数据库服务器系统崩溃 数据库服务器系统的 研发 数据库服务器系统软件 数据库服务器压缩文件 数据库管理与维护 数据库规划、部署 数据库和服务器什么协议 数据库和服务器系统怎么安装 数据库技术 数据库架构 数据库监控 数据库监控软件 数据库开发 数据库文件共享服务器配置 数据库系统概论 数据库系统原理 数据库系统怎么与软件连接到服务器 数据库与服务器通讯协议 数据库最新排名 数据类型 数据链路层 数据浏览器的字段名称显示 数据迁移 数据迁移驾驶舱 数据迁移完整性检查 数据挖掘 数据治理 数据中台 数据中心IDC架构及容灾与备份 数据重置 数据字典 数学建模篇 数字化 数字化管理 数字化转型 数字货币 数字业务整合 双计量单位 双路服务器只显示一半内存 双碳 双网文件服务器 水晶报表 税改 税率 税友报税软件让修改服务器地址 私有云虚拟化服务器群 思爱普 思科里服务器的dns配置文件 死锁 四代增强 四元数 搜索帮助 搜索引擎 搜索引擎营销 速食 算法 随便看看 随机方向 随机数 损益表 所见即所得的打印功能 锁定 锁定事务代码 抬头文本被强制清空 探测服务器操作系统版本 特殊库存 特殊移动标记 特性 腾讯云 提升工作效率的工具 题解 替代 替代/校验/BTE 天正服务器不显示 添加列到指定位置 条件 条件表 条件类型 条码系统 跳槽 跳过代码 贴花 通过SQVI增加表格字段 通信协议 同步 同方服务器系统安装 统驭科目理解 透明表 图论 图像处理 吐槽 外币评估 外币评估记账 外部采购 外部断点 外贸管理软件 外贸软件 外向交货单 外协加工 外语能力 完美汽配管理系统v12服务器 完整的采购订单业务信息凭证流 玩转STM32 万彩录屏服务器不稳定 网吧无盘用华为服务器 网卡 网卡驱动 网络 网络安全 网络安全学习 网络存储服务器的系统 网络管理定时备份服务器网站文件 网络接口 网络配置 网络通信 网络拓扑仿真模拟 网络文件服务器有哪些 网络协议 网络协议栈 网络设备 网络规划 网络工具开发 网络营销 网页 服务器 数据库 网页如何从服务器获取数据 网页与服务器数据库 网易数帆精彩活动 网站服务器存储数据库吗 网站服务器没有安装数据库 网站服务器没有数据库备份 网站服务器与系统部署策略 网站跨域访问服务器数据库 网站上传到服务器需要上传数据库 网站数据库断连重启服务器 网站虚拟服务器1核1g速度 网站需要数据库服务器吗 网站与数据库不在同一服务器 网站云服务器需要数据库吗 往来余额结转 往年购置资产 微前端 微软 微软azure 微信 微信小程序 为服务器安装操作系统的流程图解 为什么文件上传不了服务器上 为资产分类定义折旧范围 维护视图 维护思路 委托加工 委托租赁云服务器协议 委外 委外加工 委外加工采购流程里副产品的收货 委外库存 委外销售订单库存 未能找到使用主机名称的服务器 未能注册模块 未清项管理 文本编辑器 文本表 文档管理 文档管理软件 文档协作 文档资料 文华软件登录显示请选择服务器 文件存储服务器 方案 文件服务器 华为 文件服务器 内存需求 文件服务器 内存需求大么 文件服务器报码表xls 文件服务器存储 文件服务器放在哪里 文件服务器和nas存储 文件服务器和数据库的区别 文件服务器可以存储的文件类型有 文件服务器内存 文件服务器内存要大吗 文件服务器网盘 文件服务器为何存不了大文件 文件服务器帐号切换 文件服务器属于固定资产吗 文件共享服务器所需虚拟机资源 文件名带中文上传ftp服务器变乱码 文件虚拟服务器 文件一般存在数据库还是服务器 问答 问题 问题处理 问题记录 问题解决 问题总结 我的SAP系统开发里程碑 我的问题 无代码 无代码开发 无法输入事务代码 无盘服务器工作流程 无盘服务器内存多大好 无盘服务器配置20台 无线监控设置smtp服务器 无值记账 物定工厂物料状态 物联网 物料 物料编号 物料编码 物料编码更改 物料变式 物料单位更改 物料分类账 物料管理 物料价格清单 物料库存/需求及供应天 物料凭证 物料凭证类型和交易/事件类型 物料帐 物料账 物料账期 物料主数据 物料主数据视图 物料主数据视图维护状态 物料组 物料组的分配规则 物流 习题 系统/网络/运维 系统安全 系统安装 系统服务器常见出厂密码有哪些 系统集成 系统架构 系统开发 系统未配置文件服务器是啥意思 系统相关 系统云端服务器 系统怎么访问数据库服务器 系统中的缺料情况及控制 下架策略A 下架策略M 下拉框 下载 下载程序 先后顺序 先进的数据库服务器操作系统 先进生产力工具大全 现金管理 现金流量表 线段树 线性规划 响应函数 向上取整 向下取整 项目 项目表 项目部署在服务器上的形式 项目管理 项目迁移 项目前端 项目实施经验贴 项目实战 消耗冲销 消息服务器待办事项数据库 消息控制采购订单 销售 销售(SD)凭证流 销售订单 销售订单冻结 销售订单库存 销售订单项目类别 销售订单信用冻结 销售订单中的条件类型 销售发货冻结 销售发货可用性检查 销售交货 销售开票冻结 销售税 销售项目开票 销售员 小白 小白的SAP问题积累 小程序 小程序云服务器磁盘怎么分区 小丁的blog 小记 小结 小项目(新手可做) 小型服务器的操作系统 小型企业网络存储服务器系统方案 效率 协议 心得感悟 新程序员 新基建 新建表维护程序SM30 新收入准则 新手时期 新闻 新语法 新增漏洞报告 新增移动类型科目确定配置 新总帐 薪酬核算 薪酬计提和发放 信贷 信息安全 信息安全顶会论文导读 信息化 信息化建设 信息记录 信息收集 信用额度 信用管理 行业 行业客户信息 行业趋势 性能测试 性能优化 修改,F4帮助,添加按钮 修改Q系统代码 修改表数据 修改服务器端的访问模式 修改服务器网络 修改服务器信息使密钥不过期 修改记录 修改交货单 修改历史 修改数据库安装的服务器 系统时间 修改物料组 虚拟服务器需要网关吗 虚拟服务器英文翻译 虚拟服务器资源 虚拟服务器资源配置 虚拟服务器最大磁盘2TB 虚拟化 虚拟机 虚拟机迁移后服务器无法启动 虚拟机如何做服务器系统 需求分析 需求类型 需要访问其他服务器信息吗 序列号 序列号管理 序列号清单 序时账导出方法 序时账核对 选型 选择屏幕 选择屏幕打开文件路径 学术前沿 学习 学习ABAP笔记 学习笔记 学习方法 学习人生 学习问题 学校三级项目 循环 压力测试 压力测试 闪存 亚马逊 亚马逊云科技 研发管理 研发效能 业财一体化 业务 业务处理 业务范围 业务分析 业务功能 业务顾问 业务顾问的小需求 业务伙伴 业务价值 一般总账科目数据转换 一次性供应商及客户 一次性供应商及客户应用经验 一个服务器 定时从各个系统取数据 一键还原服务器系统 一台服务器能存放几个系统 一台服务器如何部署多个项目 一套适合 SAP UI5 开发人员循序渐进的学习教程 医药行业 移动开发 移动类型 移动类型101/102 移动类型325 移动类型343 移动类型配置 移动平均价 异步Function 异常 异速联客户端未获取到服务器信息 音频编码解码 音视频 音视频开发 银企直连 银企直连接口 银企直联 银行 银行账户管理 隐式增强 印度 印资企业 应付职工薪酬 应收应付 应用设计 应用性能监控 英一 英语 硬件服务器搭建系统步骤 用户 用户定义的消息搜索配置 用友 优化 由于质量原因而冻结 邮件发送 邮件服务器及相关配置 邮件合并居中,框线 邮件预警 游戏 游戏服务器修改其他玩家数据 游戏开发 游戏引擎 有没有便宜一点的网站服务器 有限元模拟 余额不平 与SAP集成相关 语言 语言概览 语音 预留 预算管理 预制凭证 原创 原创项目 原力计划 源码 源码分析 月结 阅读分享 云 文件 服务器 文件怎么恢复出厂设置密码 云ERP 云安全 云备份 云财经服务器维护 云存储系统服务器版安装 云打印 云端 云服务 云服务器 云服务器 ftp上传文件大小 云服务器 选择什么系统版本 云服务器 重做系统软件 云服务器1和1g装什么系统好 云服务器cpu系列 云服务器ecs销售渠道 云服务器ubuntu修改密码 云服务器安装其他版本系统 云服务器部署mqtt协议通信 云服务器部署tomcat文件修改 云服务器磁盘怎么安装系统 云服务器存放位置 云服务器搭建推流系统 云服务器可以存放文件吗 云服务器免费suse系统 云服务器哪种系统好用 云服务器如何修改ssh密码是什么 云服务器软件文件管理 云服务器数据库密码修改zoc 云服务器网络配置信息查询 云服务器维护安全管理制度 云服务器物理部署位置 云服务器系统类别怎么选 云服务器系统租赁费用 云服务器修改ssh密码 云服务器需要装系统吗 云服务器怎么存文件大小 云服务器怎么多人进去编辑文档 云服务器怎么设置数据库文件 云服务器转租赁协议 云基础架构 云计算 云计算/大数据 云解决方案 云排产 云平台 云文档管理 云文档管理系统 云原生 云运维&&云架构 运算符 运维 运维开发 运维实施 运维系统 服务器监控 运维相关 运行效率 杂货铺 杂记 杂谈 杂项 再次冲销 在服务器删除的文件 恢复出厂设置密码 在服务器上建一个文件夹 在建工程 在建工程期初数据 在没有配置的dns服务器响应之后名称 在制品 怎么看系统服务器类型 怎么修改存储在服务器的数据 怎么修改服务器php版本信息 怎么在服务器上备份数据库文件在哪里 怎么在服务器上复制网站 怎么找到服务器的文档 怎样读取服务器上的数据库文件 怎样修改美国的服务器节点 增长策略 增长黑客 增强 增删改查 增值税 增值税调整 掌握物料库存,需求及供应情况 账号 账期设置 账期未开 折旧记账数据不在BSEG 正确使用一次性供应商及客户 正则表达式 证书 知识分享 知识管理 知识库 知识图谱 直线折旧法 职场 职场和发展 职业 职业发展 只存放文件的服务器 指纹识别 指纹字典 指针 制造 制造商物料 质量部门 质量管理 质量信息记录 质量证书 智慧企业 智能开发 智能运维 智能制造IT规划 智能制造执行系统 中国本地化内容 中间件 中阶 中维监控显示无法连接服务器失败怎么办 中文名称的文件传不到ftp服务器 中小企业 中小型网站服务器搭建方案 中转 重复打印 重复制造 重置期初数据 重置业务数据 重置主数据 重置资产会计数据 主检验特性 主批次 主数据 主数据导入 注册机 注解 注塑行业ERP 注意事项 转换Lookup功能 转义字符 转载 装服务器得时候选择系统版本 状态栏 咨询 资产 资产负债表 资产会计 资产接管 资产年初切换上线 资产折旧 资金 资料 资讯 子屏幕 字典 字段符号 字符操作 字符串 字符串拆分 字符串前导0 字节跳动 自动补货 自动创建交货单 自动登录SAPGUI 自动化 自动化测试 自动化工具 自动清账 自动邮件 自考 自然语言处理 自学成才 综合 综合资源 总结 总账 总账科目 总账行项目中凭证缺失 总账余额结转 租赁mt4虚拟服务器 组件 组织架构 组织结构 最大限制 最佳业务实践 最具性价比的方式 作业返冲 作业价格计算 坐标反算