欢迎光临
我们一直在努力

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_ALV ALV输出 生成字段目录、显示报表
    ZINCL_HR_SALARY_LOG 日志记录 将计算结果写入日志表

    命名规范:主程序以Z_开头,Include以ZINCL_开头,清晰表达用途。


    三、主程序框架:控制流程

    主程序只做三件事:声明必要的全局变量(尽量少)、包含Include、定义执行顺序。

    主程序 Z_HR_SALARY_RPT:

    REPORT 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的?有没有更好的模块化实践?欢迎留言交流。

    赞(0)
    未经允许不得转载:171主机测评 » SAP-ABAP:模块化基础:子程序与Include程序(5篇)第5篇:实战落地:用子程序+Include搭建一个可维护的小型项目框架
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址