Skip to Content
Author's profile photo Jörg Krause

Beyond pretty printer – legible code in times of ABAP 740+

Pretty printer is very useful, but in some cases, you have to be a “Pretty coder” yourself.

Especially with ABAP 740, where all those expressions came up and finally made us feel like real programmers – as we now could do things like

data(ls_result) = new lcl_data_processor( new lcl_data_provider( p_matnr ) )->do_something_important( iv_simulation = abap_true ).

just like all the others.

The ABAP statements are getting longer these days, so I asked myself how to distribute them into several lines and keep the legibility. I started with something like

      if line_exists( lt_process[ orderid = ls_queue-orderid
                                  activity = ls_queue-activity ] ).

But when it comes to real deeply nested expressions, this would lead to enormous line sizes. What I am doing nowadays, is more like this:

        rt_res = value #(
          for s_oper in lt_operations where ( sub_activity = space  )
          ( value #(
              let ltext =  actvt_longtext_read( s_oper ) in
              act_end_date          = s_oper-act_end_date
              act_end_time          = s_oper-act_end_time
              act_start_date = reduce d(
                init d = s_oper-act_start_date
                for s_sub in it_sub_oper where (

i.e. increase indentation by bracket level. When closing the brackets, I just do

                    then cond #(
                      when s_subd-isdd < d
                      then s_subd-isdd
                      else d )
                    else d ) ) )

that is closing everything in one line.

Still I’m not totally satisfied with my approach, especially with the closing of bracket levels. But wasting a line for each closing bracket seems ugly to me.

What is your approach in coding very long ABAP statements?

Assigned Tags

      23 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Jacques Nomssi Nzali
      Jacques Nomssi Nzali

      Hello Jörg,

      you might want to check Scheme's guidelines.

      regards,

      JNN

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      Excellent link!

      Author's profile photo Joachim Rees
      Joachim Rees

      Excellent link, indeed! Thanks Jacques Nomssi Nzali !

      Author's profile photo Mike Pokraka
      Mike Pokraka

      An excellent topic. But I would like to add this predates 7.4, but the new syntax has just complicated it.

      My view is that there is a degree of personal taste and style. I tend to make a little effort to format code nicely, with future readers in mind.

      Too much horizontal information decreases legibility, as does too much vertical. So it depends a little on the content.

      I try not to make lines too long, as people (myself included) should also be able to read it on a laptop or in the debugger. So 'hanging vertical' arrangements are OK if the line isn't too long, otherwise I start to wrap it. The important thing is consistency - putting all grouped bits together.

      "If the line is short
      data(foo) = bar->calculate_something( i_param   = 1 
                                            i_another = 2
                                            i_more    = 3 ).
      
      "If the line gets too long, I use double-indents to show continuation
      data(foo) = zcl_bar_factory=>get_instance->get_bar( 
          i_param   = 1 
          i_another = 2
          i_more    = 3 ).
      
      "And if it gets really long or for chained methods, wrap 'em all:
      data(foo) = zcl_bar_factory=>get_instance( )->get_bar(
          )->set_value( i_name = 'ID'       i_value = my_id
          )->set_value( i_name = 'CUSTOMER' i_value = customer_number
          )->set_value( i_name = 'AMOUNT'   i_value = order_amount    ).
       

      Note that I'm putting two parameters on the same line in the last example. Usually each parameter gets it's own line, but here the tabular format of the entire name-value dataset is more sensible.

      All in all it's content-driven. Consider simple SQL statements, just for fun here are two styles applied to the wrong type of statements:

       

       

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      This format makes sense to me. The last statement is easily readable as well as easily copy-pasted if someone needs to add another value.

      I'm glad to see at least someone understands the value of readability. Unfortunately, some of the 7.4 statements seem to be a "how much can I stuff into this one command" contests. It looks much better when formatted like in these examples. It's like a well edited book. 🙂

      Nice blog, Jörg Krause !

       

      Author's profile photo Mike Pokraka
      Mike Pokraka

      ...and the reason my comment ended rather suddenly is because I posted by accident and couldn't edit it. One of the many long-standing bugs on this "new" platform.

      I was going to write something along the lines of folks that cram a huge long SQL statement into one line vs others that write "vertical SQL" for even the simplest thing. I find this kind of thing painful:

      SELECT auart
        FROM vbak
        INTO lv_auart
        WHERE vbeln = lv_vbeln.   
      Author's profile photo Bärbel Winkler
      Bärbel Winkler

      Well, I'm actually in favor of writing selects like that regardless of how complex they are (or get).

      I do put more than one field into a line of the field list though - how many depends on the number of fields all told and how long the names are. This is how it might look:

        SELECT as4user   strkorr trkorr  trfunction trstatus
               tarsystem as4date as4time langu
               as4text   client  tarclient
          FROM e070ctv
          INTO CORRESPONDING FIELDS OF TABLE gt_e070ctv
         WHERE trkorr     IN s_cts
           AND as4user    IN s_user
           AND as4date    IN s_date
           AND trfunction IN s_trf
           AND trstatus   IN s_trst
           AND as4text    IN s_text.
      

      To me, having the same format for each select in a program improves readability and I find it more painful if each select has a different look and feel. ?

       

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      I also tend to do "vertical" sql statements - just to be able to easily add things like fields or conditions afterwards.

      I see that you align the keywords in your select in order to align the following denominators. This may be an optical advantage, but to me it seems to be an obstacle in fluently typing in the code.

      My approach here is:

      select 
        matnr as material,
        spras as language,
        maktx as description
        from mara
          join makt 
            on makt~matnr = mara~matnr
        for all entries in @lt_key
        where
          matnr = @lt_key-matnr and
          spras = @lt_key-spras
        into table @data(lt_mat_texts).

      note that I use the new SQL syntax here. Especially to have one line for each field of the field list makes it easier to add new fields afterwards. Apart of that, it lets you use the block select function of the editor to grab all fields, which comes handy if you later want to create an according type for the result.

      Author's profile photo Mike Pokraka
      Mike Pokraka

      I think you're missing my point. Your example is sensible and close to the way I'd  write it.

      We read from left to right, so this is the easiest to read if the logic is simple - e.g. my example of picking a single value from a table, or checking existence or counting. There's no need to split this over three lines:

      SELECT SINGLE @abap_true FROM vbak WHERE vbeln = order_number.

      It's perfectly understandable as is. Contrast with:

      SELECT SINGLE @abap_true 
             FROM  vbak 
             WHERE vbeln = order_number.

      Writing it as a list of clauses decreases readability in this case because we have to scan left to right and up and down. Typically I find myself reading the second version twice to make sure I understood it.

      So my original point is that it's about balance. When the statements get more complex, then ease of comprehension is more important than ease of reading. Horizontal makes for more fluid reading, vertical provides more structure.

       

      Author's profile photo Enno Wulff
      Enno Wulff

      I understand both of you Bärbel Winkler and Mike Pokraka and I often switch between both variants. When is a statement short enough to put it in one line and when should I split it into several lines? It depends on many things - at least on the weather outside... 😉

      One word about "reading": you mostly do not read everything. You fly over the code. Therefore the column based format is much better, because your eyes can catch the content easier. Even if the lines are not that long.

      Mostly I try to format the code by respecting best copy and paste and inserting and deleting conditions. That means: If there is something in the command or command flow that might become obsolete or if there must be inserted something: What is the best format w/o touching the adjacent lines?

      Therefore I mostly do not use colons like

      DATA: a TYPE i,
            b TYPE d,
            c TYPE n.

      Instead I always use:

      DATA a TYPE i.
      DATA b TYPE d.
      DATA C TYPE n.

      This makes it easy to decomment unused variables or cut variables when refactoring the function. It also makes it easier to group variables to blocks using CTRL-ALT-UP/ DOWN:

      The same with SELECT fields FROM or VALUE

      DATA(data) = VALUE my_table_type( 
                       ( one = '1' two = '2' )
                       ( one = '6' two = '4' ) 
                    ).

      Overall: Beautfying all the complex new 740+ commands is really a challenge... 🙂

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      On the other hand, if you use colons for multiple data/type declaration, pretty printer helps you aligning the statements:

      data: first_field type c,
            second_field type c,
            short type c.
      
      Apply pretty printer:
      
      data: first_field  type c,
            second_field type c,
            short        type c.
      
      

      Since I am working often with block selection of variables, this comes handy to me.

      Author's profile photo Bärbel Winkler
      Bärbel Winkler

      Not sure, if I'm really missing your point, Mike! I guess it just comes down to slightly different points of view with my preference being a similar format for each select statement in a program. I'm actually quite happy that ABAP is flexible enough to not really insist on a specific way/sequence to write statements like this - otherwise it would just be one more thing to memorise and stick to (and there are more than enough of those already).

       

      Author's profile photo Jelena Perfiljeva
      Jelena Perfiljeva

      I agree. I write exactly like in your example above, although if it's just one field I might write SELECT ... INTO ... and then the rest on the next lines.

      This seems more of a personal preference / style thing. I wouldn't hold back a code review if I saw Mike's version either. It's just a habit for me to write on separate lines. Besides, simple statement today, complex tomorrow. 🙂

      When it comes to SELECT, the most irritating thing though is ABAP's very strong preference in the placement of spaces and parenthesis. And if you do it wrong, you don't get an intelligent error message like "hey, I think you forgot a space here" but something like "I have no idea what these fields are". At least in 7.3, maybe this got improved in later versions.

      Author's profile photo Joachim Rees
      Joachim Rees

      Haha, I thought you had somehow implemented a screenshot from some 3rd-Party site, which was then blocked by my uMatrix-Opera AddOn - great to learn the explanaiton is much simpler!

      Author's profile photo Bärbel Winkler
      Bärbel Winkler

      Thanks for this blog post, Jörg!

      As a veritable newbie when it comes to ABAP OO, I very often have a hard time deciphering what long statements actually produce as a result. I even tend to "space out" much like I do when confronted with mathematical or scientific notations and formulas. So, something I really appreciate in addtion to thoughtful formatting is a clarifying comment preceeding such a statement.

      I'm also a fan of a strategically placed empty line preceeding and following (blocks of) statements.

      Author's profile photo Kenneth Moore
      Kenneth Moore

      My solution?  Don't do it!  🙂

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      Just one more thing 🙂

      What do you think about BIG indentations like the one in Mike Pokraka 's comment:

      "If the line is short
      data(foo) = bar->calculate_something( i_param   = 1 
                                            i_another = 2
                                            i_more    = 3 ).

      In my development team, this is quite popular whilst I do not like it. Often when I'm working on a laptop and the window is narrow, parts of the code go beyond the right window border.

      Or think having to rename something

      "If the line is short
      data(foo) = bar->calculate_something_other( i_param   = 1 
                                            i_another = 2
                                            i_more    = 3 ).

      Or even when an expression comes in charge

      "If the line is short
      data(foo) = bar->calculate_something( i_param   = 1 
                                            i_another = get_another_param( i_par1 = 'A' 
                                                                           i_par2 = 'B' )
                                            i_more    = 3 ).

       

      Do you get the point? I do remain on one line if there's only one element inside the brackets and the line is not too long

      me->opinion = do_not_split( value #( when_short ) ).
      
      but_also = do_split( 
        cond #(
          when things_get_complex = abap_true
          then do_it( ) ) ).

       

      Author's profile photo Mike Pokraka
      Mike Pokraka

      I did say I do it variably. Yes, changeability is a small sacrifice and I am careful about nesting hanging indents. Generally I keep to a maximum of 80-100 character lines, only going longer where it's relevant.

      To use your example, find this is slightly less readable:

      data(foo) = bar->calculate_something( 
        i_param   = 1 
        i_another = get_another_param( 
          i_par1 = 'A' 
          i_par2 = 'B' )
        i_more    = 3 ).

      So personally I’d write it in mixed mode:

      data(foo) = bar->calculate_something( 
          i_param   = 1 
          i_another = get_another_param( i_par1 = 'A' 
                                         i_par2 = 'B' )
          i_more    = 3 ).

       

      But all of this is hardly anything new, consider the following fine example of classic ABAP from function module SWW_WI_START:

            PERFORM get_creation_parameters TABLES agents
                                                   deadline_agents
                                                   desired_end_agents
                                                   latest_start_agents
                                                   excluded_agents
                                                   notification_agents
                                                   wi_container
                                                   comp_events
                                             USING callback_fb
                                                   l_checked_wi
                                                   confirm
                                                   creator
                                                   desired_end_action
                                                   desired_end_date
                                                   desired_end_time
                                                   desired_start_date
                                                   desired_start_time
                                                   language
                                                   latest_end_action
                                                   latest_end_date
                                                   l_latest_end_escalation
                                                   latest_end_time
                                                   latest_start_action
                                                   latest_start_date
                                                   latest_start_time
                                                   l_max_event_count
                                                   l_max_retry_count
                                                   priority
                                                   task
                                                   l_workitem_type
                                                   l_do_commit
                                                   l_do_sync_callback
                                                   text
                                                   l_do_sync_wi_chain
                                                   created_by_user
                                                   l_created_by_address
                                                   called_in_background
                                                   l_logical_system
                                                   l_step_modeled_wi_display
                                                   no_deadline_parameters
                                                   restricted_log
                                                   l_seconds_until_timeout
                                                   create_event
                                                   status_event
                                                   xml_protocol
                                                   wlc_flags
                                                   l_properties
                                                   l_status
                                                   l_use_wiid_as_objid
                                                   l_node_type
                                                   l_wi_rejectable
                                          CHANGING wi_container_handle
                                                   ls_wi_create
                                                   l_crt_ctx
                                                   l_excp.
      

      We have the same issues of lining up after renaming.

      And then there are people who write:

        DATA:   foo        TYPE c
              , blah_blah  TYPE i
              , bar        TYPE blah
              , more_stuff TYPE string.

      This one goes against my sense of aesthetics.

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      Well, I guess legibilty is a matter of opinion. I would prefer

      data(foo) = bar->calculate_something( 
        i_param   = 1 
        i_another = get_another_param( 
          i_par1 = 'A' 
          i_par2 = 'B' )
        i_more    = 3 ).

      in your example. Again – a matter of personal preferences.

      By the way, there are two situations when I use those “tabular” indentations:

      data: a_variable type string,
            another_variable type string.
      
      * and
      
          methods:
            get_linked_cs_orders
              importing i_doc_number            type vbeln
                        i_document_types        type ty_doc_types
              returning value(r_service_orders) type ty_service_orders
              raising   zcx_gens,
      

      Why? Because the pretty printer has this pattern of formatting, so I adapted it. Otherwise I would have to adjust the coding after each pretty print…

      Author's profile photo Mike Pokraka
      Mike Pokraka

      I think it has practical sides to it too. If I’m skimming through at a page per second that just looks like a badly indented was-changed-but-not-pretty-printed method call with 5 parameters at first glance. It takes a second slower look to spot the parentheses. That’s why I prefer bigger offsets. But all variants are easily understood so it is still largely a matter of preference.

      Actually you’ll find that eclipse will indent depending on how you arrange it. If you put the METHODS…. IMPORTING on the same line, it’ll do this:

      methods get_linked_cs_orders IMPORTING i_doc_number            type vbeln
                                             i_document_types        type ty_doc_types
                                   RETURNING value(r_service_orders) type ty_service_orders
                                   RAISING   zcx_gens,

      And a pet peeve is that SE80 will then go and reshuffle whatever Eclipse does and also prefix everything with !

      Author's profile photo Jörg Krause
      Jörg Krause
      Blog Post Author

      Oh my, those exclamation marks - how I hate the formatting that se24 does in form based mode.

      However - this is not the pretty printer but the code generation of se24 form based mode. Since I am using (almost) always the source based class builder, my public classes do not have this ugly look - and I am using pretty prnter in there.

      Of course I must be careful because sometimes the form based builder is is being called whithout my intention ("Edit" button after syntax check for example)

      Author's profile photo Mike Pokraka
      Mike Pokraka

      Not only that, but SE80/SE24 also re-orders methods. Annoying.

      Author's profile photo Former Member
      Former Member

      Thanks for the wonderful blog,  but the horrible thing is even in S/4 with ABAP 7.5 I see many developers writing forms and crap code and the QA passing it. What can SAP do?

      Nothing or may be now just kill the backward compatibility in any custom code. Can it work I don't know but some people and some teams never want to learn 🙂