所谓条件断点,就是设置在某行语句上的断点,并不总是会触发,而是仅当满足肯定条件时才触发。
条件断点的应用场合是什么?
举个简略的例子,下图第 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 程序,分享了三种不同的条件断点的应用形式。灵活运用条件断点,能大大提高开发人员的调试效率。