Category Archives: Coding

#SAPPHIRENOW what it meant to a developer

I make no secret that I love developing. My favourite job title is “Chief HR Geek”, I adopt others as the need arises, but as a real in the dirt developer, content is always more important to me than flashy styling.

That’s why I was one of the only attendees at SAPPHIRENow 2014 wearing shorts. It’s fricking HOT in Orlando in June, wearing a suit?! Are you kidding me?

But it’s worth noting that to most businesses in the first instance, flashy styling is worth more than content! BUT – flashy styling with content, that’s awesome.

Fiori

With the announcement of Fiori being available as part of standard maintenance (yar boo sucks to those companies who’ve decided to skip SAP maintenance and have a third party do it) there comes the possibility of a double whammy of flashy styling and good underlying content.

The demo of a CFO drilling down in real-time to underperforming or problematic areas of the business and analysing why was compelling. I think Robbo has written about this as the killer app for HANA. I think he might be right.

But the key thing for a developer here, was the front end that this was achieved with, wasn’t a Business Objects add-on, wasn’t some WDA functionality. It was SAPUI5 over an OData layer exposed by Gateway.

If companies are going to be able to adopt these applications – and more and more of them are coming – there is going to be a clear need to support them.

Using tooling to build UI5 apps using ABAP won’t cut it

Whilst there are some amazing frameworks out there to help migrate stuck-in-the-mud ABAP developers across to building UI5 app, this does not help when there is a need to extend a standard Fiori app. Developers will need to learn JavaScript (or more properly ECMAScript, but that’s just me being pedantic.) If you can’t code JavaScript and refuse to learn, start calculating your redundancy payout because that’s what you’re going to be worth to your company. Alternatively, brush up on your SQL skills – and you can start writing some of the pushdown code for HANA. Either way, ABAP is going to be complementary to either DB manipulation or front-end display, but not a stand-alone skill set.

Fiori extension points

Did you know that many (not all!) Fiori apps have built-in extension points? You can use these to substantially alter the behaviour and appearance of the app. But to do so, there is something you should know – guess what? JavaScript!  Whilst the RDE (fingers cross for R to start meaning Rapid in near future) allows for some pretty amazing WYSIWYG modification to apps, the likelihood is that some form of developer intervention will be required. At the very least someone is going to have to figure out if the business requirement can/can’t be met using this simple customisation. And what skill set is going to be needed to figure out what those extensions can/can’t do? Yep you guessed it, JavaScript.

In Summary

For once I’m going to keep to a simple post without the detail that me as a developer I love so much. Because I want to emphasis this message.the future is fiori

I’m eventually learning to understand, unless you have flashy styling (Fiori), it doesn’t matter how good your content is (HANA) you can’t sell it. Combine the two together, and you have something that will change the marketplace and means developers need to change their game.

Perhaps if I ever attend SAPPHIRENow again, I’ll compromise and wear my jeans.

 

Code for UML sequence diagram extract from ABAP for plantUML

Well after a bit of umming and erring, I’ve decided that the safest way for me to share the code that I developed to extract the UML diagrams from SAP is to share it here. I could have potentially used GitHub, but an entire repository for one file that I doubt I’ll ever change again seemed like overkill.

It’s worth referencing this recent post by Nigel James and the responses to it:

http://scn.sap.com/community/abap/blog/2013/08/16/share-and-share-alike

This code is shared under an Apache Licence version 2.0 http://www.apache.org/licenses/LICENSE-2.0

class ZCL_XU_SEQUENCE_DIAGRAM definition
 public
 inheriting from CL_ATRA_UML
 create public .
public section.
* Copyright 2013 Chris Paine
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
 methods IF_ATRA_UML_TOOL~CREATE_UML_DATA
 redefinition .
 methods IF_ATRA_UML_TOOL~GET_XMI
 redefinition .
protected section.
private section.
data _T_CALL_STACK type TY_CALL_STACK_TAB .
interface IF_ATRA_UML_TOOL load .
 methods ADD_CALL
 importing
 !IV_DIAGRAM type STRING
 !IS_DETAILS type IF_ATRA_UML_TOOL=>TY_SAT_RECORD
 !IV_CALLER type I
 !IV_CALLED type I
 returning
 value(RV_DIAGRAM) type STRING .
 methods ADD_PARTICIPANT
 importing
 !IV_DIAGRAM type STRING
 !IS_OBJECT type TY_OBJECT
 !IT_OBJECTS type TY_OBJECT_TAB
 !IV_FIRST_TIME type BOOLE_D
 returning
 value(RV_DIAGRAM) type STRING .
 methods ADD_RETURN
 importing
 !IV_DIAGRAM type STRING
 !IV_RETURN_TO type I
 returning
 value(RV_DIAGRAM) type STRING .
 methods FILL_GAPS
 importing
 !IT_WITH_GAPS type IF_ATRA_UML_TOOL~TY_SAT_TAB
 returning
 value(RV_FILLED_GAPS) type IF_ATRA_UML_TOOL~TY_SAT_TAB .
 methods GET_SEQUENCE_DIAGRAM
 returning
 value(RV_DIAGRAM) type STRING .
ENDCLASS.

CLASS ZCL_XU_SEQUENCE_DIAGRAM IMPLEMENTATION.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_XU_SEQUENCE_DIAGRAM->ADD_CALL
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_DIAGRAM TYPE STRING
* | [--->] IS_DETAILS TYPE IF_ATRA_UML_TOOL=>TY_SAT_RECORD
* | [--->] IV_CALLER TYPE I
* | [--->] IV_CALLED TYPE I
* | [<-()] RV_DIAGRAM TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD add_call.
DATA: lv_call TYPE string,
 ls_call_stack TYPE ty_call_stack,
 lv_text TYPE string,
 lv_prefix TYPE string.
IF iv_caller = iv_called AND is_details-caller <> is_details-called.
 lv_text = is_details-called && '->' && is_details-called_mod.
 ELSE.
 lv_text = is_details-called_mod.
 ENDIF.
CASE is_details-id.
 WHEN 'F'.
 lv_prefix = 'Perform'.
 WHEN 'U'.
 lv_prefix = 'Call FM'.
 WHEN 'm'.
 lv_prefix = 'Call method'.
 WHEN 'R'.
 lv_prefix = 'Create instance of class'.
 lv_text = is_details-called.
 WHEN 'X'. "skip over SAP code
 lv_prefix = '<b>Skipping over SAP code until calling'.
 lv_text = is_details-called_mod && '</b>'.
 ENDCASE.
lv_call = iv_caller && ` -> ` &&
 iv_called && `: ` &&
 lv_prefix && ` ` &&
 lv_text && cl_abap_char_utilities=>cr_lf &&
 `activate ` && iv_called && cl_abap_char_utilities=>cr_lf.
IF is_details-id = 'X'.
 lv_call = lv_call && `note over ` && iv_caller && ',' && iv_called && cl_abap_char_utilities=>cr_lf &&
 'Standard SAP code has called some custom code' && cl_abap_char_utilities=>cr_lf &&
 'end note' && cl_abap_char_utilities=>cr_lf.
 ENDIF.
ls_call_stack-code = iv_called.
 ls_call_stack-sap_code = is_details-aus_ebene.
 APPEND ls_call_stack TO _t_call_stack.
rv_diagram = iv_diagram && lv_call.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_XU_SEQUENCE_DIAGRAM->ADD_PARTICIPANT
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_DIAGRAM TYPE STRING
* | [--->] IS_OBJECT TYPE TY_OBJECT
* | [--->] IT_OBJECTS TYPE TY_OBJECT_TAB
* | [--->] IV_FIRST_TIME TYPE BOOLE_D
* | [<-()] RV_DIAGRAM TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD add_participant.
DATA: lv_participant TYPE string,
 lv_name TYPE string,
 ls_object TYPE ty_object,
 lv_counter TYPE i,
 lv_create_or_not TYPE string.
CASE is_object-object_type.
 WHEN 'CLAS'.
 IF is_object-instance = 0.
 lv_name = 'Static Methods of Class\n' && is_object-object.
 ELSE.
* check how many other instances of same class exist.
 LOOP AT it_objects INTO ls_object
 WHERE object = is_object-object
 AND object_type = is_object-object_type
 AND instance <> 0.
 lv_counter = lv_counter + 1.
 IF ls_object-instance = is_object-instance.
 EXIT. "leave the loop.
 ENDIF.
 ENDLOOP.
 lv_name = `Instance ` && lv_counter && ` of Class\n` &&
 is_object-object.
 ENDIF.
 WHEN 'FUGR'.
 lv_name = `Function Group\n` && is_object-object+4.
 WHEN OTHERS.
 lv_name = is_object-object_type && '\n' && is_object-object.
 ENDCASE.
IF iv_first_time = abap_true.
 lv_create_or_not = 'participant "'.
 ELSE.
 lv_create_or_not = 'create "'.
 ENDIF.
 lv_participant = lv_create_or_not &&
 lv_name && `" as ` &&
 is_object-code && cl_abap_char_utilities=>cr_lf.
rv_diagram = iv_diagram && lv_participant.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_XU_SEQUENCE_DIAGRAM->ADD_RETURN
* +-------------------------------------------------------------------------------------------------+
* | [--->] IV_DIAGRAM TYPE STRING
* | [--->] IV_RETURN_TO TYPE I
* | [<-()] RV_DIAGRAM TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD add_return.
DATA: ls_call_stack_from TYPE ty_call_stack,
 ls_call_stack_to TYPE ty_call_stack,
 lv_stack_pointer TYPE i,
 lv_return TYPE string.
 lv_stack_pointer = lines( _t_call_stack ).
 rv_diagram = iv_diagram.
IF lv_stack_pointer > 0.
 READ TABLE _t_call_stack INTO ls_call_stack_from INDEX lv_stack_pointer.
 WHILE ls_call_stack_from-sap_code >= iv_return_to.
 READ TABLE _t_call_stack INTO ls_call_stack_to INDEX ( lv_stack_pointer - 1 ).
lv_return = ls_call_stack_from-code && ` --> ` && ls_call_stack_to-code && cl_abap_char_utilities=>cr_lf &&
 `deactivate ` && ls_call_stack_from-code && cl_abap_char_utilities=>cr_lf.
 rv_diagram = rv_diagram && lv_return.
DELETE _t_call_stack INDEX lv_stack_pointer.
 lv_stack_pointer = lv_stack_pointer - 1.
 IF lv_stack_pointer = 0.
 EXIT.
 ENDIF.
 READ TABLE _t_call_stack INTO ls_call_stack_from INDEX lv_stack_pointer.
 ENDWHILE.
 ENDIF.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_XU_SEQUENCE_DIAGRAM->FILL_GAPS
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_WITH_GAPS TYPE IF_ATRA_UML_TOOL~TY_SAT_TAB
* | [<-()] RV_FILLED_GAPS TYPE IF_ATRA_UML_TOOL~TY_SAT_TAB
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD fill_gaps.
 DATA: lt_without_gaps TYPE if_atra_uml_tool=>ty_sat_tab,
 ls_previous_line TYPE if_atra_uml_tool=>ty_sat_record,
 ls_gap TYPE if_atra_uml_tool=>ty_sat_record,
 ls_line TYPE if_atra_uml_tool=>ty_sat_record.
LOOP AT it_with_gaps INTO ls_line.
 IF ls_previous_line IS NOT INITIAL.
 IF ( ls_line-caller <> ls_previous_line-called
 OR ls_line-caller_inst <> ls_previous_line-called_inst
 OR ls_line-caller_type <> ls_previous_line-called_type ) AND
 ls_line-aus_ebene > ls_previous_line-aus_ebene.
* need to insert a new line into the table at this point to link the two together.
 ls_gap-caller = ls_previous_line-called.
 ls_gap-caller_inst = ls_previous_line-called_inst.
 ls_gap-caller_type = ls_previous_line-called_type.
 ls_gap-called = ls_line-caller.
 ls_gap-called_inst = ls_line-caller_inst.
 ls_gap-called_type = ls_line-caller_type.
 ls_gap-aus_ebene = ls_previous_line-aus_ebene.
 ls_gap-id = 'X'. "skip
APPEND ls_gap TO lt_without_gaps.
 ELSE.
 ls_previous_line = ls_line.
 ENDIF.
 ELSE.
 ls_previous_line = ls_line.
 ENDIF.
APPEND ls_line TO lt_without_gaps.
 ENDLOOP.
rv_filled_gaps = lt_without_gaps.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Private Method ZCL_XU_SEQUENCE_DIAGRAM->GET_SEQUENCE_DIAGRAM
* +-------------------------------------------------------------------------------------------------+
* | [<-()] RV_DIAGRAM TYPE STRING
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD get_sequence_diagram.
 DATA: ls_line TYPE if_atra_uml_tool=>ty_sat_record,
 ls_object TYPE ty_object,
 ls_last_call TYPE if_atra_uml_tool=>ty_sat_record,
 lv_next_object TYPE i,
 lv_first_time TYPE boole_d,
 ls_first_call TYPE ty_call_stack,
 lt_objects TYPE ty_object_tab.
FIELD-SYMBOLS: <ls_caller> TYPE ty_object,
 <ls_called> TYPE ty_object.
rv_diagram =
 '@startuml' && cl_abap_char_utilities=>cr_lf &&
 'hide footbox' && cl_abap_char_utilities=>cr_lf &&
 'autonumber' && cl_abap_char_utilities=>cr_lf.
* first of all, lets get all the objects
LOOP AT if_atra_uml_tool~sat_tab INTO ls_line.
IF ( ls_line-caller(1) = 'Z' OR ls_line-called(1) = 'Z' )
 AND ls_line-caller IS NOT INITIAL.
 ls_object-object = ls_line-caller.
 ls_object-object_type = ls_line-caller_type.
 ls_object-instance = ls_line-caller_inst.
IF ls_object-instance <> 0.
 READ TABLE lt_objects TRANSPORTING NO FIELDS
 WITH KEY instance = ls_object-instance.
 ELSE.
 READ TABLE lt_objects TRANSPORTING NO FIELDS
 WITH KEY object = ls_object-object
 instance = ls_object-instance.
 ENDIF.
 IF sy-subrc <> 0.
 APPEND ls_object TO lt_objects.
 ENDIF.
ls_object-object = ls_line-called.
 ls_object-object_type = ls_line-called_type.
 ls_object-instance = ls_line-called_inst.
IF ls_object-instance <> 0.
 READ TABLE lt_objects TRANSPORTING NO FIELDS
 WITH KEY instance = ls_object-instance.
 ELSE.
 READ TABLE lt_objects TRANSPORTING NO FIELDS
 WITH KEY object = ls_object-object
 instance = ls_object-instance.
 ENDIF.
 IF sy-subrc <> 0.
 APPEND ls_object TO lt_objects.
 ENDIF.
*we do special handling for construction - but don't want to show twice in diagram
 IF ls_last_call-id = 'R'. "constructor
 IF ls_line-called_mod = 'CONSTRUCTOR'
 AND ls_line-called = ls_last_call-called
 AND ls_line-called_inst = ls_last_call-called_inst.
* this line is a duplicate of the previous line
 DELETE if_atra_uml_tool~sat_tab.
 ENDIF.
 ENDIF.
ELSE.
* this line does not concern code that we are concerned about documenting
 DELETE if_atra_uml_tool~sat_tab.
 ENDIF.
ls_last_call = ls_line.
 ENDLOOP.
* now there will possibly be some gaps in the sequence if custom code calls standard SAP code
* that calls custom code.
if_atra_uml_tool~sat_tab = fill_gaps( if_atra_uml_tool~sat_tab ).

* ok now have all objects, let's go through and put together the diagram
 ls_last_call-aus_ebene = - 1.
 lv_first_time = abap_true.
 LOOP AT if_atra_uml_tool~sat_tab INTO ls_line.
* is this an implicit return?
 IF ls_line-aus_ebene <= ls_last_call-aus_ebene.
rv_diagram = add_return( iv_diagram = rv_diagram
 iv_return_to = ls_line-aus_ebene ).
 ENDIF.
* does caller object already exist?
 READ TABLE lt_objects ASSIGNING <ls_caller>
 WITH KEY object = ls_line-caller
 instance = ls_line-caller_inst.
IF <ls_caller>-code IS INITIAL.
 lv_next_object = lv_next_object + 1.
 <ls_caller>-code = lv_next_object.
* add participant
 rv_diagram = add_participant( iv_diagram = rv_diagram
 is_object = <ls_caller>
 it_objects = lt_objects
 iv_first_time = lv_first_time ).
 lv_first_time = abap_false.
ENDIF.
* handle the case of very first call
 IF ls_first_call IS INITIAL.
 ls_first_call-code = 1.
 ls_first_call-sap_code = ls_line-aus_ebene.
 APPEND ls_first_call TO _t_call_stack.
 rv_diagram = rv_diagram && 'activate 1' && cl_abap_char_utilities=>cr_lf.
 ENDIF.
* does called object already exist?
 READ TABLE lt_objects ASSIGNING <ls_called>
 WITH KEY object = ls_line-called
 instance = ls_line-called_inst.
IF <ls_called>-code IS INITIAL.
 lv_next_object = lv_next_object + 1.
 <ls_called>-code = lv_next_object.
* add participant
 rv_diagram = add_participant( iv_diagram = rv_diagram
 is_object = <ls_called>
 it_objects = lt_objects
 iv_first_time = abap_false ).
ENDIF.
rv_diagram = add_call( iv_diagram = rv_diagram
 iv_caller = <ls_caller>-code
 iv_called = <ls_called>-code
 is_details = ls_line ).
 ls_last_call = ls_line.
 ENDLOOP.
rv_diagram = add_return( iv_diagram = rv_diagram
 iv_return_to = ls_first_call-sap_code ).
rv_diagram = rv_diagram && '@enduml' && cl_abap_char_utilities=>newline.
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_XU_SEQUENCE_DIAGRAM->IF_ATRA_UML_TOOL~CREATE_UML_DATA
* +-------------------------------------------------------------------------------------------------+
* | [--->] IT_SAT_TAB TYPE IF_ATRA_UML_TOOL=>TY_SAT_TAB
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD if_atra_uml_tool~create_uml_data.
* rebuild to export data as UMLet compatible source
 if_atra_uml_tool~sat_tab = it_sat_tab.

DATA: lv_filename TYPE string,
 lv_path TYPE string,
 lv_fullpath TYPE string.
cl_gui_frontend_services=>file_save_dialog(
 EXPORTING
 window_title = 'Save As PlantUML data' " Window Title
 default_extension = '.txt'" Default Extension
* default_file_name = " Default File Name
* with_encoding =
* file_filter = " File Type Filter Table
* initial_directory = " Initial Directory
* prompt_on_overwrite = 'X'
 CHANGING
 filename = lv_filename " File Name to Save
 path = lv_path " Path to File
 fullpath = lv_fullpath " Path + File Name
* user_action = " User Action (C Class Const ACTION_OK, ACTION_OVERWRITE etc)
* file_encoding =
 EXCEPTIONS
 OTHERS = 0 ).
if_atra_uml_tool~fname = lv_fullpath.
if_atra_uml_tool~generate_xmi_file( ) .
ENDMETHOD.
* <SIGNATURE>---------------------------------------------------------------------------------------+
* | Instance Public Method ZCL_XU_SEQUENCE_DIAGRAM->IF_ATRA_UML_TOOL~GET_XMI
* +-------------------------------------------------------------------------------------------------+
* | [<---] C_DATA TYPE REF TO DATA
* +--------------------------------------------------------------------------------------</SIGNATURE>
METHOD if_atra_uml_tool~get_xmi.
DATA lv_ref_xstr TYPE REF TO xstring.
 FIELD-SYMBOLS <lv_xstr> TYPE xstring.
CREATE DATA lv_ref_xstr.
 ASSIGN lv_ref_xstr->* TO <lv_xstr>.
TRY.
 <lv_xstr> = cl_bcs_convert=>string_to_xstring( iv_string = get_sequence_diagram( ) ).
 CATCH cx_bcs. "#EC NO_HANDLER
* if this happens we're buggered. not much to do really
ENDTRY.
 c_data = lv_ref_xstr.
ENDMETHOD.
ENDCLASS.

 

 

I hate doco

It’s kinda a mantra that I live a reasonable part of my life to. I think the above image is a not unjustifiable representation of my feelings about documentation:

But there are many good reasons that I should be doing doco. Most of them are supposed to save the customer money in the long run. And occasionally by doing the doco, I even find some small errors in my code that I hadn’t seen before.

 InnoJam Sydney 2011

Before InnoJam had any of that fun fluffy design thinking aspect to it, we ran one in Sydney. It was good fun, and people could build whatever the heck they wanted.

In staying true to my aversion for writing doco, I came up with an idea about auto-generation UML diagrams from SAP code.

Here’s a video of the solution we came up with:

here’s a link to the Prezi that I presented in that video:

http://prezi.com/zri5q-ib4vzp/?utm_campaign=share&utm_medium=copy&rc=ex0share

I wrote a blog post about it:

https://scn.sap.com/blogs/chris.paine2/2011/08/10/programmers-are-lazy–innojam-them

But it was long time ago and the move to a new version of SCN has kinda buggered it up.

Anyway, in short – Rui never managed to get the terms and conditions of CodeExchange changed to a level where I’m happy to support it and put code in there. I’m pretty sure he tried though. So I didn’t do anything with the code.

 

Fast forward 3 years

I have a whiteboard in the office covered in post-it notes. They all represent at least one development that I’ve done for the current project I’m working on. At the beginning of the project, I was very good, and did all my doco as I went along. Then the poo hit the fan, and everyone wanted everything done yesterday, and didn’t care about doco.

So I now have a whiteboard full of post-it notes that represent potentially weeks of doco hell for me. So in my “free time” in the evening I decided to see if I could recreate the solution that we’d built in Sydney, and perhaps make it a little nicer.

 

UML output

The first thing I decided, was that I was NOT going to try to build the graphical output myself. Having had lots of fun in Sydney trying to make Gravity do something it really wasn’t designed for I thought I’d research how else I could get my interaction diagrams created.

If in doubt Wikipedia

http://en.wikipedia.org/wiki/List_of_UML_tools

There were loads there, and I’d pretty much decided on UMLet when I discovered something about interaction diagrams. Basically, interaction diagrams are supposed to show interaction between objects. Bit bloody obvious really. However, the thing is, if I’m documenting my code, I’d really like to show the interaction within my objects too. I.e. if I make a call to a private method of my class, I’d really like that to show up in my diagram. Given that interaction diagrams are only supposed to show external interaction it’s not surprising that most of the tools for creating the diagrams don’t really support this idea of internal object calls.

So a bit of browsing later and I found PlantUML. It has some awesome functionality for creating sequence diagrams, actually, most UML diagrams it seems, but it was the sequence diagrams that I was interested in.

Here’s a simple example:simple_example

 

 

See how it’s quite possible to show “internal” calls of an instance and also show the life time of those calls. This feature I didn’t find on the other free UML tools that I looked at. There are a bunch of other formatting features that can be used too. If you’re interested check out their website: http://plantuml.sourceforge.net/sequence.html

 

Intercepting the SAP standard UML generation

So in transaction SAT there is the possibility to generate your own JNet UML sequence diagram (this exists as standard.)

press the button

 

However, it does not allow you to do things like filter out standard SAP routines (as far as I know! If anyone can tell me how to do this (without needing to list every method I call, please let me know!) When I was looking at one of my examples, where I ran a program to generate a performance review document for an employee, there were over 100,000 different routines called. Only about 400 of those calls involved my code, so you can imagine generating a UML diagram for the whole 100,000 calls would be a bit of overkill (not to mention an impossible to read diagram).

In customer systems there is a function module  ATRA_UML_DECIDER  that has been purposely handicapped. One does have to wonder why this has been done, but nevertheless it has.  It allows the user to chose from a list of potential UML extraction routines. All of these routines implement the IF_ATRA_UML_TOOL interface. There are classes for extracting to JNet, Borland Together and Altova. Now, I’m sure that Borland and Altova have good products, it’s just that I don’t really want to spend money on then when there are perfectly good (for my tasks) free and open source products out there.

There is a factory class/method CL_ATRA_UML_FACTORY  that creates an instance of a class implementing the interface. I overrode this method to use my particular extractor if it was me running the code. In the future, I might enhance this to check for a user role, or perhaps a user parameter, that’s trivial, the main point will be to allow others to access this logic too.

The guts of the code

Simply my implementation of the interface reads the table of data that is passed to the interface, removes all calls that aren’t to or from custom code and then builds a PlantUML representation of that code.

Here’s a very simple output that generates the diagram above.

@startuml
hide footbox
autonumber
participant "Instance 1 of Class\nZCL_HR_EMPLOYEE" as 1
1 -> 1: Call method GET_HELD_QUALIFICATIONS
activate 1
1 -> 1: Call method ZCL_HR_OBJECT->GET_RELATIONSHIPS
activate 1
create "Static Methods of Class\nCL_HRBAS_READ_INFOTYPE" as 2
1 -> 2: Call method GET_INSTANCE
activate 2
2 --> 1
deactivate 2
create "Instance 1 of Class\nCL_HRBAS_READ_INFOTYPE" as 3
1 -> 3: Call method IF_HRBAS_READ_INFOTYPE~READ_PLAIN_1001
activate 3
3 --> 1
deactivate 3
1 --> 1
deactivate 1
create "Instance 1 of Class\nZCL_HR_QUALIFICATION" as 4
1 -> 4: Create instance of class ZCL_HR_QUALIFICATION
activate 4
4 -> 4: Call method ZCL_HR_OBJECT->CONSTRUCTOR
activate 4
create "Function Group\nRHS0" as 5
4 -> 5: Call FM RH_GET_ACTIVE_WF_PLVAR
activate 5
5 --> 4
deactivate 5
4 --> 4
deactivate 4
4 --> 1
deactivate 4
deactivate 1
@enduml

 

 A slightly less trivial example

The following code does some pretty simple stuff, it finds who is my manager, and finds out what required qualifications my position/job has.

DATA: lo_emp TYPE REF TO zcl_hr_employee,
lt_managers TYPE ztthr_employee_objects,
lt_required_quals TYPE ztthr_qualifications.

TRY.
lo_emp = zcl_hr_employee=>get_employee_by_user_id( sy-uname ).

lt_managers = lo_emp->get_position( )->get_managers_recursive( ).
lt_required_quals = lo_emp->get_position( )->get_required_qualifications( ).

CATCH zcx_hr_no_managing_pos_found  ” No managing position found
zcx_hr_no_holders_found  ” no holders for position found.
zcx_hr_no_position_found    ” no position found
zcx_hr_user_id_not_found.  ” cannot find user id for employee
ENDTRY.

So I thought I’d trace it:

it works out at around 200,000 different routines being called. 62 of those are my code, the rest standard.

run through 1

First of all, I need to schedule a trace for myself…

run through 2

 

run through 3

 

Need to know which session I will be recording. If I left it as “Any” it will start recording this session, not very useful!

run through 4

 

Session 2 it is!

run through 5

run through 6

actually run it now!

and the schedule status changes to executed.

run through 7

 

I now need to delete the scheduled measurement. Despite the intimidating words, this does not delete my measurement, just the scheduling of it.

run through 8

 

now swapping to the “Evaluate” tab in SAT I can see my measurement, and I can click to output to UML

run through 9

 

 

Clicking on the button triggers a bit of a pause whilst the system code chugs away and does loads of stuff it doesn’t need to do…

 

Then

run through 10

Save the data and PlantUML starts converting it immediately:

run through 11

 

and the result:

example

Would probably have been a little bigger if my employee had a job assigned to their position, but you can see how incredibly easy this now makes documenting the functionality I’ve built.

I’m still considering how to make the code publicly available. I’m sure someone else would be happy to post it to CodeExchange, so perhaps I’ll let them.

ABAP Code Naming Conventions

Ok, you can probably guess that I’m not the most conventional person. I probably don’t fit the mould of the stereotypical developer either. I’m certainly not what one would call an introvert.

So please take this with the necessary pitch of salt. (especially if you’re one of the people who writes the code naming conventions that I have to follow from time to time 😉 )A pinch of salt required

<rant>

Why on earth does every SAP project I go to insist on such inane naming standards for the code? The SAP editor is a wonderful IDE (caveat I did not say it was the best IDE) that allows you to see the definition of any variable with a simple double click – so why on earth are you so worried that I should prefix all my local variable definitions with an ‘l’? What on earth potential benefit can this have on the code readability? Perhaps it helps if you’re still one of my nemesis developers who are passing all your variables between methods through the use of global variables and/or singletons. Perhaps one needs to look at a piece of code, see lots of l’s and that gives satisfaction? The use of Hungarian Notation in ABAP code seems to be universal, although never it seems implemented in the same way.

Then when I define a structure, I must prefix it with a “S” just so you can be sure that it isn’t actually a table or a single field, or so help me, a woolly mammoth. When I look in the IDE view of the package I am developing, all of these different things are arranged in a tree so you can easily tell one from the other. Again a single double-click can bring me to the definition if it is ever referred to in a piece of code. Perhaps it might save some time looking at a variable definition to see if it is a table, a structure, object reference or a variable – but if I’m in the code, it should be pretty damn obvious! If I’m appending or inserting into it, it’s a table. If I’m referencing a sub-field of it, it’s a structure. If I’m assigning a value to it it’s a variable, if I’m creating an instance of it, it better be an object reference. There again may be cases of my nemeses developers still using tables with header lines and confusing the heck out of me. But I’m hoping that the code inspector might weed at least that out.

Searching outside of the SAP world the use of Hungarian Notation within code is not universally disliked, but with such a clear list of disadvantages and such luminaries as Uncle “Bob” Martin and Linus Torvalds against it, you’d have to proclaim yourself a pretty die-hard supporter of “doing it the old way” not to just think a little – “is this really useful? Or is it even potentially bad?”.

Then there comes the requirement that every object should reference the area of use it is intended for. Thus the forth and fifth characters of the object name must be “HR” or “PA” or “XX” or whatever. The use of Positional Notation for implicit metadata about a component is, however not something I’ve seen outside of SAP projects except for the COBOL example given in the linked Wikipedia page. At this point when reading the naming convention guide, I casually check if there is any mention of packages and package hierarchies and hope upon hope, package interfaces. When there isn’t, I sigh again and just bite my tongue again. Because SAP has provided a wonderful way of helping us see what use a component is put to – as every component must belong to a package, and that package can (and should) have an application component defined. And to give even more clarity, the package can have a super-package, thus grouping all like component together, whatever types they are and where ever in their object names they have a ridiculous two character code. The package interface can even tell you if the object is safe for use outside of the package. What a great concept!

So instead of spending time thinking about whether the components we are building are truly reusable, and what the scope of that reuse is. We spend hours checking if we have the first n characters of our our objects correct according to the development standard book.

</rant>

One day someone will be silly enough to let me do it my way, I’ll confuse the bejeebers out of all the guys who’ve only been coding ABAP badly for the last 10 year and the project will potentially fail because I’ll spend my entire time looking for enough of a development team that can understand that following a rigid way of doing things isn’t always the best way to do it…. <sigh>