写点什么

实现 ABAP 条件断点的三种方式分享

作者:Jerry Wang
  • 2022 年 7 月 08 日
  • 本文字数:4969 字

    阅读完需:约 16 分钟

实现 ABAP 条件断点的三种方式分享

所谓条件断点,就是设置在某行语句上的断点,并不总是会触发,而是仅当满足一定条件时才触发。


条件断点的使用场合是什么?


举个简单的例子,下图第 15 行 ADD 语句设置一个断点。因为它在一个具有 1000 行的内表循环体内,所以正常情况下会触发 1000 次。



假设我们在调试一个 bug,这个 bug 当循环到第 999 次时才出现,那我们前 998 次的单步调试都是无效的。最高效的做法,就是借助条件断点的概念,让断点在代码执行到第 999 次循环时,触发且仅触发一次。


本文介绍实现 ABAP 条件断点的三种方式。也欢迎大家分享自己最喜欢用的且本文尚未提到的条件断点技术。

方法一:给 ABAP 断点维护触发条件

在 ABAP 调试器里点击 Break/Watchpoints 面板,新建一个断点:



在 Free Condition Entry 里维护这个断点的触发条件。


回到我上面的例子,我的内表里包含了从 1 递增到 1000 的整数,总共 1000 条记录,而我的触发条件维护为<data> = 22. 显然,这个断点在第 22 次循环时,唯一触发一次。



维护完毕后,我们在断点面板里看到了这个新建的断点:



按 F8 继续调试,断点有且仅触发了一次,此时<data>的值为 22,正好符合我们维护的触发条件,成功。


方法二:利用 ABAP 调试器里的观察点(Watchpoint)

打开 ABAP 的调试器,此处创建 Watchpoint:



我们知道在 LOOP 循环体内,系统变量 sy-tabix 会自动赋以当前的循环次数。因此我们在 Watchpoint 的触发条件里,维护成 sy-tabix = 22, 也可以达到在第 22 次循环时触发的目的。



Watchpoint 创建好之后显示如下:



按 F8 继续调试,程序果然在第 22 次循环时触发了:



并且调试器里弹出一条提示信息:Watchpoint reached


方法三:ABAP Debugger Script

ABAP Debugger Script 这项技术,在 SAP 研究院内部用的很广泛。


回到上面的例子,我们将编写一段简单的 ABAP 代码,去控制目标 ABAP 代码的断点触发。


在 ABAP 调试器里,点击 Script 标签页,创建一个新的 ABAP 脚本:



我们想用 ABAP 脚本监控 ABAP 代码里某个简单变量的值变化,所以使用脚本创建向导里的 Variable Value(for Simple Variables):



这个向导会自动帮我们生成 ABAP 脚本,其实也就是一段 ABAP 代码了,这段代码可以用编程的方式,在调试器激活的上下文里,获取某个 ABAP 变量的值。


下图脚本的语义很清晰,获取调试器里 field symbol <data>的值,存储在临时变量 lv_result 里。如果该变量的值为 22,就调用 ABAP 脚本的工具方法 break,触发断点。



把这段脚本通过上图的 Save As 按钮另存下来,取名 ZJERRY_TEST.


然后重新执行我们的测试代码, 使用 Load Script 加载刚才保存的 ABAP 脚本:



点击 Start Script 执行脚本:



断点再次如期触发.



这个 script 的源代码如下:


*---------------------------------------------------------------------**       CLASS lcl_debugger_script DEFINITION*---------------------------------------------------------------------***---------------------------------------------------------------------*CLASS lcl_debugger_script DEFINITION INHERITING FROM  cl_tpda_script_class_super  .
PUBLIC SECTION. METHODS: prologue REDEFINITION, init REDEFINITION, script REDEFINITION, end REDEFINITION. INTERFACES: if_tpda_script_w_input, if_tpda_script_w_output.
PRIVATE SECTION. DATA: entity_name TYPE string. DATA: value TYPE string. DATA: output TYPE tpda_transfer_it_unsorted. DATA: bol_object_name TYPE crmt_ext_obj_name. METHODS get_attribute IMPORTING io_oref_descr TYPE REF TO cl_tpda_script_orefdescr iv_attribute_name TYPE string RETURNING VALUE(ro_descr) TYPE REF TO cl_tpda_script_data_descr.
ENDCLASS. "lcl_debugger_script DEFINITION*---------------------------------------------------------------------** CLASS lcl_debugger_script IMPLEMENTATION*---------------------------------------------------------------------***---------------------------------------------------------------------*CLASS lcl_debugger_script IMPLEMENTATION.
METHOD prologue.*** generate abap_source (source handler for ABAP) super->prologue( ). ENDMETHOD. "prolog
METHOD if_tpda_script_w_input~get_parameters.
DATA lt_input TYPE tpda_transfer_it. DATA ls_input TYPE tpda_transfer_struc.
ls_input-id = 'ENTITY'. APPEND ls_input TO lt_input. p_parameters_it = lt_input.
ENDMETHOD. "if_tpda_script_w_input~get_parameters
METHOD if_tpda_script_w_input~set_parameter_values.
* Tabelle mit Inputparameter und Wert DATA lt_input TYPE tpda_transfer_it. DATA ls_input TYPE tpda_transfer_struc.
lt_input = p_parameter_values_it. LOOP AT lt_input INTO ls_input. IF ls_input-id = 'ENTITY'. entity_name = ls_input-value. ENDIF. ENDLOOP.
ENDMETHOD. "if_tpda_script_w_input~set_parameter_values
METHOD init.*** insert your initialization code here ENDMETHOD. "init
METHOD script.
DATA lr_data_descr TYPE REF TO cl_tpda_script_data_descr. DATA lr_struct_descr TYPE REF TO cl_tpda_script_structdescr. DATA lr_cx TYPE REF TO cx_root. DATA ls_quick TYPE tpda_scr_quick_info. DATA lv_name TYPE string. DATA lt_struct TYPE tpda_scr_struct_comp_it. DATA ls_struct TYPE tpda_scr_struct_comp. DATA ls_output TYPE tpda_transfer_struc. DATA lr_symbsimple TYPE REF TO tpda_sys_symbsimple. DATA ls_varinfo TYPE tpda_quick_vars.
FIELD-SYMBOLS: <lv_value> TYPE any.
TRY. CLEAR output.
* BREAK-POINT.
ls_varinfo = cl_tpda_script_data_descr=>get_variable_info( 'LO_PRODUCT' ).
* get object type name IF ls_varinfo-varvalue = 'OBJECT'.* class instance passed directly lv_name = entity_name && '-CONTAINER_PROXY->DATA_REF->OBJECT_NAME'. ELSE.* variable of class instance passed lv_name = ls_varinfo-varvalue && '-CONTAINER_PROXY->DATA_REF->OBJECT_NAME'. ENDIF.
ls_quick = cl_tpda_script_data_descr=>get_quick_info( lv_name ). ASSIGN ls_quick-quickdata TO <lv_value>. lr_symbsimple ?= <lv_value>. bol_object_name = lr_symbsimple->valstring.
* get content IF ls_varinfo-varvalue = 'OBJECT'. lv_name = entity_name && '-CONTAINER_PROXY->DATA_REF->ATTRIBUTE_REF->*'. ELSE. lv_name = ls_varinfo-varvalue && '-CONTAINER_PROXY->DATA_REF->ATTRIBUTE_REF->*'. ENDIF.
lr_data_descr = cl_tpda_script_data_descr=>factory( lv_name ). lr_struct_descr ?= lr_data_descr.
lr_struct_descr->components( IMPORTING* p_components_it = p_components_full_it = lt_struct ).
LOOP AT lt_struct INTO ls_struct. ls_output-id = ls_struct-compname. TRY. ASSIGN ls_struct-symbquick-quickdata TO <lv_value>. lr_symbsimple ?= <lv_value>. ls_output-value = lr_symbsimple->valstring. CATCH cx_root INTO lr_cx. ls_output-value = lr_cx->get_text( ). ENDTRY. APPEND ls_output TO output. ENDLOOP.
DATA lt_col_alv TYPE tpda_script_service_source_tab. DATA ls_col_alv LIKE LINE OF lt_col_alv. ls_col_alv-fieldname = ls_col_alv-content = 'ID'. APPEND ls_col_alv TO lt_col_alv. ls_col_alv-fieldname = ls_col_alv-content = 'VALUE'. APPEND ls_col_alv TO lt_col_alv.
CALL METHOD cl_tpda_script_data_display=>data_display EXPORTING p_list_header = 'Query Selection Parameters' p_column_it = lt_col_alv p_popup = 'X' CHANGING p_data_it = output.
* BREAK-POINT. CATCH cx_root INTO lr_cx. BREAK-POINT. "#EC NOBREAK value = lr_cx->get_text( ). ENDTRY. ENDMETHOD. "script
METHOD end.*** insert your code which shall be executed at the end of the scripting (before trace is saved)*** here
ENDMETHOD. "end
METHOD if_tpda_script_w_output~get_parameter_values.
DATA lt_param TYPE tpda_transfer_it_unsorted. DATA ls_param TYPE tpda_transfer_struc.
ls_param-id = 'VARIABLE'. ls_param-value = entity_name. APPEND ls_param TO lt_param. ls_param-id = 'OBJECT_NAME'. ls_param-value = bol_object_name. APPEND ls_param TO lt_param.
APPEND INITIAL LINE TO lt_param.
APPEND LINES OF output TO lt_param.
p_parameter_values_it = lt_param.
ENDMETHOD. "if_tpda_script_w_output~get_parameter_values
METHOD get_attribute.
DATA lr_oref_descr TYPE REF TO cl_tpda_script_orefdescr. DATA lr_object_descr TYPE REF TO cl_tpda_script_objectdescr. DATA ls_varinfo TYPE tpda_quick_vars. DATA lv_longname TYPE string.
DATA lt_attributes TYPE tpda_script_object_attribut_it.
lr_oref_descr = io_oref_descr. lr_object_descr = lr_oref_descr->get_object_handle( ).
lt_attributes = lr_object_descr->attributes( ).
ro_descr = lr_object_descr->get_attribut_handle( lv_longname ).
ENDMETHOD. "get_oref_attribute
ENDCLASS. "lcl_debugger_script IMPLEMENTATION
复制代码


我们知道,像如图一这种类的静态属性,因为不属于类的实例所有,因此调试到这个类的方法内部时,只能通过图二演示的两种方式在调试器显示该属性的值。而一旦调试到该类方法的外部,通常就只能通过"类名=>属性名"的方式来显示静态属性值(图三)。其实还有一种方式,如图四和图五所示。



图一:ABAP 类的静态属性


图二:如何在 ABAP 调试器里查看类的静态属性


图三:在调试器里跳出类的方法之后,如何查看静态属性


图四和图五在调试器的 Objects 面板里,手动输入{C:ZCL_STATIC}, 这里的 ZCL_STATIC 替换成其他包含有静态属性的类名,回车即可查看。


可能有些朋友觉得这个小技巧没啥用吧,我以前在调试很多用单例模式(Singleton)实现的框架代码时经常用。当排错需要查看一个用单例模式实现的类的多个静态属性时,如果用图三介绍的"类名=>属性名"的方式,要重复敲很多字符,敲击键盘的时间复杂度为 o(n), n 为静态属性的个数。用 Object 面板这种技巧,敲击键盘的时间复杂度一下子降到 o(1), 提高了排错效率。

总结

所谓条件断点,就是设置在某行语句上的断点,并不总是会触发,而是仅当满足一定条件时才触发。本文首先介绍了 ABAP 条件断点的使用场合,接着使用了一个包含循环的 ABAP 程序,分享了三种不同的条件断点的使用方式。灵活运用条件断点,能大大提高开发人员的调试效率。

发布于: 刚刚阅读数: 3
用户头像

Jerry Wang

关注

🏆InfoQ写作平台-签约作者🏆 2017.12.03 加入

SAP成都研究院开发专家,SAP社区导师,SAP中国技术大使。2007 年从电子科技大学计算机专业硕士毕业后加入 SAP 成都研究院工作至今。工作中使用 ABAP, Java, JavaScript 和 TypeScript 进行开发。

评论

发布
暂无评论
实现 ABAP 条件断点的三种方式分享_debug_Jerry Wang_InfoQ写作社区