原文链接: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。由于上一节中提到的原因,重复使用相同的 CASE 或 SWITCH 语句有一些缺点需要考虑。SWITCH 中使用的枚举可能会更改,所有用户都需要更改。因此,应减少 SWITCH 的占用空间,从而使枚举的更改导致其他方法的更改量最小。在自己的方法中提取操作符,重点是识别当前情况并返回正确的值,这样可以更好地分离操作符,而枚举中的变化只会导致封装枚举的单个开关发生变化。
最后,我们必须做出同样的决定:是使用 SWITCH 的单一方法最合理,还是将不同的情况隐藏在工厂方法中并创建自己的类最合理?
同一 CASE 的多种用途
CASE 的优势在于创建了一种结构合理的方法,易于遵循,而且往往能直接应对不同的情况。遗憾的是,由于这些优点,同一个 CASE 经常被重复使用多次。上述 CASE 或 IF 部分中的第二个示例确定了一种车辆类型,该类型的持续时间应易于计算。基于类型的区分仅适用于该计算,而不会在任何其他方法中重复使用,这一点不太可能。
由于 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。