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

少年恃险若平地,独倚长剑凌清秋。这篇文章主要讲述实现 ABAP 条件断点的三种方式分享相关的知识,希望能为你提供帮助。
所谓条件断点,就是设置在某行语句上的断点,并不总是会触发,而是仅当满足一定条件时才触发。
条件断点的使用场合是什么?
举个简单的例子,下图第15行ADD语句设置一个断点。因为它在一个具有1000行的内表循环体内,所以正常情况下会触发1000次。

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

文章图片

假设我们在调试一个bug,这个bug当循环到第999次时才出现,那我们前998次的单步调试都是无效的。最高效的做法,就是借助条件断点的概念,让断点在代码执行到第999次循环时,触发且仅触发一次。
本文介绍实现ABAP条件断点的三种方式。也欢迎大家分享自己最喜欢用的且本文尚未提到的条件断点技术。
方法一:给ABAP断点维护触发条件在ABAP调试器里点击Break/Watchpoints面板,新建一个断点:
【实现 ABAP 条件断点的三种方式分享】
实现 ABAP 条件断点的三种方式分享

文章图片

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

文章图片

维护完毕后,我们在断点面板里看到了这个新建的断点:
实现 ABAP 条件断点的三种方式分享

文章图片

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

文章图片

方法二:利用ABAP调试器里的观察点(Watchpoint)打开 ABAP 的调试器,此处创建Watchpoint:
实现 ABAP 条件断点的三种方式分享

文章图片

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

文章图片

Watchpoint创建好之后显示如下:
实现 ABAP 条件断点的三种方式分享

文章图片

按F8继续调试,程序果然在第22次循环时触发了:
实现 ABAP 条件断点的三种方式分享

文章图片

并且调试器里弹出一条提示信息:Watchpoint reached
实现 ABAP 条件断点的三种方式分享

文章图片

方法三:ABAP Debugger ScriptABAP Debugger Script这项技术,在SAP研究院内部用的很广泛。
回到上面的例子,我们将编写一段简单的ABAP代码,去控制目标ABAP代码的断点触发。
在ABAP调试器里,点击Script标签页,创建一个新的ABAP脚本:
实现 ABAP 条件断点的三种方式分享

文章图片

我们想用ABAP脚本监控ABAP代码里某个简单变量的值变化,所以使用脚本创建向导里的Variable Value(for Simple Variables):
实现 ABAP 条件断点的三种方式分享

文章图片

这个向导会自动帮我们生成ABAP脚本,其实也就是一段ABAP代码了,这段代码可以用编程的方式,在调试器激活的上下文里,获取某个ABAP变量的值。
下图脚本的语义很清晰,获取调试器里field symbol < data> 的值,存储在临时变量lv_result里。如果该变量的值为22,就调用ABAP脚本的工具方法break,触发断点。
实现 ABAP 条件断点的三种方式分享

文章图片

把这段脚本通过上图的Save As按钮另存下来,取名ZJERRY_TEST.
然后重新执行我们的测试代码, 使用Load Script加载刚才保存的ABAP脚本:
实现 ABAP 条件断点的三种方式分享

文章图片

点击Start Script执行脚本:
实现 ABAP 条件断点的三种方式分享

文章图片

断点再次如期触发.
实现 ABAP 条件断点的三种方式分享

文章图片

这个 script 的源代码如下:
*---------------------------------------------------------------------* *CLASS lcl_debugger_script DEFINITION *---------------------------------------------------------------------* * *---------------------------------------------------------------------* CLASS lcl_debugger_script DEFINITION INHERITING FROMcl_tpda_script_class_super.PUBLIC SECTION. METHODS: prologueREDEFINITION, initREDEFINITION, scriptREDEFINITION, endREDEFINITION. 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_descrTYPE 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."prologMETHOD if_tpda_script_w_input~get_parameters.DATA lt_inputTYPE tpda_transfer_it. DATA ls_inputTYPE 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_parametersMETHOD if_tpda_script_w_input~set_parameter_values.*Tabelle mit Inputparameter und Wert DATA lt_inputTYPE tpda_transfer_it. DATA ls_inputTYPE 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_valuesMETHOD init. *** insert your initialization code here ENDMETHOD."initMETHOD script.DATA lr_data_descrTYPE REF TO cl_tpda_script_data_descr. DATA lr_struct_descrTYPE REF TO cl_tpda_script_structdescr. DATA lr_cxTYPE REF TO cx_root. DATA ls_quickTYPE tpda_scr_quick_info. DATA lv_nameTYPE string. DATA lt_structTYPE tpda_scr_struct_comp_it. DATA ls_structTYPE tpda_scr_struct_comp. DATA ls_outputTYPE tpda_transfer_struc. DATA lr_symbsimpleTYPE REF TO tpda_sys_symbsimple. DATA ls_varinfoTYPE 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 = https://www.songbingjia.com/android/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_alvTYPE tpda_script_service_source_tab. DATA ls_col_alvLIKE 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 = https://www.songbingjia.com/android/lr_cx-> get_text( ). ENDTRY. ENDMETHOD."scriptMETHOD end. *** insert your code which shall be executed at the end of the scripting (before trace is saved) *** hereENDMETHOD."endMETHOD 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 = https://www.songbingjia.com/android/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_valuesMETHOD get_attribute.DATA lr_oref_descrTYPE REF TO cl_tpda_script_orefdescr. DATA lr_object_descrTYPE REF TO cl_tpda_script_objectdescr. DATA ls_varinfoTYPE tpda_quick_vars. DATA lv_longnameTYPE 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_attributeENDCLASS."lcl_debugger_script IMPLEMENTATION

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

文章图片

图一:ABAP类的静态属性
实现 ABAP 条件断点的三种方式分享

文章图片
图二:如何在ABAP调试器里查看类的静态属性
实现 ABAP 条件断点的三种方式分享

文章图片
图三:在调试器里跳出类的方法之后,如何查看静态属性
实现 ABAP 条件断点的三种方式分享

文章图片

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

文章图片
图四和图五在调试器的Objects面板里,手动输入C:ZCL_STATIC, 这里的ZCL_STATIC替换成其他包含有静态属性的类名,回车即可查看。
可能有些朋友觉得这个小技巧没啥用吧,我以前在调试很多用单例模式(Singleton)实现的框架代码时经常用。当排错需要查看一个用单例模式实现的类的多个静态属性时,如果用图三介绍的" 类名=> 属性名" 的方式,要重复敲很多字符,敲击键盘的时间复杂度为o(n), n为静态属性的个数。用Object面板这种技巧,敲击键盘的时间复杂度一下子降到o(1), 提高了排错效率。
总结所谓条件断点,就是设置在某行语句上的断点,并不总是会触发,而是仅当满足一定条件时才触发。本文首先介绍了 ABAP 条件断点的使用场合,接着使用了一个包含循环的 ABAP 程序,分享了三种不同的条件断点的使用方式。灵活运用条件断点,能大大提高开发人员的调试效率。

    推荐阅读