Identifying Unused Custom Code During S/4 Migration
When migrating to SAP S/4, one of the key tasks in the preparation phase is identifying unused custom code to optimize the transition. While SAP offers various reports, none of them met our specific requirements, so we decided to create our own custom report to detect unused code. In this blog post, I will walk you through the steps we took to create this report, from setting up data collection to checking the objects in the productive system and generating the final results.
🛠️ The Approach
To effectively identify unused custom code, we needed to gather usage data over a long period. SAP recommends running SCMON (the Custom Code Migration Workbench) for at least 13 months to collect meaningful data. This usage data forms the foundation of the report we created. We exported this data from SUSG (Usage Data Snapshot) and imported it into our development system, where we could combine it with a remote existence check to ensure the objects were also present in the production system.
📝 1. Get All Relevant Objects to Check
The first step was to gather all relevant objects that needed to be checked. This included programs, classes, and function groups. We executed a select statement to pull these objects and their associated metadata from the TADIR and PROGDIR tables, as shown below:
1
2
3
4
5
6
7
8
9
10
11
12
13
" Get the relevant programs/classes/function groups - Local system
SELECT DISTINCT object, tadir~obj_name, progdir~subc AS psubc, progdir~cdat AS pcdat, tcode
FROM tadir
LEFT OUTER JOIN progdir
ON progdir~name EQ tadir~obj_name
LEFT OUTER JOIN tstc
ON tstc~pgmna EQ tadir~obj_name
INTO TABLE @DATA(lt_objname)
WHERE object IN @s_objtyp
AND tadir~obj_name IN @s_objnam
AND tadir~delflag EQ @abap_false
AND tadir~genflag EQ @abap_false
AND tadir~devclass IN @s_devc.
This select statement retrieves all objects that are still active and relevant for analysis, excluding deleted or generated objects.
🔍 2. Check in the Productive System
Next, we needed to check if these objects were also available in the production system. To do this, we used a remote function call (RFC) to verify the existence of each object in the target system:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
METHOD get_remote_objects.
DATA: lf_where_clause TYPE string,
lf_message TYPE char1024.
DATA: lt_options TYPE TABLE OF rfc_db_opt,
lt_temp TYPE TABLE OF rfc_db_opt,
lt_fields TYPE TABLE OF rfc_db_fld,
lt_data TYPE TABLE OF t_remote_tadir.
APPEND ` delflag EQ '' ` TO lt_options.
APPEND ` AND genflag EQ '' ` TO lt_options.
" Object type checks
IF s_objtyp[] IS NOT INITIAL.
lt_temp = morph_selopt_to_rfc( if_fieldname = 'object' it_selopt = s_objtyp[] ).
IF lt_temp IS NOT INITIAL.
APPEND ` AND ` TO lt_options.
APPEND LINES OF lt_temp TO lt_options.
ENDIF.
ENDIF.
" object name
IF s_objnam[] IS NOT INITIAL.
lt_temp = morph_selopt_to_rfc( if_fieldname = 'obj_name' it_selopt = s_objnam[] ).
IF lt_temp IS NOT INITIAL.
APPEND ` AND ` TO lt_options.
APPEND LINES OF lt_temp TO lt_options.
ENDIF.
ENDIF.
" package
IF s_devc[] IS NOT INITIAL.
lt_temp = morph_selopt_to_rfc( if_fieldname = 'devclass' it_selopt = s_devc[] ).
IF lt_temp IS NOT INITIAL.
APPEND ` AND ` TO lt_options.
APPEND LINES OF lt_temp TO lt_options.
ENDIF.
ENDIF.
APPEND VALUE #( fieldname = 'OBJECT' ) TO lt_fields.
APPEND VALUE #( fieldname = 'OBJ_NAME' ) TO lt_fields.
" get objects
CALL FUNCTION 'RFC_READ_TABLE'
DESTINATION p_rfcdes
EXPORTING
query_table = 'TADIR'
TABLES
options = lt_options
fields = lt_fields
data = lt_data
EXCEPTIONS
system_failure = 1 MESSAGE lf_message
communication_failure = 2 MESSAGE lf_message
OTHERS = 3.
LOOP AT lt_data ASSIGNING FIELD-SYMBOL(<s_data>).
INSERT <s_data> INTO TABLE gt_remote_objects.
ENDLOOP.
ENDMETHOD.
This method helps us verify if the objects we are interested in are also present and valid in the production environment.
📊 3. Get SCMON Data and Create the Result
Finally, we integrate the SCMON data to identify which objects are actually being used. This step is crucial because it provides the real usage data needed to flag unused objects. We loop through the objects and check their usage data from SCMON. Below is the code to retrieve and display the results:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
LOOP AT lt_objname ASSIGNING FIELD-SYMBOL(<s_objname>).
DATA(lf_remote) = abap_false.
" Check if available in remote
READ TABLE gt_remote_objects WITH KEY object = <s_objname>-object
obj_name = <s_objname>-obj_name
TRANSPORTING NO FIELDS.
IF sy-subrc EQ 0.
lf_remote = abap_true.
ENDIF.
CASE <s_objname>-object.
WHEN 'PROG'.
" Report/Include
CHECK <s_objname>-psubc IN s_psubc.
CHECK <s_objname>-pcdat IN s_pcdat.
IF <s_objname>-psubc EQ '1'.
" Program will be used as is
LOOP AT get_usage_data_prog( <s_objname>-obj_name ) ASSIGNING FIELD-SYMBOL(<s_outtab>).
<s_outtab>-cdat = <s_objname>-pcdat.
<s_outtab>-remote = lf_remote.
<s_outtab>-tcode = <s_objname>-tcode.
SELECT SINGLE text
FROM trdirt
INTO @<s_outtab>-title
WHERE name EQ @<s_objname>-obj_name
AND sprsl EQ @sy-langu.
IF sy-subrc NE 0.
SELECT SINGLE text
FROM trdirt
INTO @<s_outtab>-title
WHERE name EQ @<s_objname>-obj_name.
ENDIF.
APPEND <s_outtab> TO gt_outtab.
ENDLOOP.
ELSE.
" Include will be "extracted" to the main program
LOOP AT get_usage_data_include( <s_objname>-obj_name ) ASSIGNING <s_outtab>.
<s_outtab>-cdat = <s_objname>-pcdat.
<s_outtab>-remote = lf_remote.
<s_outtab>-tcode = <s_objname>-tcode.
APPEND <s_outtab> TO gt_outtab.
ENDLOOP.
ENDIF.
WHEN 'CLAS'.
" Handle Class
LOOP AT get_usage_data_clas( <s_objname>-obj_name ) ASSIGNING <s_outtab>.
<s_outtab>-cdat = <s_objname>-pcdat.
<s_outtab>-remote = lf_remote.
APPEND <s_outtab> TO gt_outtab.
ENDLOOP.
WHEN 'FUGR'.
" Handle Function Group
LOOP AT get_usage_data_fugr( <s_objname>-obj_name ) ASSIGNING <s_outtab>.
<s_outtab>-cdat = <s_objname>-pcdat.
<s_outtab>-remote = lf_remote.
APPEND <s_outtab> TO gt_outtab.
ENDLOOP.
WHEN OTHERS.
CONTINUE.
ENDCASE.
ENDLOOP.
This part of the report collects the relevant usage data for each object and checks whether it is being used remotely (in the production system). It then appends the results for later display in an ALV grid.
🧠 Final Thoughts
By combining SCMON data, remote checks, and custom reports, we were able to efficiently identify unused custom code during our SAP S/4 migration. This process helped us clean up the codebase, ensuring that only the necessary and relevant custom code was carried over to the new system.
If you are facing similar challenges during your S/4 migration, I hope this approach will save you time and effort in identifying unused code. Feel free to adapt the solution to your own needs or extend it to cover additional scenarios. Best of luck with your migration journey!
Happy coding!