ABAP 的 CASE 语句

647 阅读6分钟

原文链接:The CASE Statement in ABAP

ABAP 中的 CASE 语句提供了以干净且有组织的方式处理不同情况的能力。

关于结构化,编写 CASE 语句比一系列的 IF 语句具有明显的优势,因为 CASE 语句减少了必要的检查并生成结构化良好的代码。然而,使用 CASE 语句必须小心处理,否则结构良好的方法与结构良好的体系结构相矛盾。我们将在这篇博文中介绍用于 ABAP 开发的 CASE 的各种用法。

CASE 还是 IF

IF 语句相比,CASE 语句的语法在代码简洁方面有两大优势。CASE 语句总是可以用一组 IF 块来代替,但反过来却不可能,因为 CASE 语句只能检查单个变量的特定值,而在 IF 语句中,可以检查任何类型的条件和组合条件。

因此,在可能和有用的情况下,应首选 CASE 语句。在设计上,CASE 语句在方法中创建了一个易于遵循的结构,因为每个处理过的情况都由一个新的 WHEN 块引入。由于唯一的操作是进行相等比较,因此所有的比较都具有隐含的正向措辞。因此,不会出现倒置或其他否定形式,从而更容易跟踪控制流。

CASE 语句尤其适用于枚举或类似的值集。它们包含一组定义明确的可能情况,这些情况定义了可能的 WHEN 块。此外,当扩展枚举或在范围中添加新情况时,可以通过添加新的 WHEN 块来轻松增强 CASE。由于这种方法对其他情况的影响很小,因此引入错误的几率也很低。

尽管 CASE 语句提供了一个良好的结构,但这些语句在方法内部往往有更多的逻辑,而不是识别相关情况并将进一步处理委托给另一个方法。这种情况与单一责任原则相矛盾,因为方法确实有不止一个需要更改的原因。当添加另一个 CASE 时,以及当所涵盖 case 块之一的处理发生变化时,都必须进行更改。下面列出了一个未考虑单一责任原则的示例。

CASE vehicle_type.

   WHEN 'TRAIN'.
     duration = waiting + distance / speed + stops * wait_at_stop.

   WHEN 'PLANE'.
     duration = waiting * 2 + ( distance / speed ).

   WHEN 'CAR'.
     duration = distance / speed.

ENDCASE.

在本例中,每当有新的车辆类型添加到范围中时,就必须更改方法。此外,当计算持续时间发生变化时(例如,包括停车前减速),也必须更改方法。因此,改变的原因不止一个。总之,一个方法几乎不应该比情况本身做得更多。考虑到这一原则,每个 WHEN 块只应委托给另一个实现计算的方法,如图所示。

CASE vehicle_type.

   WHEN 'TRAIN'.
     duration = calculate_train_duration( ).

WHEN 'PLANE'.
     duration = calculate_plane_duration( ).

   WHEN 'CAR'.
     duration = calculate_car_duration( ).

ENDCASE.

有了最后一个改动,该方法只需识别相关 case 块,并委托相应的处理。不过,在这个示例中,我们的所有计算都相当简单;基于决策处理,代码可能会变得相当复杂。此外,将 case 方法与实际计算分开测试会更容易,因为需要考虑的 case 块会更少。

CASE or SWITCH

有时,CASE 块只是用来将一个特定字段值赋值给一个更通用的字段值,这可能是因为类型已不再相关,或者是为了给用户提供一个单一的值。为此,我们引入了 SWITCH 操作,它可以用作内联赋值,如下图所示。

DATA(id_to_display) = SWITCH string( id_type
                      WHEN 'EMPLOYEE' THEN partner-employee_id
                      WHEN 'EXT_WORK' THEN partner-assignment_id
                      WHEN 'CUSTOMER' THEN partner-customer_id
                      ELSE THROW unknown_type( ).

与其他操作符一样,SWITCH 也可以内联使用,例如,用于允许更改 SWITCH 结果的方法参数。适用的规则与其他操作符相同。一开始,尽可能使用内联选项似乎很方便。遗憾的是,对参数进行非繁琐的赋值可能会导致代码难以遵循和维护。

前面的赋值并不复杂,只涉及 id_type 的三种可能值。不过,该语句还是用了好几行,如果嵌入到另一个方法调用中,就很难理解了。SWITCH 可以节省空间,并在不增加价值的情况下减少语句的数量。但是,您很容易过度使用这个操作符,一般来说,您不应该在另一个函数调用中使用 SWITCH。相反,应该将结果存储在自己的变量中,这样就可以引入一个额外的名称。

不过,由于 SWITCH 提供了一种编写 CASE 语句的简捷方法,程序员往往会多次使用 SWITCH。由于上一节中提到的原因,重复使用相同的 CASESWITCH 语句有一些缺点需要考虑。SWITCH 中使用的枚举可能会更改,所有用户都需要更改。因此,应减少 SWITCH 的占用空间,从而使枚举的更改导致其他方法的更改量最小。在自己的方法中提取操作符,重点是识别当前情况并返回正确的值,这样可以更好地分离操作符,而枚举中的变化只会导致封装枚举的单个开关发生变化。

最后,我们必须做出同样的决定:是使用 SWITCH 的单一方法最合理,还是将不同的情况隐藏在工厂方法中并创建自己的类最合理?

同一 CASE 的多种用途

CASE 的优势在于创建了一种结构合理的方法,易于遵循,而且往往能直接应对不同的情况。遗憾的是,由于这些优点,同一个 CASE 经常被重复使用多次。上述 CASEIF 部分中的第二个示例确定了一种车辆类型,该类型的持续时间应易于计算。基于类型的区分仅适用于该计算,而不会在任何其他方法中重复使用,这一点不太可能。

由于 CASE 使得识别案例变得非常容易,因此它经常被重复使用多次,从而违反了 "不重复原则"。遵守这一原则尤为重要,因为代码会发生变化。如果复制,很少会对这些副本进行相应的调整,这样就会产生不一致的行为。

CASE 的重复不能完全相同。有时,只有部分案例在另一处重复,这也是应该避免的。在面向对象的环境中,避免重复的正确方法是多态性。罗伯特-C-马丁(Robert C. Martin)甚至建议将 CASE 语句隐藏在类的工厂方法中,绝不重复。

如下图所示,改编后的示例将 CASE 移至工厂方法中,从而将不同类型隐藏在工厂本身中。这样,条件就被多态性取代了。

CASE vehicle_type.

   WHEN 'TRAIN'.
     instance = new train( ).
   WHEN 'PLANE'.
     instance = new plane( ).
   WHEN 'CAR'.
     instance = new car( ).

ENDCASE.

车辆类的每个子类都会按要求实现 calculate_duration 方法。除了包含独立于车辆类型的所有编码的上层车辆类之外,其他具有不同行为的方法也可以这样做。将 CASE 隐藏在工厂方法中有助于我们遵循单一责任原则和避免重复的建议。

编者注:本文章改编自《Clean ABAP: A Style Guide for Developers》一书中的部分内容,作者为 Klaus Haeuptle、Florian Hoffmann、Rodrigo Jordão、Michel Martin、Anagha Ravinarayan 和 Kai Westerholz。