模块化基础:子程序与Include程序(5篇)
第1篇:从冗余到精简:子程序是模块化开发的第一块基石
你是否有过这样的经历:在一个ABAP报表里,同样的20行代码被复制粘贴了五六次,每次修改都要挨个改一遍?或者一个程序超过5000行,想定位某段逻辑却不得不来回滚动屏幕?这些都是“代码冗余”和“缺乏模块化”的典型症状。而子程序(Subroutine,ABAP中称为FORM)正是解决这些问题的第一把钥匙。本文从零开始,讲透子程序的核心定义、存在意义,并通过对比案例,帮你建立最基础的模块化认知——为什么写代码要先学会“拆分子程序”。
一、什么是子程序?
子程序是一段可被重复调用的、具有独立逻辑功能的代码块。在ABAP中,子程序使用FORM和ENDFORM包裹,并通过PERFORM语句执行。
最简单的子程序示例:
FORM display_hello. WRITE 'Hello, ABAP'. ENDFORM. " 调用子程序 PERFORM display_hello.当你需要多次输出“Hello, ABAP”时,不用重复写WRITE语句,只需要反复PERFORM display_hello.即可。
二、为什么需要子程序?——从重复代码的痛苦说起
2.1 无子程序的“面条式代码”
假设你要开发一个报表,需要三次计算员工的税后工资,每次计算方法相同(基本工资 - 社保 - 个税)。如果不使用子程序,代码会是这样:
" 第一次计算员工A lv_gross_a = 10000. lv_social_a = lv_gross_a * 0.1. lv_tax_a = ( lv_gross_a - lv_social_a ) * 0.05. lv_net_a = lv_gross_a - lv_social_a - lv_tax_a. " 第二次计算员工B(完全相同的逻辑) lv_gross_b = 15000. lv_social_b = lv_gross_b * 0.1. lv_tax_b = ( lv_gross_b - lv_social_b ) * 0.05. lv_net_b = lv_gross_b - lv_social_b - lv_tax_b. " 第三次计算员工C...问题:
- 代码臃肿,重复三次几乎一模一样的计算。
- 维护困难:如果税率从5%调整到6%,需要修改三个地方,极易遗漏。
- 可读性差:真实业务中可能有几十个员工,程序会长达数百行,难以理解核心逻辑。
2.2 引入子程序后的改进
将计算税后工资的逻辑抽取为一个独立的子程序:
FORM calc_net USING iv_gross TYPE i CHANGING cv_net TYPE i. DATA: lv_social TYPE i, lv_tax TYPE i. lv_social = iv_gross * 0.1. lv_tax = ( iv_gross - lv_social ) * 0.05. cv_net = iv_gross - lv_social - lv_tax. ENDFORM. " 调用子程序计算员工A PERFORM calc_net USING 10000 CHANGING lv_net_a. " 调用子程序计算员工B PERFORM calc_net USING 15000 CHANGING lv_net_b. " 调用子程序计算员工C PERFORM calc_net USING 12000 CHANGING lv_net_c.优势:
- 代码行数大幅减少。
- 修改税率只需改动子程序内部一处,所有调用自动生效。
- 主流程清晰:一眼就能看出在计算三个员工的税后工资。
三、子程序如何实现模块化?
模块化的核心思想是分而治之:将一个大问题拆解为若干小问题,每个小问题用一个独立的模块(子程序)解决,然后按顺序组装。
3.1 一个复杂报表的模块化拆分案例
假设你要开发一个销售订单统计报表,流程包括:
- 获取用户输入的日期范围。
- 从数据库中读取订单数据。
- 计算总金额、平均金额。
- 输出ALV报表。
- 记录日志。
如果不拆分,所有代码挤在一个REPORT中,长达上千行,调试和修改都极为困难。
模块化拆分后:
REPORT z_sales_statistics. * 主控逻辑 START-OF-SELECTION. PERFORM get_user_input. PERFORM fetch_data. PERFORM calculate_statistics. PERFORM display_report. PERFORM write_log. * 子程序定义 FORM get_user_input. " 获取日期区间 ENDFORM. FORM fetch_data. " 数据库查询 ENDFORM. FORM calculate_statistics. " 计算汇总 ENDFORM. FORM display_report. " 输出ALV ENDFORM. FORM write_log. " 记录日志 ENDFORM.好处:
- 可读性:主控逻辑一目了然,就像一份执行清单。
- 可测试性:可以单独
PERFORM某个子程序进行测试。 - 可复用性:
write_log子程序可能在其他报表中也能用(通过复制,后续会讲到更高级的复用)。 - 团队协作:不同开发者可以同时编写不同的子程序,互不干扰。
四、子程序的“副作用”与合理使用边界
子程序虽好,但也不是万能的。它的主要局限是:
- 默认全局数据访问:子程序可以直接访问主程序中的全局变量,这虽然方便,但容易造成意外的数据修改,降低代码的可预测性。
- 不能递归调用自己(ABAP中FORM不支持递归,会引发错误)。
- 无法真正封装:相比于函数模块或类方法,子程序缺乏独立的命名空间和异常处理机制。
因此,子程序最适合的场景是:
- 程序内部重复使用的同质逻辑。
- 用于结构化主流程,将长程序切分成有意义的段落。
- 作为初学模块化的第一实践,培养“先拆分,后实现”的思维习惯。
当需要更高级的封装、跨程序复用或独立测试时,后续篇章会介绍函数模块和类。但无论如何,子程序是模块化思维的起点。
五、对比总结:有子程序 vs 无子程序
| 维度 | 无子程序(一次性代码) | 有子程序(模块化) |
|---|---|---|
| 代码长度 | 重复代码多,整体冗长 | 精简,逻辑集中 |
| 维护成本 | 修改一处需改多处,易遗漏 | 只需修改子程序内部 |
| 可读性 | 逻辑被重复代码淹没 | 主流程清晰,细节藏于子程序 |
| 测试 | 难以单独测试某段逻辑 | 可单独调用子程序测试 |
| 复用 | 只能复制粘贴 | 同一程序内可多次PERFORM |
六、动手实践:重构一段重复代码
原始代码(计算圆面积三次,每次都写π*r²):
DATA: r1 TYPE f VALUE 3, r2 TYPE f VALUE 5, r3 TYPE f VALUE 7, area1 TYPE f, area2 TYPE f, area3 TYPE f. area1 = 3.14159 * r1 * r1. area2 = 3.14159 * r2 * r2. area3 = 3.14159 * r3 * r3.重构后:
FORM calc_circle_area USING iv_radius TYPE f RETURNING VALUE(rv_area) TYPE f. CONSTANTS: gc_pi TYPE f VALUE '3.141592653589793'. rv_area = gc_pi * iv_radius * iv_radius. ENDFORM. PERFORM calc_circle_area USING r1 CHANGING area1. PERFORM calc_circle_area USING r2 CHANGING area2. PERFORM calc_circle_area USING r3 CHANGING area3.注意:ABAP中
RETURNING参数需要配合VALUE,调用时使用CHANGING接收。这是子程序参数传递的细节,下一篇会详细讲解。
七、总结:子程序是模块化道路的第一步
子程序看起来简单,但它教会我们编程中最重要的思维——抽象与封装。将重复的、可独立的逻辑从主流程中抽离,赋予一个有意义的名称,然后重复使用。这种思维不仅是ABAP开发的基础,也是所有编程语言的共通智慧。
下一篇我们将深入子程序的核心用法:参数传递的四种方式、返回值设计、以及常见的边界场景处理。掌握了这些,你就能够编写出既灵活又健壮的子程序。
📌下篇预告:《子程序核心用法指南:参数传递、返回值与边界场景处理》
作者:你的ABAP学习伙伴
版本记录:2026年5月
💬 你是否曾因没有使用子程序而陷入“复制-粘贴-修改”的泥潭?欢迎留言分享你的经历。