SAP-ABAP:模块化基础:子程序与Include程序(5篇)第5篇:实战落地:用子程序+Include搭建一个可维护的小型项目框架 模块化基础子程序与Include程序5篇第5篇实战落地用子程序Include搭建一个可维护的小型项目框架前面的四篇文章我们分别学习了子程序的基础、参数传递、Include的用法以及常见避坑指南。理论知识已经齐备但如何在实际项目中真正运用这些模块化技巧本文将通过一个完整的实战案例——员工月度工资报表从0到1演示如何合理拆分子程序、规划Include文件结构最终搭建一个可维护、易扩展的小型项目框架。读完本文你将能够把模块化思维落地到日常开发中。一、项目需求概述我们要开发一个员工月度工资报表功能如下接收用户输入的年月如202605和公司代码。从数据库读取该月的所有员工工资记录表ZHR_SALARY含员工号、姓名、基本工资、奖金、扣款等。计算每位员工的应发工资 基本工资 奖金 - 扣款。计算部门汇总部门从员工主数据表ZHR_EMPLOYEE获取。输出ALV报表展示员工明细和部门汇总。将计算结果写入日志表ZHR_SALARY_LOG。为了展示模块化我们会将程序拆分为主控制流程、数据获取、业务计算、ALV输出、日志记录等模块。二、整体文件结构规划我们将创建一个主程序Z_HR_SALARY_RPT并配套4个Include文件文件名职责内容Z_HR_SALARY_RPT主程序主控流程调用各Include中的子程序START-OF-SELECTION全局变量声明少量ZINCL_HR_SALARY_DATA数据获取从数据库读取员工工资、员工主数据ZINCL_HR_SALARY_CALC业务计算计算应发工资、部门汇总ZINCL_HR_SALARY_ALVALV输出生成字段目录、显示报表ZINCL_HR_SALARY_LOG日志记录将计算结果写入日志表命名规范主程序以Z_开头Include以ZINCL_开头清晰表达用途。三、主程序框架控制流程主程序只做三件事声明必要的全局变量尽量少、包含Include、定义执行顺序。主程序Z_HR_SALARY_RPTREPORT z_hr_salary_rpt. *----------------------------------------------------------------------* * 全局变量声明仅跨Include共享的数据 *----------------------------------------------------------------------* DATA: gv_bukrs TYPE bukrs, 公司代码 gv_gjahr TYPE gjahr, 年份 gv_monat TYPE monat. 月份 主要数据结构内表 DATA: gt_salary TYPE TABLE OF zhr_salary, 原始工资记录 gt_employee TYPE TABLE OF zhr_employee, 员工主数据 gt_result TYPE TABLE OF ty_result, 计算结果见类型定义 gt_summary TYPE TABLE OF ty_summary. 部门汇总 *----------------------------------------------------------------------* * 包含模块化组件 *----------------------------------------------------------------------* INCLUDE zincl_hr_salary_data. 数据获取子程序 INCLUDE zincl_hr_salary_calc. 计算子程序 INCLUDE zincl_hr_salary_alv. ALV输出子程序 INCLUDE zincl_hr_salary_log. 日志子程序 *----------------------------------------------------------------------* * 主控流程 *----------------------------------------------------------------------* START-OF-SELECTION. PERFORM f_get_parameters. 获取输入参数 PERFORM f_load_data. 加载数据 PERFORM f_calculate. 计算 PERFORM f_display_alv. 输出ALV PERFORM f_write_log. 写日志注意类型ty_result和ty_summary我们将在计算Include中定义这样其他Include也能使用。四、Include设计详解4.1 数据获取模块ZINCL_HR_SALARY_DATA负责从数据库读取原始数据并填充主程序中的全局内表。*---------------------------------------------------------------------* * Include ZINCL_HR_SALARY_DATA * 数据获取子程序 *---------------------------------------------------------------------* FORM f_get_parameters. 从选择屏幕获取参数假设已定义选择屏幕 gv_bukrs p_bukrs. gv_gjahr p_gjahr. gv_monat p_monat. ENDFORM. FORM f_load_data. 读取工资记录 SELECT * FROM zhr_salary INTO TABLE gt_salary WHERE bukrs gv_bukrs AND gjahr gv_gjahr AND monat gv_monat. IF sy-subrc 0. MESSAGE 没有找到工资数据 TYPE W. RETURN. ENDIF. 读取员工主数据使用FOR ALL ENTRIES IF gt_salary IS NOT INITIAL. SELECT * FROM zhr_employee INTO TABLE gt_employee FOR ALL ENTRIES IN gt_salary WHERE pernr gt_salary-pernr. ENDIF. ENDFORM.4.2 业务计算模块ZINCL_HR_SALARY_CALC定义结果结构、计算每位员工的应发工资并汇总部门数据。*---------------------------------------------------------------------* * Include ZINCL_HR_SALARY_CALC * 业务计算子程序 *---------------------------------------------------------------------* 定义结果行结构 TYPES: BEGIN OF ty_result, pernr TYPE pernr_d, 员工号 ename TYPE emnam, 姓名 dept_id TYPE dept_id, 部门ID dept_name TYPE dept_name, 部门名称 basic TYPE p DECIMALS 2, 基本工资 bonus TYPE p DECIMALS 2, 奖金 deduct TYPE p DECIMALS 2, 扣款 net TYPE p DECIMALS 2, 实发工资 END OF ty_result. TYPES: BEGIN OF ty_summary, dept_id TYPE dept_id, dept_name TYPE dept_name, emp_count TYPE i, total_net TYPE p DECIMALS 2, END OF ty_summary. FORM f_calculate. DATA: ls_result TYPE ty_result, ls_emp TYPE zhr_employee, lt_summary TYPE TABLE OF ty_summary, ls_summary LIKE LINE OF lt_summary. 清空结果表 REFRESH: gt_result, gt_summary. LOOP AT gt_salary INTO DATA(ls_sal). 获取员工信息 READ TABLE gt_employee INTO ls_emp WITH KEY pernr ls_sal-pernr. IF sy-subrc 0. CONTINUE. ENDIF. 计算实发 ls_result-pernr ls_sal-pernr. ls_result-ename ls_emp-ename. ls_result-dept_id ls_emp-dept_id. ls_result-dept_name ls_emp-dept_name. ls_result-basic ls_sal-basic. ls_result-bonus ls_sal-bonus. ls_result-deduct ls_sal-deduct. ls_result-net ls_sal-basic ls_sal-bonus - ls_sal-deduct. APPEND ls_result TO gt_result. 部门汇总累加 READ TABLE lt_summary INTO ls_summary WITH KEY dept_id ls_emp-dept_id. IF sy-subrc 0. ls_summary-emp_count ls_summary-emp_count 1. ls_summary-total_net ls_summary-total_net ls_result-net. MODIFY lt_summary FROM ls_summary INDEX sy-tabix. ELSE. ls_summary-dept_id ls_emp-dept_id. ls_summary-dept_name ls_emp-dept_name. ls_summary-emp_count 1. ls_summary-total_net ls_result-net. APPEND ls_summary TO lt_summary. ENDIF. ENDLOOP. 将汇总结果赋值给全局内表供ALV显示 gt_summary lt_summary. ENDFORM.4.3 ALV输出模块ZINCL_HR_SALARY_ALV负责生成字段目录并调用ALV函数。*---------------------------------------------------------------------* * Include ZINCL_HR_SALARY_ALV * ALV输出子程序 *---------------------------------------------------------------------* FORM f_display_alv. 如果无数据不显示 IF gt_result IS INITIAL. RETURN. ENDIF. 生成字段目录仅示意实际可调用函数批量生成 DATA: lt_fieldcat TYPE slis_t_fieldcat_alv, ls_fieldcat LIKE LINE OF lt_fieldcat. 员工号 ls_fieldcat-fieldname PERNR. ls_fieldcat-seltext_l 员工号. APPEND ls_fieldcat TO lt_fieldcat. 姓名 ls_fieldcat-fieldname ENAME. ls_fieldcat-seltext_l 姓名. APPEND ls_fieldcat TO lt_fieldcat. 基本工资 ls_fieldcat-fieldname BASIC. ls_fieldcat-seltext_l 基本工资. APPEND ls_fieldcat TO lt_fieldcat. 实发工资 ls_fieldcat-fieldname NET. ls_fieldcat-seltext_l 实发工资. APPEND ls_fieldcat TO lt_fieldcat. 可以继续添加其他字段... 调用ALV显示函数 CALL FUNCTION REUSE_ALV_GRID_DISPLAY EXPORTING i_callback_program sy-repid it_fieldcat lt_fieldcat i_save A TABLES t_outtab gt_result EXCEPTIONS OTHERS 1. ENDFORM.扩展如果需要同时显示部门汇总表可以弹出第二个ALV或者在主ALV中通过双击事件触发。这里为了简洁只展示明细。4.4 日志记录模块ZINCL_HR_SALARY_LOG将计算结果写入日志表便于追溯。*---------------------------------------------------------------------* * Include ZINCL_HR_SALARY_LOG * 日志记录子程序 *---------------------------------------------------------------------* FORM f_write_log. DATA: ls_log TYPE zhr_salary_log, lv_count TYPE i. LOOP AT gt_result INTO DATA(ls_res). ls_log-pernr ls_res-pernr. ls_log-dept_id ls_res-dept_id. ls_log-net ls_res-net. ls_log-rundate sy-datum. ls_log-username sy-uname. INSERT zhr_salary_log FROM ls_log. IF sy-subrc 0. lv_count lv_count 1. ENDIF. ENDLOOP. IF lv_count 0. WRITE: / 日志已写入, lv_count, 条记录. ENDIF. ENDFORM.五、模块化带来的好处5.1 可读性主程序只有20行清晰地展示了整个报表的执行流程参数获取 → 读数据 → 计算 → 显示 → 记日志。新接手项目的同事可以在1分钟内了解程序的全貌。5.2 可维护性如果业务逻辑变了比如奖金计算规则调整只需修改ZINCL_HR_SALARY_CALC中的f_calculate子程序不影响其他模块。如果ALV显示需要增加分组、排序功能只需改动ZINCL_HR_SALARY_ALV不会意外破坏数据获取逻辑。如果数据库表结构变更仅修改对应的Include即可。5.3 可测试性可以编写单独的测试程序仅PERFORM f_calculate传入模拟数据验证计算结果是否正确无需运行整个报表。5.4 团队协作可以让三个开发者同时开发不同的Include一个负责数据读取一个负责计算一个负责ALV输出互不干扰。六、扩展与优化建议6.1 将类型定义提取到独立的Include如果多个Include都需要使用ty_result、ty_summary可以将它们放到一个单独的ZINCL_HR_SALARY_TYPES中然后在所有其他Include最开头INCLUDE ZINCL_HR_SALARY_TYPES。6.2 使用函数模块或类替代Include对于更复杂的项目可以将上述子程序迁移到函数模块或类方法中以获得更好的封装和异常处理。但Include子程序的模式轻量、简单非常适合中小型项目。6.3 添加异常处理在每个子程序内部增加错误捕获和返回标志位主程序根据标志位决定是否继续。例如FORM f_load_data CHANGING cv_error TYPE abap_bool. 如果出错设置 cv_error abap_true ENDFORM.6.4 使用选择屏幕可以为报表添加选择屏幕让用户输入年月和公司代码。选择屏幕的逻辑可以在主程序中独立编写也可放入f_get_parameters中。七、总结通过这个完整的实战案例我们演示了如何从零开始使用子程序和Include程序构建一个可维护的小型项目框架。核心要点回顾模块职责关键技术主程序控制流程、少量全局变量START-OF-SELECTION、PERFORM数据Include数据获取SELECT、FOR ALL ENTRIES计算Include业务计算、汇总LOOP、内表处理ALV Include报表输出REUSE_ALV_GRID_DISPLAY日志Include记录执行结果INSERT、LOOP模块化不是银弹但合理的模块化可以让你的代码在三个月后、三年后仍然易于理解和修改。从今天开始在你编写每一个新程序时都先问自己三个问题这个程序可以拆分为几个独立的逻辑步骤哪些步骤可能在未来发生变化哪些逻辑可能会被其他程序复用然后按照本文的示例构建你的Include子程序框架。祝你在模块化之路上越走越顺本系列回顾第1篇《从冗余到精简子程序是模块化开发的第一块基石》第2篇《子程序核心用法指南参数传递、返回值与边界场景处理》第3篇《代码复用的另一条路径Include程序的底层逻辑与基础用法》第4篇《避坑指南子程序与Include程序的常见误用场景解析》第5篇《实战落地用子程序Include搭建一个可维护的小型项目框架》本文作者你的ABAP学习伙伴版本记录2026年5月 你在实际项目中是如何划分Include的有没有更好的模块化实践欢迎留言交流。