Quantcast
Channel: ABAP Development
Viewing all 948 articles
Browse latest View live

Fanning the Flames: Prefixing Variable/Attribute Names

$
0
0

Trigger Warning: This blog will probably annoy a number of people. It's been lurking in the back of my mind for some time now, originally inspired by Ralf Wenzel's article Hungarian beginner's course - a polemic scripture against Hungarian Notation. Among others, I wouldn't be too surprised to receive critical comments from Matthew Billingham in particular - but then again, maybe not. As Tolkien has put it a long time ago, "So many strange things have chanced that to learn the praise of a fair lady under the loving strokes of a Dwarf's axe will seem no great wonder." I figured I might as well give it a try, even though it might spoily my birthday festivities.

 

To cut to the point: I use prefixes for variables and parameters myself, and I encourage people to use a consistent prefix system throughout their code. In my experience, a sensible naming scheme for variables does help developing and maintaining ABAP code. I don't for a moment believe that I will convince anyone who doesn't want to accept that, but I do believe that in this discussion, there are a number of arguments that are too easily forgotten or cast aside. I'm well aware of the fact that this subject is guaranteed to trigger a lively - hum - discussion, so I will try to keep the tone a bit more formal and a little less emotional than Ralf did in his post. (To be fair - his trigger warning is in the title as well as the introduction.)

 

Perhaps, first and foremost - what is the prefix system I use? It boils down to a combination of one or two letters, an underscore and a sensible name for the variable. I'll focus on the assembly of the prefix here, since that's what the 'hungarian notation' discussion is about - whether you want to name your variables according to the dictionary structures or rather have some 'english' name is another discussion.

 

The first letter designates the origin or scope of the variable of parameter:

 

  • class members:
    • g = instance attribute
    • s = static attribute
  • methods and (where applicable) function modules:
    • l = local variable
    • i = importing parameter
    • e = exporting parameter
    • c = changing parameter
    • r = returning parameter
  • programs, function groups
    • g = global variable
    • p = PARAMETERS (report only)

 

The second letter distinguishes between the data types:

  • (none) - elementary data type
  • s = structure
  • t = internal table
  • r = reference

 

For WebDynpro coding, I use a slightly different notation - basically because I'm too lazy to to through the generated code and adapt it all:

  • v = elementary data type (WebDynpro coding since that's the default in the generated code)
  • o = object reference (WebDynpro)
  • r = data reference

 

Exceptions and inconsistencies I haven't overcome yet:

  • so_ = SELECT-OPTIONS
  • co_ = constants
  • no prefix for public read-only attributes of persistence classes
  • no prefix for constants in "enum interfaces" that only contain constants
  • TABLES - hello, dynpros! - for structures EXCLUSIVELY, NEVER for transparent tables, same name as the structure

 

So we get, for example

  • gt_foo in a class - that would be an instance attribute that is an internal table
  • ls_bar - a structured local variable
  • ir_baz - an importing reference parameter

For FIELD-SYMBOLS, I tend to use the same notation, so you'll see stuff like <ls_foo> or <lr_bar> in my code. Since I tend to avoid global FIELD-SYMBOLS, I could omit the 'l', but decided to keep it for simplicity of the ruleset.

 

To sum it up: nothing too special here. I don't claim to have invented this prefix system, only adopted and adapted it slightly - although it's so generic that probably hundreds of developers around the world use either this system or something very similar. It's slightly more verbose than the scheme presented by Kai Meder in ABAP Modern Code Conventions, but I believe that the differences do justify an extra character.

 

Before discussion the advantages and disadvantages in detail, I would like to point out a simple and important fact that appears to be overlooked in many discussions: There is no perfect life. There is just life. We have to make do with what we have, and while we can (and should!) always aspire to refine our skills and improve ourselves as well as our environment, there are shortcomings, deficiencies and historically-explainable-but-nonetheless-ugly-kludges that we just have to deal with. Dreaming of a world without global variables, methods no longer than (insert number here) lines of code and pure object orientation may be an enjoyable pastime, but it doesn't get the job done. The world is out there, and it contains loooooong methods (many of which started out fairly short and grew through years of maintenance by many hands and a lack of refactoring), insanely complex systems and a weird mix of object-oriented and procedural programming, so we have to deal with stuff like that on a daily basis.

 

We also have to deal with another important fact: Humans make errors. Even the most intelligent and experienced developer will eventually produce a bug (other blog authors and moderators excepted, of course). Since a naming convention is of no use whatsoever to the maching, it has to be optimized to support the human in front of the machine. Ideally, it should aid in understanding the code that is already present and prevent coding errors wherever possible - or at least make them more obvious. So let's take a look at some common mistakes and how the naming conventions above aid in preventing or at least spotting these.

 

Let's start with a common controversy - why use prefixes for local and global variables at all? There are really two arguments that make me recommend using the prefixes 'g' and 'l' respectively, despite many notable voices arguing against their use. First off - shadowingand name masking. These concepts exist in many programming languages, and I have yet to find a real-world application that does not involve a decent amount of sadism towards the poor souls who have to maintain the code afterwards. From my experience, wherever you see variable shadowing actually happening outside of a programming class, something has usually gone south when refactoring or maintaining the code. Variable shadowing produces particularly nasty bugs that can easily take hours or even days to hunt down. Spliting the namespace into distinct partitions for global and local variables neatly solves that problem - if all class attributes start with 'g' (or 's') and all method-internal variables start with 'l', shadowing won't ever be a problem. Other languages cope by adding compiler warnings and smart IDE extensions to help the developer spot variables shadowing each other, which is just another workaround for the same problem, and one we don't have yet (the last time I checked, even the splendid ATC didn't warn about shadowed variables).

 

The second argument for "scope prefixes" I'd like to present is related to the counter-argument "variables are always local, and who get's to say what's called global in an object-orientated aplication anyway?" It certainly isn't easy to answer the latter question, but it helps to approach the issue from a different angle: When I change the contents of that foo thingy over there - then what is the scope (or lifetime, if you prefer) of that change?Am I only affecting the execution context of the method I'm currently in, and whatever I change gets discarded when the method is processed anyway - or am I permanently changing the state of the current instance, or even the entire class? You may want to call these changes "local" and "global", for want of a better name. Of course, this can be easily determined with a simple double-click, but it's much easier to write and maintain code if you've got that information embedded in the identifier. If you use this consistently, especially as an entry-level programmer, you will find that after a time, using something prefixed with 'g' will start to feel slightly awkward - should this really be global, do I need this anywhere else, can't I just make this a local variable? - whereas accessing something prefixed with 's' outside of some specially designated factory methods will set of alarm bells at once just by reading it. I've seen many bugs that ultimately could be traced back to an accidental modification of an attribute value (often after an improperly executed refactoring operation), and again, these bugs are really hard to find because they usually only surface much later in the application, and even then only under particular circumstances.

 

Now for the data type designator - what's the point in distinguishing between elementary fields, structures, tables and references? Convenience, mostly, but there are a few cases where these prefixes really can help you spot problems. ABAP has a history of peculiar conversion rules, and the one I'm after here is the conversion between flat structures and single fields. Let's assume you're coding a data object for some translateable entity (something with a separate language-dependent table that contains the translateable parts of the object), and you're keeping the text in an atribute named description. Some day you note that you've forgotten to add an accessor method for that, so you write a getter that returns a STRING, something as simple as r_result = description. Clean and obvious - until you notice that your application displays '100DEFoobar' instead of 'Foobar'. Whoops. Looks like description wasn't only the text, but you decided to store the entire text table entry instead (which makes perfect sense, especially if you've got multiple translateable fields). If you had named that attribute gs_description, you would have noted that immediately and prevented that mistake. Now this is an easy example, but I've seen this error happen to experienced developers in large applications, and again, it takes time and patience to hunt down that unwanted client number in the outbound message stream.

 

With internal tables, this kind of conversion error will not occur, but there's another quirk in the ABAP syntax that virtually every newbie has to master. Consider the following two examples:

 

DATA: sflight TYPE TABLE OF sflight.

 

" somehow, magically, fill that table with some values

 

LOOP AT sflight ASSIGNING FIELD-SYMBOL(<sflight>).

  " some condition, don't care, whatever...

  DELETE TABLE sflight FROM <sflight>.

ENDLOOP.

 

as opposed to

 

DATA: sflight TYPE TABLE OF sflight.

 

" somehow, magically, fill that table with some values

 

LOOP AT sflight ASSIGNING FIELD-SYMBOL(<sflight>).

  " some condition, don't care, whatever...

  DELETE sflight FROM <sflight>.

ENDLOOP.

 

Now granted, that's taking it to the limits, but the experienced ones among you will know how inventive the inexperienced among you can get when introduced to ABAP. I might have turned that into another trapdoor article, but just to reiterate: the first example will modify the internal table while the second example will actually kill selected contents of the database table. The issue here isn't only that this will be dangerous for the application data - bugs like these are usually so catastrophic in their results that they tend to be found early on - but that it is much harder to determine the intent of the developer and pinpoint the bug. Now let's compare this to a prefixed version:

 

DATA: lt_sflight TYPE TABLE OF sflight.

 

" somehow, magically, fill that table with some values

 

LOOP AT lt_sflight ASSIGNING FIELD-SYMBOL(<ls_sflight>).


  DELETE TABLE lt_sflight FROM <ls_sflight>. " OK - we're modifying the local table

                                             " contents here


  DELETE TABLE sflight FROM <ls_sflight>.    " syntax error: The field "SFLIGHT" is

                                             " unknown, but there is a field with

                                             " the similar name "LT_SFLIGHT".


  DELETE lt_sflight FROM <ls_sflight>.       " ATC complains about "Non-numeric index

                                             " access on internal table"

                                             " and suggests correct variant above.


  DELETE sflight FROM <ls_sflight>.          " OK - changing the database contents


ENDLOOP.

 

Also, after a while, typing LOOP AT lt_ followed by Ctrl+Space becomes a habit, and the code completion will present only the local tables - without robbing you of the flexibility to simply type LOOP AT<Ctrl+Space> and still have access to the full list of options.

 

All things considered, prefixes for variable names aren't as bad as some articles make them. Prefixes are a workaround, a compromise between what we have, what we can get, what we need and most importantly what we don't want. If you've ever ran into any of the bugs I mentioned above, either in your own code or someone else's, you know what I'm talking about. If you haven't (yet?), still please consider the junior members of your team and the people who succeed you and have to maintain your code. Someone may be very grateful for those few extra characters that really take no time either typing or reading.


ABAP OO - Functional Method usage

$
0
0

ABAP OO Functional Method


We can use functional method like variables, and functional method can save us from declaring new data. Here I try to write very simple code for usage.


Definition of functional method is like any other method, but you can't use exporting or changing parameters and returning parameter must be fully specified.

Functional_method1.JPG

 

From now on I will continue with local class.

 

1) we can use with WRITE, IF, DO and LOOP statement.

Definition part :

Functional_method2.JPG

Implemantation part :

Functional_method3.JPG

Report code :

Functional_method4.JPG

2) we can use with new statement like REDUCE, FOR, | { } |

Functional_method5.JPG

3) singleton class and get_instance

Functional_method6.JPG

Functional_method7.JPG

Functional_method8.JPG

4) I wish, I like

We can declare table with TYPE TABLE OF REF TO <class> But we can't use functional method for LOOP AT...WHERE condition or READ TABLE...WITH KEY. I wish at new release of ABAP we can write code *** table field;

Functional_method11.JPG

Functional_method10.JPG

If m_count declared as public, we can write read or loop

Functional_method12.JPG

Functional_method13.JPG

 

Regards

ABAP Test Double Framework versus mockA

$
0
0

I recently had the chance to have a look into SAP’s ABAP Test Double Framework. Previously, the main tool for mock object creation was mockA, which did a great job in my previous projects.

 

Code readability

The Test Double Framework’s fake object creation is quite straight forward. Creating such an instance is handy and even allows you to use the native method call to specify the importing-parameters. Please see the blog post from SAP:


"create test double object
    lo_currency_converter_double ?= cl_abap_testdouble=>create( 'if_td_currency_converter' ).

 

  "configuration for stubbing method 'convert':

    "step 1: set the desired returning value for the method call
cl_abap_testdouble=>configure_call( lo_currency_converter_double )->returning( 80 ).

    "step 2: specifying which method should get stubbed
lo_currency_converter_double->convert(
EXPORTING
amount          = 100
source_currency = 'USD'
target_currency = 'EUR'
        ).

 

The same test double configuration in mockA:

 

"create test double object
lo_currency_converter_double ?= zcl_mocka_mocker=>zif_mocka_mocker~mock( 'if_td_currency_converter' )->method( ‘convert’ )->with( i_p1 = 100 i_p2 = ‘USD’ i_p3 = ‘EUR’ )->returns( 80 )->generate_mockup( ).

 

As you can see, mockA doesn’t allow to verify whether you correctly supplied the method’s importing-parameters and method name at design time. This restriction applies for the Test Double Framework only for exporting and changing-parameters.

The only two disadvantages that I’ve recognized are

  • checked exceptions need to be catched in a dummy TRY…CATCH…RETURN…ENDTRY-statement to avoid ATC messages
  • the exporting- and returning parameters specification comes before the actual method call specification and configuration of the importing parameter. You need to get used to it, but it’s fine

Let the framework ignore importing parameters

The Test Double Framework allows you to separately ignore importing parameters by attaching “->ignore_parameter(…)” to your “configure_call(…)” method call. This feature is not yet existent in mockA. However, if you avoid the “->with(…)” method call in mockA completely, it will return the specified output in any case . This is equivalent to “->ignore_all_parameters(…)”.

Verify interactions

Quite handy in the Test Double Framework. Just call


cl_abap_testdouble=>verify_expectations( lo_currency_converter_double ).

 

..and let the framework assert that your specified methods have been called as intended.

 

mockA requires you to store the mock creation object of type ZIF_MOCKA_MOCKER.

It allows you to verify method calls like this:

 

cl_abap_unit_assert=>assert_true( mo_mocker->method( 'convert' )->has_been_called_with( i_p1 = 100 i_p2 = 'USD' i_p3 = 'EUR' ) ).

Custom matchers

The Test Double Framework allows you to pass custom matchers which will implement interaction verifications on the fake method input parameters. The interface is called if_abap_testdouble_matcher and it is very easy to implement. mockA does not offer such a concept.

System requirements

mockA requires NW 7.01. The Test Double Framework requires NW 7.40.

Object support

mockA supports classes and interfaces, while the Test Double Framework supports interfaces only at the moment.

Conclusion

The Test Double Framework is a really nice mocking framework. It works very well and offers functionality, which mockA doesn’t offer. However, some drawbacks still exist, e.g. when you want to mock classes or work with NW releases lower than 7.40

International Editable SALV Day 2016

$
0
0

International Editable SALV Day 2016 – Year Eight


image001.png

Dear CL_SALV_TABLE Fans,

 

Welcome to February 8th, 2016 which is the eighth International Editable SALV Day. See below for a link to a blog I wrote to celebrate this day exactly one year ago:-

 

http://scn.sap.com/community/abap/blog/2015/02/08/international-editable-salv-day-2015

 

As you may know back in the year 2000 we were all encourage to “enjoy” SAP and the good old CL_GUI_ALV_GRID came along to replace function module REUSE_ALV_DISPLAY_GRID.

 

This was nice and object orientated, which we all love. ABAP People love OO programming so much that sixteen years on, some have even started to use it!

 

Anyway, what we liked about the CL_GUI_ALV_GRID was the fact that you could make some of the columns, or even individual cells, editable. This is what the end users were crying out for. SAP itself made a big song and dance about having a convergence between analytical applications and transactional applications.

 

That is, a business user did not just stare at the data and admire it, but could actually take action on the data they were looking at e.g. remove a billing block, or adjust the price.

 

Thus began a golden age. All the peoples of the world forgot their differences and at long last there was world peace, an end to sickness and suffering, and an increase in the average human lifespan to 206 years. As Rufus said “bowling averages were way up, mini-golf scores were way down”.

 

Then, a great disaster befell the universe. With the advent of 7.20 a new monster was created by SAP, named CL_SALV_TABLE and we were all told to bow down and worship this new beast.

 

It fooled us all with its sweet promises of dynamically generating the field catalogue based on an internal table, and we fell for it, writing many reports using this new class, and converting many an old report to use this bright new technology.

 

We laughed and sang and thought how wonderful we were. But as always, pride comes before a fall.

 

One day the business users came to us and said “what we most desire in the world is to have this or that column editable, and maybe a few cells here and there, based on certain logic”. They then stood there looking at us expectantly; after all we had always been able to do it before. Alas, lack – now we could not! We had been cast out of Heaven!

 


image002.png

How could SAP play such a cruel trick on us? Pretending a new technology was superior in every way to the last, and not mentioning a whacking great hole in the middle. This was all the more annoying since CL_SALV_TABLE is nothing more than a wrapper around CL_GUI_ALV_GRID, adding some features, and clearly subtracting others.

 

For many years the sky turned black, and a plague of frogs rained down upon the SAP developer community. Then several programmers took a leaf out of Twisted Sister’s book and declared “we’re not going to take this, we’re not going to take this, and we’re not going to take this, any more”.

 

Here is the article that started the rebellion, written by Naimesh Patel

 

http://zevolving.com/2008/12/salv-table-10-editable-salv-model-overcome-the-restriction-of-salv-model/

 

Since that point many people have joined the crusade, including my good self. There have been many approaches created as to how to get around this, get the benefits of the SALV and also make it editable. Here is the latest blog I wrote on the subject, building on the work of the others who went before me:-

 

http://scn.sap.com/community/abap/blog/2015/08/07/salv-and-pepper-editing-individual-columns-in-the-salv

 

Of course the best way around this problem would be for the powers that be at SAP to recognise that every single one of their customers desires this functionality. I wonder how far SAP would have got if they had said “we have embraced the internet age, with a wonderful suite of new UI5 apps. All of them will be read-only”.

 

This day marks the 8th anniversary of James Hawthorne going cap in hand to SAP and suggesting maybe the SALV could be brought up to parity with the CL_GUI_ALV_GRID and have an option to be editable.

 

http://scn.sap.com/thread/733872

 

SAP did in fact respond to this request. They said it was impossible, and just laughed and laughed and laughed. They are still laughing to this day, fit to burst. No-one in all eternity has ever laughed so loud and so long, with the exception of Jock McLaughing, the Laughing Scotsman, winner of the All Scotland Laughing Competition 2015.

 

We have to face the cold hard truth. It is never going to change. We have proved it is technically possible, not even that difficult, but all the powers that be do is look at our workarounds and then try to close them down.

 

Currently, the way around this problem is to:-

(a)  Use CL_GUI_ALV_GRID

(b)  Use one of the many workarounds you can find on the SCN as to how to make the CL_SALV_TABLE editable, though this is of course naughty

(c)  Take a hybrid approach. Use the CL_SALV_TABLE to generate the field catalogue dynamically, and then pass that field catalogue into CL_GUI_ALV_GRID. There have been some articles on the SCN about that also.

 

Here is an example of such an approach by Łukasz Pęgiel :-

http://scn.sap.com/community/abap/blog/2016/01/10/falv--fast-alv-grid-object

 


image003.jpg

In conclusion, next year I will be publishing a blog celebrating the 9th annual International Editable SALV Day. See you then!

 

Cheersy Cheers

 

Paul

 

 

A kill to an ABAP CDS View?

$
0
0

In the first blog of this series we considered that CDS views are not just for the super cool HANA types, but can also be used to escape when marooned on planets with nothing but a lowly Oracle DB. Many of SAP's customers I suspect have only just begun to dip their toes into these scary HANA waters, and so its important to understand what of this new technology we can learn and use today (whilst still on the traditional non-HANA DB); and what must wait for a possible future HANA implementation.

 

In today's blog we will consider the performance of the ABAP CDS view with an Oracle DB, if we are without the HANA DB in the back end; all from the perspective of a certain double-o secret agent. In Teched I posed the question to an SAP expert, (Who shall remain nameless, less Spectre (of course we all know that SAP==Spectre ABAP Programmers) learn of his details and track him down), here I was lead to believe that even on Oracle the use of the ABAP CDS views would give major performance benefits over the equivalent SQL statement. I have to say I had my suspicions that this may have been a double agent trying to lure me, and so without further ado on returning from TECHED set out to test the ABAP CDS view tech with the help of my very own 00-agent and the necessary 00 CDS-View.

 

So first to construct my CDS-view I made a join, both literally (between 3 Oracle DB tables) and metaphorically between 2 infamous Spectre  CDs that I happen to have in my secret agent archives, creating a very real CDs view.

 

cds_views_blog_4SDN.jpg

 

The literal CDS-View is made by creating the DDL source, here I reused the same DDL source as from my the previous blog.

 

<DDL source for CDS view>

@AbapCatalog.sqlViewName:'ZLOMARCDS001'

@ClientDependent:true

@AbapCatalog.compiler.CompareFilter:true

@AccessControl.authorizationCheck:#CHECK

@EndUserText.label:'Material master CDS View for secret agents...'

defineview ZLO_MAR_CDSVW

   (matnr, maktx, mtart, matkl, size1, werks )

    withparametersp_mandt:mandt,

                    p_site :WERKS_D,

                    p_size :WRF_SIZE1,

                    p_lang :spras

 

asselectfrom mara

       join marc on mara.matnr = marc.matnr

       join makt on mara.matnr = makt.matnr {

       key mara.matnr,

       makt.maktx,

mara.mtart,

mara.matkl,

mara.size1,

       marc.werks

 

}

where makt.mandt =:p_mandt

  and makt.spras =:p_lang

  and mara.mandt =:p_mandt

  and mara.size1 =:p_size

  and marc.mandt =:p_mandt

  and marc.werks =:p_site

;

 

 

I also have spent some time deciphering the DDL source, (with the help of Q-division) to turn it into the equivalent standard Open SQL statement.

 

<SQL for the equivalent >

  SELECT mara~matnrmaktxmtartmatklsize1werks

   FROM mara

   INNER JOIN marc ON mara~matnr = marc~matnr

      INNER JOIN makt ON mara~matnr = makt~matnr

      INTO TABLE et_spacesuits

      WHERE mara~size1 = iv_size

        AND marc~werks = iv_site

        AND makt~spras = iv_lang.

 

 

Now what we are interested in is to establish, will the CDS-view help our secret agent perform better than the more traditional viewless open SQL statement???

 

To put this to the test we need to perform some traces, and establish if we have the ultimate performing secret agent or not. So we navigate our way to ST12; the top-secret performance analysis tool for single transaction analysis. In ST12 we will perform some traces in the current mode for both the CDS-View and the equivalent SQL code, and then we will review the performance of the Oracle database with each solution. Now I know before Spectre send their agents around to do me in, that DB performance tracing is a bit of a minefield, the first time you run a SQL statement, the DB will likely cache the results and then the second time you run you are really just reading from the cache, and not from the DB. However our system caches are cleared once per day, and so the first run of the day with each solution should give a rough indication of the typical performance of each solution. This testing has now been performed several times within our SAP ECC Oracle DB solution.

 

Then to test our metaphorical solution I kitted out my own secret agent with both the CDS-View technology (these I believe are also known as SPECTRECLES), and the old fashioned non-spectrecles solution. Our agents job was to asses which tech made him feel most like a secret agent, and enable him to perform and to shoot straight. We will see that both the metaphorical testing and the literal testing reach the same conclusion.

 

So first to the metaphorical testing, and we kit out our double-O secret agent with his very own CDs views:

cds_views_blog5_4SDN.jpg

 

We then perform the literal system test using this technology in our backend system:

 

CDSView_result.jpg

 

Then we resort back to our traditional non-CDS view (traditional SQL), and test again:

 

The metaphorical performance is definitely increased and looks way less dorky!

cds_views_blog_007_4SDN.jpg

This is also reflected in the DB results - 17K ms as opposed to 27Kms with the CDS-views.

SQL_result.jpg

 

Well as you can see the SQL statement result (and trust me that I performed this test many times) on average performs a bit faster than the CDS views. Our secret agent agrees, that while the SPECTRECLES look cool, they aren't much use for quick fire shooting (dad they are rubbish!) I think was the comment from our agent in the field.

 

Then a member of Spectre ABAP programmers (one that likes to write really THICK books) left an intriguing comment on my last blog, which confirmed my suspicions, the ABAP CDS-views are actually implemented in native SQL, and so at their very best will perform similar to the corresponding open SQL statement. Its just a question of what can be turned to native SQL faster the Open SQL or the CDS-view. This can be seen in the results, I suspect there is a little overhead prior to the DB layer to turn the CDS view into its equivalent SQL, and hence the slight drop in performance that we see for the CDS view. This should mean that for more heavy duty selections, the ABAP CDS-view performance is more equivalent to the Open SQL.

 

Unfortunately another disadvantage I discovered with running the CDS-Views on Oracle in our systems is that there is no shared cursor cache information recorded for the CDS views that is viewable in the DBACOCKPIT transaction , so we are unable to really see the nitty gritty of how these statements are implemented. It would be nice to know if the failure to update the cursor cache is a particular issue with the CDS views, or maybe its some system setting we are missing.

 

So is this a kill to a CDS-View?

Well not really, it just means that this technology is close to equivalent in performance to the corresponding open SQL, and will likely become more equivalent as the work involved in the SQL statement overtakes that of translating the CDS view to the native SQL. Should we then not bother with the CDS-View prior to implementing HANA? or are there real reasons why they may still make sense? This is the question we will try to address in my final blog of the series The ABAP CDS-View Strikes Back!

Does OOP require FAE?

$
0
0

Before Matthew Billingham will tear out my poor head and impale it on the walls of Join Castle as warning for the future generations of fools, please try to follow the White Rabbit down the hole as I try to explain why I think so.

First of all, which is the main feature of OOP? Well, obviously is to identify and manage all the components of a report/procedure/flow as stand alone objects which can inteact each others via some public methods and can be identified by public attributes (I put it down pretty simple and somehow semplicistic, I know).

So, take as an example an easy and quick report required last week to be develop: extract all the materials open in a set of storage locations and retrieve the last material movement for that material-storage location (inbound or outbound do not count).
The user then can select one or more records and create a corrective movement for specific situation.

It's nothing too complicated and we can start to check out which are the objects we need.

The first one is obviously my APPLICATION with the report bones and structure then I can see the MATERIAL with all the logic to retrieve materials' data. As third step, we have MATERIAL DOCUMENT to get the last material document and to manage the new one. And finally our ALV to show data and manage user interaction.

Easy task, quick report but 4 objects.

I do not want to bother with a detailed explain about the report but I want to focus on the interaction bewteen MATERIAL and MATERIAL DOCUMENT (from now only MDOC).


Now that I got my objects to manage, I sink a bit deeper in them and I find out that MATERIAL is used just to retrieve data and do some check (like authorization ones on plant).

So my methods will be

  •   GET_DATA (public, export a table with all the materials-description-plants-s.locations-qty)
  •   GET_MATERIALS (private, fill main table from marc-mard)
  •   FILTER_AUTH (private, filter retrieved data with auth check on plant)
  •   SET_DESCRIPTION(private, retrieve material description)

As you can see, I kept SET_DESCRIPTION separated because if GET_MATERIALS can be easly implemented with a join, my descriptions are language dependant and I had to do a double read based first on SY-LANGU and then with english (company default).

At this point I'm forced to use FOR ALL ENTRIES based of the result of the join between MARC-MARD.


But even if I manage to extract a bunch of useless records putting in join MAKT wihout the language filter and then skim out the result from my internal table, I just got half of the informations I need.

And here the MDOC enters in play.

MDOC will have the following methods:

  •   GET_LAST_MDOC
  •   <methods for the building and calling for BAPI_GOODSMVT_CREATE>

I want to focus on GET_LAST_MDOC.


So, i got my array of materials and for each one I have toretrieve last material document and, being them 2 different objects, I cannot create any join.

So i got 2 options:

LOOP my materials, for each one retrieve the data with a bunch of selects.

Or i can use FAE, retrieve all data at start and then work with internal tables

Surely, it's a report, i can throw away the OOPs and put all in a join together but then? OOPs helps alot in organizing the code, identify the issues/objects to be threaten and I like the idea I'm building a forma mentis.


I know,  FAE are deprecated and should be replaced by JOINS but… YOU CANNOT DO IT if you are going to use OOP!

How can you improve performances and void the bad guy (FAE) when you work with objects?

The power of objects is that you virtually can develop them stand alone and only later you can mix and merge them in your process/development.

And in my opinion FAE becomes quickly the only option you have to avoid the even more deprecated SELECT in LOOP


Now, Matthew, you can have my head

 

A little note-add:

Pointing out my failures and faults is really appreciated: I believe in constant learning so if I can improve exploiting the SCN work and experience and knowledge, why not?

Performance when working with internal table with results in a hashed table: "insert into hashed table inside loop" or "sort internal table and delete adjusted duplicated"?

$
0
0

Here is my thoughts about performance in the next task:

select many records from database and put results by some keys into an internal hashed table.

We can insert records into hashed table inside loop or we can sort internal table and delete duplicated records.

What is quicker?

Here is a small program to test:

 

REPORT  ztest_performance.

 

TYPES: BEGIN OF ekpo_typ,

       ebeln TYPE ebeln,

       ebelp TYPE ebelp,

       matnr TYPE matnr,

       bukrs TYPE bukrs,

       werks TYPE EWERK,

   END OF ekpo_typ.

 

DATA: lt_ekpo TYPE TABLE OF ekpo_typ.

DATA: lt_ekpo_h TYPE HASHED TABLE OF ekpo_typ WITH UNIQUE KEY bukrs.

DATA: lt_ekpo_2 TYPE HASHED TABLE OF  ekpo_typ WITH UNIQUE KEY bukrs.

 

DATA: i1 TYPE i,

       i2 TYPE i,

       i3 TYPE i,

       i4 TYPE i,

       lv_lines1 TYPE i,

       lv_lines2 TYPE i,

       diff21 TYPE i,

       diff32 TYPE i.

 

FIELD-SYMBOLS: <fs_ekpo> LIKE LINE OF lt_ekpo.

 

   SELECT ebeln

          ebelp

          matnr

          bukrs

          werks

      FROM ekpo

     INTO CORRESPONDING FIELDS OF TABLE lt_ekpo

     UP TO 1000000 ROWS.

 

   GET RUN TIME FIELD i1.

 

   LOOP AT lt_ekpo ASSIGNING <fs_ekpo>.

     INSERT <fs_ekpo> INTO TABLE lt_ekpo_h.

   ENDLOOP.

 

   GET RUN TIME FIELD i2.

 

   lv_lines1 = LINES( lt_ekpo_h ).

   REFRESH lt_ekpo_h[].

 

   GET RUN TIME FIELD i3.

 

   SORT lt_ekpo BY bukrs.

   DELETE ADJACENT DUPLICATES FROM lt_ekpo COMPARING bukrs.

 

   lt_ekpo_2[] = lt_ekpo.

 

   GET RUN TIME FIELD i4.

 

   lv_lines2 = LINES( lt_ekpo_2 ).

   refresh lt_ekpo_2[].

 

   diff21 = i2 - i1.

   diff32 = i4 - i3.

 

   WRITE: 'i2-i1 = ', diff21, /.

   WRITE: 'i4-i2 = ', diff32, /.

   WRITE: 'lines1 = ', lv_lines1.

   WRITE: 'lines2 = ', lv_lines2.


In my test system the result is:


i2-i1 =     814.957

i4-i2 =     480.459

lines =          29

 

 

So, "delete duplicated records" seems to work quiker than "insert records into hashed table inside loop".

ABAP - A Bit of Agile Programming - The scope

$
0
0

Summary


Agile project management methods address especially the inflexibility of a fixed scope in long running software projects by making the scope variable. How this is achieved and what it changes for the ABAP developer I want to show in this blog article.

 

Agile programming is of course not the definitive solution for all problems in SAP projects and there are also some pitfalls, especially when extending and adapting a big system like SAP - well a greenfield project is generally easier to plan. But especially the possibility to change the original specification gives the development team the possibility to contribute more than simple coding which finally can lead to better project results.


For ignoring agile approaches they have too much potential


I would never recommend to blind adopting agile programming and agile project management in SAP projects but would also beware of ignoring the agile methology only because the few drawbacks and pitfalls that certainly will coming up. For ignoring it, it has too much potential in improving the whole project life cycle.


As agile methology is such a wide field, I will focus in the following lines only on the role of the scope in agile projects and such sweet things like Change for free, which can contribute a lot to a successful project. One of the most important thing to get a variable scope is a specification that enhances it. I skip this topic for now to get this blog not too long.

   

Agile methology of course will not solve all your problems but as my coach a lot of years back teached me with a sense of humor: The agile development approach is also not perfect, but it is the least evil of all approaches.

 

Introduction

Developer:“The customer wants an information system for his sales representatives? Let’s give him a cool Fiori frontend like in the project X, then the representative can access the information about his customers also on his mobile?
Project manager:“Well, fantastic idea, but that is not part of the negotiated specification. And two years back we didn’t know about this Fiori stuff.”


Developer:“Why we need a Z-table for this feature. I know it’s written in the functional specification, but why we do not make it like we did for the other project by using BADIs”
Project manager:“No, we have to deliver it like agreed at project start with the customer” 


Developer:“If we wouldn’t implement this feature, which in my opinion he will rarely to never need, we would finish one month earlier.
Project manager:“Well, but than we do not fulfill our contract” 


These are some dialogues between developers and project managers that often occur in traditional managed projects. At the beginning of the project there is something agreed which is hard to change later in the project when you have more knowledge and information what the customer really needs.

 

The below probably well known cartoon shows in a funny way how a lot of software projects are emerging over time. Especially long running projects, for example SAP implementation projects, have mostly beside others the following characteristics:

  • There are a lot of people of different areas involved. With a lot of people there is also the risk of misunderstandings
  • The customers has little to nothing knowledge of SAP
  • The supplier has no detail knowledge about the customers processes
  • The customer's requirements changes over time
  • The developer do not know all details about the selected technologies
  • .. and so on.

 

So there is nearly always a big shortage in the knowledge about the ideal project goal and its impossible to get rid of this shortage in the short planning phase.

 

     Business requirement management - the different views

business-requirements-management_by_project_cartoon_com.jpg

    Source: ProjectCartoon.com

 

Well, the cartoon of course pushes too hard on the topic, but it is a fact that in long running projects especially in the beginning the stakeholders view on the desired goal differs sometimes widely.

So how to handle the need of a detailed specification as part of the negotiated contract between customer and supplier and the changing project goal during the project period?


Managment does not want a fix scope, they want fix cost and fix schedule


Traditionally a software project starts with a specification or design phase (something about 20% of the whole project duration). Nothing wrong about this also from the perspective of agile project management. To start a project without a common specified view of the desired goal would be not a good idea.


The output is normally a large specification document and maybe a POC. But unless the project members on customer and supplier side have done a lot of similar projects before they will not be able to define all desired features in the initial specification in a granularity and completeness that the  scope stays carved in stone.

 

 

   The  project management triangle - traditional and agile approach

Traditional_vs_Agile_Approach_with_Locks.png

In the traditional approach of project management there is a fix scope. But Projects do not run like initially planned, so you normally have the possibility to change the Cost domain, for example by additional developers or extra hours or the schedule by moving the project finish date.

The agile approach leaves cost and schedule fixed. That's of course desired by management (in budget and in time is mostly their primary focus). So if two of the three domains stay fixed the scope needs to be variable.

That means for example descoping the non essential features or to focus first only on the critical elements of the agreed use cases. (of course the skipped part have to be delivered afterwards).

 

But also if a project stays in time and in budget the possibility to change the scope has a lot of advantages. As a developer you can decide to bring in new ideas. Customer and supplier can much easier replace a feature in the scope because from the start of the project the fact that scope will change is considered. In agile approaches this is known as "change for free", which means that a change of a feature has not that much of additional cost like in traditional approaches as the functionality is not already fully specified and probably a lot of specification effort for features which are finally not needed is avoided.

 

But what advantages has agile programming for the ABAP developer. If the company takes this approaches serious there has to be some invest in the beginning of the project in tooling which facilitates robust and changeable code. But when done right, this will pay off more and more, the longer the project and the relationship to the customer lasts by avoiding extensive maintenance costs and much more important to be ready to integrate the changing business needs of the customer into the delivered software.

 

Its all about avoiding the creation of ZSmaug.

 

Abap objects, testing tools like Unit tests   helps a lot to make the code robust and even Mocking frameworks are evolving in the ABAP area. There are also a lot of quality tools like the ABAP code inspector. And even continuous integration and continuous delivery frameworks like Jenkins are already touched as far as I know.

 

Finally it's all about to avoid the creation of the dragon ZSmaug (similarity to the dragon in the hobbit is not intended). These custom Z-program with thousand of lines sitting on a deep golden treasure of functionality which nobody wants to touch as the danger to awaken it the dragon is not worth getting changed some of his well preserved treasure of functionality. So normally the dragon is kept asleep until there comes a young brave, fearless developer who wants to adapt some of the functionality and wishes he would never had touched ZSmaug, which then spreads all the hidden smoulder.

 

I know it is not that easy to apply all best practices of agile programming on a SAP projectand in a lot of circumstances it is not possible and sometimes also not constructive to use some agile methology. But I am sure that a bit of agile programming makes also SAP projects more successful.

 

As this is for sure a topic that does not has a unique solution and of course different views I am waiting with eagerness on your comments. I hope you could get out some helpful ideas and that I can get some new insights about the following discussion.

 

Regards, Andreas


screenshot with embedded text data

$
0
0

Preface

 

Not so long ago I discovered the "GET_SCREENSHOT" method of class "CL_GUI_FRONTEND_SERVICES". The method creates an image of your current SAP GUI window in the "Portable Network Graphics" format (shortly called "PNG"). My first thought was: "Great, useful for support teams." Second thought was: "Support teams would need more information such as transaction code or report name." Third thought was:"Would it be really necessary to have these information in an extra (text) file?" I did a little research and found a workable solution in the PNG format itself. This blog is about my implementation to demonstrate the mentioned solution. Please use it as starting point for your own research and share your ideas and thoughts about if you want.    

 

 

Required ressources

 

  • SAP Netweaver stack (I used SAP Netweaver Trial 7.40 on MaxDB)
  • W3C PNG specification (second edition, all mentioned chapter in this blogs belongs to this document)
  • TweakPNG (freeware for examining and modifying PNG image files)
  • ASCII/Hex-Converter (I used an online converter)
  • IrfanView (freeware image viewer)

 

 

Basic idea


The method "GET_SCREENSHOT" will be used to create a PNG image of the current SAP GUI window. PNG images consist of different chunks (cf. chapter 5.3). Each chunk has a chunk type to specify its function (cf. chapter 4.7.2). The chunk type "tEXt" is used to store text strings (cf. chapter 11.3.4.3). Keywords can be used to identify the content of the text string. Here are some useful keywords (cf. 11.3.4.2):


  • Title: short title or caption
  • Author: name of creator
  • Description: more details than in title
  • Creation Time: time of original image creation
  • Comment: miscellaneous comment

 

A Keyword and his associated text string are called chunk data. Now we have all fundamentals for implementing our own solution.

 

 

Source code of demo report

 

Here is my demo report. Pay attention to the comments and subroutine structure to follow the program flow.

 

*&---------------------------------------------------------------------*
*& Report  ZMKE_SCREENSHOT_ENHANCE
*&
*&---------------------------------------------------------------------*
*&
*&
*&---------------------------------------------------------------------*

REPORT zmke_screenshot_enhance.

PARAMETERS: pa_kword TYPE text20 AS LISTBOX VISIBLE LENGTH 20 DEFAULT '1',
            pa_txstr TYPE text20.

INITIALIZATION.
   PERFORM initialization.

AT SELECTION-SCREEN OUTPUT.
   PERFORM at_selection_screen_output.

START-OF-SELECTION.
   PERFORM main.

*&---------------------------------------------------------------------*
*&      Form  MAIN
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*  -->  p1        text
*  <--  p2        text
*----------------------------------------------------------------------*
FORM main.

   DATA: lv_subrc TYPE sysubrc,
         lv_image TYPE xstring.

   IF pa_kword IS INITIAL.
     MESSAGE 'Please choose a keyword.' TYPE 'I'.
     RETURN.
   ENDIF.

   IF pa_txstr IS INITIAL.
     MESSAGE 'Please supply a text string.' TYPE 'I'.
     RETURN.
   ENDIF.

   PERFORM screenshot_create
           CHANGING lv_subrc
                    lv_image.

   IF lv_subrc <> 0.
     RETURN.
   ENDIF.

   PERFORM screenshot_enhance
           CHANGING lv_subrc
                    lv_image.

   IF lv_subrc <> 0.
     RETURN.
   ENDIF.

   PERFORM screenshot_download
           USING    lv_image
           CHANGING lv_subrc.

   IF lv_subrc <> 0.
     RETURN.
   ENDIF.

ENDFORM.                    " MAIN
*&---------------------------------------------------------------------*
*&      Form  SCREENSHOT_CREATE
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      <--P_LV_SUBRC  text
*----------------------------------------------------------------------*
FORM screenshot_create CHANGING cv_subrc TYPE sysubrc
                                cv_image TYPE xstring.

   DATA lv_mtype TYPE string.

   CALL METHOD cl_gui_frontend_services=>get_screenshot
     IMPORTING
       mime_type_str        = lv_mtype
       image                = cv_image
     EXCEPTIONS
       access_denied        = 1
       cntl_error           = 2
       error_no_gui         = 3
       not_supported_by_gui = 4
       OTHERS               = 5.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

ENDFORM.                    " SCREENSHOT_CREATE
*&---------------------------------------------------------------------*
*&      Form  SCREENSHOT_ENHANCE
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->P_LV_IMAGE  text
*      <--P_LV_SUBRC  text
*----------------------------------------------------------------------*
FORM screenshot_enhance CHANGING cv_subrc TYPE sysubrc
                                 cv_image TYPE xstring.

   CONSTANTS: lc_zero  TYPE xstring VALUE '00',
              lc_space TYPE xstring VALUE '20'.

   TYPES: BEGIN OF line,
           text TYPE text132,
          END OF line.

   DATA: lv_chunk  TYPE xstring,
         lv_ileng  TYPE i,
         lv_xleng  TYPE x LENGTH 4,
         lv_icrc   TYPE i,
         lv_xcrc   TYPE x LENGTH 4,
         lt_stext  TYPE TABLE OF line,
         ls_stext  TYPE line,
         lv_search TYPE xstring,
         lv_rest   TYPE REF TO data,
         lv_offset TYPE i,
         lv_kword  TYPE text20.

   FIELD-SYMBOLS <fs_rest> TYPE any.

* choose keyword
   CASE pa_kword.
     WHEN '1'.
       lv_kword = 'Title'.

     WHEN '2'.
       lv_kword = 'Author'.

     WHEN '3'.
       lv_kword = 'Description'.

     WHEN '4'.
       lv_kword = 'Source'.

     WHEN '5'.
       lv_kword = 'Comment'.
   ENDCASE.

* combine chunk type ("tEXt") and keyword
  CONCATENATE 'tEXt' lv_kword INTO ls_stext.

* combine chunk type, keyword and text string
  CONCATENATE ls_stext pa_txstr INTO ls_stext SEPARATED BY space.
  APPEND ls_stext TO lt_stext.

   CALL FUNCTION 'SCMS_TEXT_TO_XSTRING'
*   EXPORTING
*     FIRST_LINE       = 0
*     LAST_LINE        = 0
*     MIMETYPE         = ' '
*     ENCODING         =
     IMPORTING
       buffer   = lv_chunk
     TABLES
       text_tab = lt_stext
     EXCEPTIONS
       failed   = 1
       OTHERS   = 2.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

* "Zero Byte" is delimiter between keyword and text string
* (remember: keyword and text string are our "chunk data")
   REPLACE FIRST OCCURRENCE OF lc_space
           IN lv_chunk
           WITH lc_zero
           IN BYTE MODE.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

* function "SCMS_TEXT_TO_XSTRING" adds a line break at the end
* which has to be removed
   lv_ileng = xstrlen( lv_chunk ) - 2.
   lv_chunk = lv_chunk+0(lv_ileng).

* get length of chunk data (ignore chunk type by -4)
   CLEAR lv_ileng.
   lv_ileng = xstrlen( lv_chunk ) - 4.
   lv_xleng = lv_ileng.

* generate CRC32 for chunk type and chunk data
   CALL METHOD cl_abap_zip=>crc32
     EXPORTING
       content = lv_chunk
     RECEIVING
       crc32   = lv_icrc.

   lv_xcrc = lv_icrc.

* build complete chunk
   CONCATENATE lv_xleng lv_chunk lv_xcrc INTO lv_chunk IN BYTE MODE.

* place the new chunk before "IEND"-chunk
   CLEAR lt_stext.
   ls_stext = 'IEND'.
   APPEND ls_stext TO lt_stext.

   CALL FUNCTION 'SCMS_TEXT_TO_XSTRING'
*   EXPORTING
*     FIRST_LINE       = 0
*     LAST_LINE        = 0
*     MIMETYPE         = ' '
*     ENCODING         =
     IMPORTING
       buffer   = lv_search
     TABLES
       text_tab = lt_stext
     EXCEPTIONS
       failed   = 1
       OTHERS   = 2.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

   lv_search = lv_search+0(4). " avoid line break

   FIND FIRST OCCURRENCE OF lv_search
        IN cv_image
        IN BYTE MODE
        MATCH OFFSET lv_offset
        MATCH LENGTH lv_ileng.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

* respect length indication befor "IEND"-chunk by -4
   lv_offset = lv_offset - 4.
   lv_ileng = xstrlen( cv_image ) - lv_offset.

* create variable for the image's tail
   CREATE DATA lv_rest TYPE x LENGTH lv_ileng.
   ASSIGN lv_rest->* TO <fs_rest>.
   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

   <fs_rest> = cv_image+lv_offset(lv_ileng).

   CONCATENATE lv_chunk <fs_rest> INTO lv_chunk IN BYTE MODE.
   REPLACE SECTION OFFSET lv_offset
           OF cv_image
           WITH lv_chunk
           IN BYTE MODE.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

ENDFORM.                    " SCREENSHOT_ENHANCE
*&---------------------------------------------------------------------*
*&      Form  SCREENSHOT_DOWNLOAD
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*      -->P_LV_IMAGE  text
*      <--P_LV_SUBRC  text
*----------------------------------------------------------------------*
FORM screenshot_download USING    uv_image TYPE xstring
                          CHANGING cv_subrc TYPE sysubrc.

   DATA: lt_bdata  TYPE TABLE OF x,
         lv_title  TYPE string,
         lv_dname  TYPE string,
         lv_fname  TYPE string,
         lv_fpath  TYPE string,
         lv_path   TYPE string,
         lv_usrac  TYPE i,
         lv_filter TYPE string.

   CALL FUNCTION 'SCMS_XSTRING_TO_BINARY'
     EXPORTING
       buffer     = uv_image
*     APPEND_TO_TABLE = ' '
*   IMPORTING
*     OUTPUT_LENGTH   =
     TABLES
       binary_tab = lt_bdata.

   IF lt_bdata IS INITIAL.
     cv_subrc = 4.
     RETURN.
   ENDIF.

   lv_title = 'Download image to ...'.
   CONCATENATE sy-datum '-' sy-uzeit '.png' INTO lv_dname.
   lv_filter = '*.png'.

   CALL METHOD cl_gui_frontend_services=>file_save_dialog
     EXPORTING
       window_title         = lv_title
       default_extension    = '*.png'
       default_file_name    = lv_dname
*     with_encoding        =
       file_filter          = lv_filter
*     initial_directory    =
*     prompt_on_overwrite  = 'X'
     CHANGING
       filename             = lv_fname
       path                 = lv_path
       fullpath             = lv_fpath
       user_action          = lv_usrac
*     file_encoding        =
     EXCEPTIONS
       cntl_error           = 1
       error_no_gui         = 2
       not_supported_by_gui = 3
       OTHERS               = 4.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

   IF lv_usrac = cl_gui_frontend_services=>action_cancel.
     RETURN.
   ENDIF.

   CALL METHOD cl_gui_frontend_services=>gui_download
     EXPORTING
*     BIN_FILESIZE            =
       filename                = lv_fname
       filetype                = 'BIN'
*     APPEND                  = SPACE
*     WRITE_FIELD_SEPARATOR   = SPACE
*     HEADER                  = '00'
*     TRUNC_TRAILING_BLANKS   = SPACE
*     WRITE_LF                = 'X'
*     COL_SELECT              = SPACE
*     COL_SELECT_MASK         = SPACE
*     DAT_MODE                = SPACE
*     CONFIRM_OVERWRITE       = SPACE
*     NO_AUTH_CHECK           = SPACE
*     CODEPAGE                = SPACE
*     IGNORE_CERR             = ABAP_TRUE
*     REPLACEMENT             = '#'
*     WRITE_BOM               = SPACE
*     TRUNC_TRAILING_BLANKS_EOL = 'X'
*     WK1_N_FORMAT            = SPACE
*     WK1_N_SIZE              = SPACE
*     WK1_T_FORMAT            = SPACE
*     WK1_T_SIZE              = SPACE
*     SHOW_TRANSFER_STATUS    = 'X'
*     FIELDNAMES              =
*     WRITE_LF_AFTER_LAST_LINE  = 'X'
*     VIRUS_SCAN_PROFILE      = '/SCET/GUI_DOWNLOAD'
*    IMPORTING
*     FILELENGTH              =
     CHANGING
       data_tab                = lt_bdata
     EXCEPTIONS
       file_write_error        = 1
       no_batch                = 2
       gui_refuse_filetransfer = 3
       invalid_type            = 4
       no_authority            = 5
       unknown_error           = 6
       header_not_allowed      = 7
       separator_not_allowed   = 8
       filesize_not_allowed    = 9
       header_too_long         = 10
       dp_error_create         = 11
       dp_error_send           = 12
       dp_error_write          = 13
       unknown_dp_error        = 14
       access_denied           = 15
       dp_out_of_memory        = 16
       disk_full               = 17
       dp_timeout              = 18
       file_not_found          = 19
       dataprovider_exception  = 20
       control_flush_error     = 21
       not_supported_by_gui    = 22
       error_no_gui            = 23
       OTHERS                  = 24.

   IF sy-subrc <> 0.
     cv_subrc = sy-subrc.
     RETURN.
   ENDIF.

ENDFORM.                    " SCREENSHOT_DOWNLOAD
*&---------------------------------------------------------------------*
*&      Form  AT_SELECTION_SCREEN_OUTPUT
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*  -->  p1        text
*  <--  p2        text
*----------------------------------------------------------------------*
FORM at_selection_screen_output.

   DATA: ls_values TYPE vrm_value,
         lt_values TYPE vrm_values.

   ls_values-key  = '1'.
   ls_values-text = 'Title'.
   APPEND ls_values TO lt_values.

   ls_values-key  = '2'.
   ls_values-text = 'Author'.
   APPEND ls_values TO lt_values.

   ls_values-key  = '3'.
   ls_values-text = 'Description'.
   APPEND ls_values TO lt_values.

   ls_values-key  = '4'.
   ls_values-text = 'Source'.
   APPEND ls_values TO lt_values.

   ls_values-key  = '5'.
   ls_values-text = 'Comment'.
   APPEND ls_values TO lt_values.

   CALL FUNCTION 'VRM_SET_VALUES'
     EXPORTING
       id              = 'PA_KWORD'
       values          = lt_values
     EXCEPTIONS
       id_illegal_name = 1
       OTHERS          = 2.

   IF sy-subrc <> 0.
     RETURN.
   ENDIF.

ENDFORM.                    " AT_SELECTION_SCREEN_OUTPUT
*&---------------------------------------------------------------------*
*&      Form  INITIALIZATION
*&---------------------------------------------------------------------*
*       text
*----------------------------------------------------------------------*
*  -->  p1        text
*  <--  p2        text
*----------------------------------------------------------------------*
FORM initialization.

   pa_txstr = 'Hello World!'.

ENDFORM.                    " INITIALIZATION



Example


For our example we us "Title" as keyword and "Hello World!" as text string.

 

StepDescription
1We get a screenshot of our current SAP GUI window.
2

We combine chunk type, keyword and our text string.

Result: "tEXtTitle Hello World!"

3

We convert it to hex and place the "Zero Byte" delimiter between keyword and text string (in hex it's "00").

Result: "744558745469746C650048656C6C6F20576F726C6421"

  • "74455874" corresponds to "tEXt",
  • "5469746C65" corresponds to "Title"
  • "00" is a zero byte
  • "48656C6C6F20576F726C6421" corresponds to "Hello World!"
4

We get the length of the chunk data (that are keyword and text string).

Result: 18 bytes
5

We generate a CRC32 for chunk type and chunk data.

Result: "1373921944" (in hex it is "51E46298")

6

We build the complete chunk. It consists of length, chunk type, chunk data and CRC32.

Result "00000012744558745469746C650048656C6C6F20576F726C642151E46298"

7We place our tEXt-chunk before the IEND-chunk.
8We download our enhanced screenshot to desktop.


After downloading the image to your desktop, you can use IrfanView for displaying.

Demo.jpg

Have a look at the function "View->Show HEX view" of IrfanView.

HexView.jpg

With TweakPNG you can have a look at the different chunks. You will find our tEXt-chunk "Title" with the corresponding text string "Hello World!".

TweakPNG.jpg



Idea for practical use


It's just an idea and not a well proofed business case: Create your own generic object service to get a screenshot of the current SAP GUI window. Analyze the business context (given business object type and id, sy-structure, callstack and more - perhaps I will show that in another blog) and encode the gathered data into the PNG image as shown above. Download the image to the user's desktop so he can mail it to his support team. Support team of course would need a tool to show the PNG image and decode all tEXt chunks in it. Even if the user puts no additional information in his mail there are some helpful business context information in the image. As I said before just an idea ...

How to Make All Field in Account Assignment To Be Read only in SAP Purchase Requisition

$
0
0

In some of business case of MM ( Material Management ), you need to make all field in commitment item in SAP Change Purchase Requisition ( ME52N ) to be read only based on condition.

You can configure most of field of SAP Purchase Requisition ( ME52N ) to be  input or read only through SPRO Configuration but SPRO have a limit, it can’t  control all field on Commitment Item Tab.

For this case , i will use example : User cannot change commitment Item data  in purchase Requisition ( Preq ) document after that particular Preq document has been released 5 ( EBAN-BANPR )

Basically, SAP Preq ( ME52N ) commitment item field always input-able although all other field read only. but you can control it by creating an enhancement point .

me52n-sap-purchase-requisition

You need to create enhancement point which located in KACB Function Group and LKACBF01 Include program and in feldmodifikation_normal subroutine.

me52n-enhancement-point-sap

You can see on the picture above, I create enhancement-point and put ths code:

 

 

1

 

2

3

4

5

6

7

8

9

10

 

data:ls_eban type eban.

 

 

select single *from eban into ls_eban where banfn=cobl-awkey.

 

ifls_eban-banpr='05'.

   loop at gt_coblf into l_coblf.

     l_coblf-input=0.

     modify gt_coblf from l_coblf TRANSPORTING input.

   endloop.

endif.


Note : You just need to modify data ( L_COBLF-INPUT = 0 ) in internal table GT_COBLF .

Then the result is :

me52n-enhancement-point-sap

Fanning the Flames: Prefixing Variable/Attribute Names

$
0
0

Trigger Warning: This blog will probably annoy a number of people. It's been lurking in the back of my mind for some time now, originally inspired by Ralf Wenzel's article Hungarian beginner's course - a polemic scripture against Hungarian Notation. Among others, I wouldn't be too surprised to receive critical comments from Matthew Billingham in particular - but then again, maybe not. As Tolkien has put it a long time ago, "So many strange things have chanced that to learn the praise of a fair lady under the loving strokes of a Dwarf's axe will seem no great wonder." I figured I might as well give it a try, even though it might spoily my birthday festivities.

 

To cut to the point: I use prefixes for variables and parameters myself, and I encourage people to use a consistent prefix system throughout their code. In my experience, a sensible naming scheme for variables does help developing and maintaining ABAP code. I don't for a moment believe that I will convince anyone who doesn't want to accept that, but I do believe that in this discussion, there are a number of arguments that are too easily forgotten or cast aside. I'm well aware of the fact that this subject is guaranteed to trigger a lively - hum - discussion, so I will try to keep the tone a bit more formal and a little less emotional than Ralf did in his post. (To be fair - his trigger warning is in the title as well as the introduction.)

 

Perhaps, first and foremost - what is the prefix system I use? It boils down to a combination of one or two letters, an underscore and a sensible name for the variable. I'll focus on the assembly of the prefix here, since that's what the 'hungarian notation' discussion is about - whether you want to name your variables according to the dictionary structures or rather have some 'english' name is another discussion.

 

The first letter designates the origin or scope of the variable of parameter:

 

  • class members:
    • g = instance attribute
    • s = static attribute
  • methods and (where applicable) function modules:
    • l = local variable
    • i = importing parameter
    • e = exporting parameter
    • c = changing parameter
    • r = returning parameter
  • programs, function groups
    • g = global variable
    • p = PARAMETERS (report only)

 

The second letter distinguishes between the data types:

  • (none) - elementary data type
  • s = structure
  • t = internal table
  • r = reference

 

For WebDynpro coding, I use a slightly different notation - basically because I'm too lazy to to through the generated code and adapt it all:

  • v = elementary data type (WebDynpro coding since that's the default in the generated code)
  • o = object reference (WebDynpro)
  • r = data reference

 

Exceptions and inconsistencies I haven't overcome yet:

  • so_ = SELECT-OPTIONS
  • co_ = constants
  • no prefix for public read-only attributes of persistence classes
  • no prefix for constants in "enum interfaces" that only contain constants
  • TABLES - hello, dynpros! - for structures EXCLUSIVELY, NEVER for transparent tables, same name as the structure

 

So we get, for example

  • gt_foo in a class - that would be an instance attribute that is an internal table
  • ls_bar - a structured local variable
  • ir_baz - an importing reference parameter

For FIELD-SYMBOLS, I tend to use the same notation, so you'll see stuff like <ls_foo> or <lr_bar> in my code. Since I tend to avoid global FIELD-SYMBOLS, I could omit the 'l', but decided to keep it for simplicity of the ruleset.

 

To sum it up: nothing too special here. I don't claim to have invented this prefix system, only adopted and adapted it slightly - although it's so generic that probably hundreds of developers around the world use either this system or something very similar. It's slightly more verbose than the scheme presented by Kai Meder in ABAP Modern Code Conventions, but I believe that the differences do justify an extra character.

 

Before discussion the advantages and disadvantages in detail, I would like to point out a simple and important fact that appears to be overlooked in many discussions: There is no perfect life. There is just life. We have to make do with what we have, and while we can (and should!) always aspire to refine our skills and improve ourselves as well as our environment, there are shortcomings, deficiencies and historically-explainable-but-nonetheless-ugly-kludges that we just have to deal with. Dreaming of a world without global variables, methods no longer than (insert number here) lines of code and pure object orientation may be an enjoyable pastime, but it doesn't get the job done. The world is out there, and it contains loooooong methods (many of which started out fairly short and grew through years of maintenance by many hands and a lack of refactoring), insanely complex systems and a weird mix of object-oriented and procedural programming, so we have to deal with stuff like that on a daily basis.

 

We also have to deal with another important fact: Humans make errors. Even the most intelligent and experienced developer will eventually produce a bug (other blog authors and moderators excepted, of course). Since a naming convention is of no use whatsoever to the maching, it has to be optimized to support the human in front of the machine. Ideally, it should aid in understanding the code that is already present and prevent coding errors wherever possible - or at least make them more obvious. So let's take a look at some common mistakes and how the naming conventions above aid in preventing or at least spotting these.

 

Let's start with a common controversy - why use prefixes for local and global variables at all? There are really two arguments that make me recommend using the prefixes 'g' and 'l' respectively, despite many notable voices arguing against their use. First off - shadowingand name masking. These concepts exist in many programming languages, and I have yet to find a real-world application that does not involve a decent amount of sadism towards the poor souls who have to maintain the code afterwards. From my experience, wherever you see variable shadowing actually happening outside of a programming class, something has usually gone south when refactoring or maintaining the code. Variable shadowing produces particularly nasty bugs that can easily take hours or even days to hunt down. Spliting the namespace into distinct partitions for global and local variables neatly solves that problem - if all class attributes start with 'g' (or 's') and all method-internal variables start with 'l', shadowing won't ever be a problem. Other languages cope by adding compiler warnings and smart IDE extensions to help the developer spot variables shadowing each other, which is just another workaround for the same problem, and one we don't have yet (the last time I checked, even the splendid ATC didn't warn about shadowed variables).

 

The second argument for "scope prefixes" I'd like to present is related to the counter-argument "variables are always local, and who get's to say what's called global in an object-orientated aplication anyway?" It certainly isn't easy to answer the latter question, but it helps to approach the issue from a different angle: When I change the contents of that foo thingy over there - then what is the scope (or lifetime, if you prefer) of that change?Am I only affecting the execution context of the method I'm currently in, and whatever I change gets discarded when the method is processed anyway - or am I permanently changing the state of the current instance, or even the entire class? You may want to call these changes "local" and "global", for want of a better name. Of course, this can be easily determined with a simple double-click, but it's much easier to write and maintain code if you've got that information embedded in the identifier. If you use this consistently, especially as an entry-level programmer, you will find that after a time, using something prefixed with 'g' will start to feel slightly awkward - should this really be global, do I need this anywhere else, can't I just make this a local variable? - whereas accessing something prefixed with 's' outside of some specially designated factory methods will set of alarm bells at once just by reading it. I've seen many bugs that ultimately could be traced back to an accidental modification of an attribute value (often after an improperly executed refactoring operation), and again, these bugs are really hard to find because they usually only surface much later in the application, and even then only under particular circumstances.

 

Now for the data type designator - what's the point in distinguishing between elementary fields, structures, tables and references? Convenience, mostly, but there are a few cases where these prefixes really can help you spot problems. ABAP has a history of peculiar conversion rules, and the one I'm after here is the conversion between flat structures and single fields. Let's assume you're coding a data object for some translateable entity (something with a separate language-dependent table that contains the translateable parts of the object), and you're keeping the text in an atribute named description. Some day you note that you've forgotten to add an accessor method for that, so you write a getter that returns a STRING, something as simple as r_result = description. Clean and obvious - until you notice that your application displays '100DEFoobar' instead of 'Foobar'. Whoops. Looks like description wasn't only the text, but you decided to store the entire text table entry instead (which makes perfect sense, especially if you've got multiple translateable fields). If you had named that attribute gs_description, you would have noted that immediately and prevented that mistake. Now this is an easy example, but I've seen this error happen to experienced developers in large applications, and again, it takes time and patience to hunt down that unwanted client number in the outbound message stream.

 

With internal tables, this kind of conversion error will not occur, but there's another quirk in the ABAP syntax that virtually every newbie has to master. Consider the following two examples:

 

DATA: sflight TYPE TABLE OF sflight.

 

" somehow, magically, fill that table with some values

 

LOOP AT sflight ASSIGNING FIELD-SYMBOL(<sflight>).

  " some condition, don't care, whatever...

  DELETE TABLE sflight FROM <sflight>.

ENDLOOP.

 

as opposed to

 

DATA: sflight TYPE TABLE OF sflight.

 

" somehow, magically, fill that table with some values

 

LOOP AT sflight ASSIGNING FIELD-SYMBOL(<sflight>).

  " some condition, don't care, whatever...

  DELETE sflight FROM <sflight>.

ENDLOOP.

 

Now granted, that's taking it to the limits, but the experienced ones among you will know how inventive the inexperienced among you can get when introduced to ABAP. I might have turned that into another trapdoor article, but just to reiterate: the first example will modify the internal table while the second example will actually kill selected contents of the database table. The issue here isn't only that this will be dangerous for the application data - bugs like these are usually so catastrophic in their results that they tend to be found early on - but that it is much harder to determine the intent of the developer and pinpoint the bug. Now let's compare this to a prefixed version:

 

DATA: lt_sflight TYPE TABLE OF sflight.

 

" somehow, magically, fill that table with some values

 

LOOP AT lt_sflight ASSIGNING FIELD-SYMBOL(<ls_sflight>).


  DELETE TABLE lt_sflight FROM <ls_sflight>. " OK - we're modifying the local table

                                             " contents here


  DELETE TABLE sflight FROM <ls_sflight>.    " syntax error: The field "SFLIGHT" is

                                             " unknown, but there is a field with

                                             " the similar name "LT_SFLIGHT".


  DELETE lt_sflight FROM <ls_sflight>.       " ATC complains about "Non-numeric index

                                             " access on internal table"

                                             " and suggests correct variant above.


  DELETE sflight FROM <ls_sflight>.          " OK - changing the database contents


ENDLOOP.

 

Also, after a while, typing LOOP AT lt_ followed by Ctrl+Space becomes a habit, and the code completion will present only the local tables - without robbing you of the flexibility to simply type LOOP AT<Ctrl+Space> and still have access to the full list of options.

 

All things considered, prefixes for variable names aren't as bad as some articles make them. Prefixes are a workaround, a compromise between what we have, what we can get, what we need and most importantly what we don't want. If you've ever ran into any of the bugs I mentioned above, either in your own code or someone else's, you know what I'm talking about. If you haven't (yet?), still please consider the junior members of your team and the people who succeed you and have to maintain your code. Someone may be very grateful for those few extra characters that really take no time either typing or reading.

How to use AMDP Function implementation for a CDS Table Function as a data source in CDS views

$
0
0

I had a requirement to get date name from a time stamp, which was stored as decimals in the backend.So, I need to use the ‘dayname’ date function of the sql query to convert to decimals to a string.

Because you cannot use direct sql functions in CDS views,my only option was left to use CDS table function. As defined CDS table function is associated with an AMDP function, in which it is implemented using SQLScript Like any CDS entity. In the following example, I have no input parameters and it is not client dependent.

First, I created a table function using the statementDEFINE TABLE FUNCTIONin theABAP CDSDDL.


@ClientDependent:false

@EndUserText.label:'DEMO_TABLE_FUNCTION'

 

definetablefunction  DEMO_TABLE_FUNCTION

 

returns{

         

       key     guid:GUID;

        weekdayname:string10;

            st_time:string10;

             }

implementedbymethod

    Z_CL_AMDP_CDS_TBLFN=>FIND_DAY_FROM_DATES;



Here the output parameters are guid,weekdayname and st_time which are associated with corresponding data types.Now I will create an AMDP class and flag it as a CDS table function. Please note an AMDP function implementation for a CDS table function can only be declared in the public visibility section of a static AMDP class.

 

CLASS Z_CL_AMDP_CDS_TBLFN DEFINITION

PUBLIC

FINAL

CREATE PUBLIC .

PUBLIC SECTION.

INTERFACES if_amdp_marker_hdb .

 

 

CLASS-METHODS find_day_from_datesFOR TABLE FUNCTION DEMO_ICM_TABLE_FUNCTIO.

 

 

 

PROTECTED SECTION.

PRIVATE SECTION.

  1. ENDCLASS.

 

 

 

CLASS Z_CL_AMDP_CDS_TBLFN IMPLEMENTATION.

 

 

METHOD find_day_from_dates

       BY DATABASE function FOR HDB

       LANGUAGE SQLSCRIPT

       OPTIONS READ-ONLY using table_name.

 

  RETURN SELECT CASE_GUID as guid,DAYNAME ( TO_DATE ( SUBSTRING (create_time,1,8))) as weekdayname,TO_DATE ( SUBSTRING (create_time,1,8)) as  st_time  FROM table_name  ;

 

 

  1. ENDMETHOD.
  2. ENDCLASS.

 

Create a class  and declare interface‘if_amdp_marker_hdb’ for HANA database(for other database use interface name as  IF_AMDP_MARKER_(DB name)).Then declare the class method and use ‘FOR TABLE FUNCTION’ and the CDS table function name to declare the AMDP method as a CDS table function implementation . Not need to declare any importing exporting parameters in the declaration of method.

 

In the implementation of the class, declare method as an AMDP function. Use ‘RETURN’ statement to return to exporting parameters .The input and the output parameters in the AMDP function implementation are determined by theinput parameters of the CDS table function.

 

Now create a CDS view in ABAP CDSDDL and use the CDS table function as a data source as the following.

 

@AbapCatalog.sqlViewName:'CCMC'

@AbapCatalog.compiler.compareFilter:true

@AccessControl.authorizationCheck:#CHECK

@EndUserText.label:'Day based graph'

defineview  ZCOMBI_VIEWAND_FNTABLE asselectfrom CDS_VIEW_NAME as VIEW_NAME1 innerjoin CDS_table_function_name as day_name on VIEW_NAME1. guid=day_name.guid {

    key day_name.weekdayname as DayName,

    VIEW_NAME1.description as Description,

 

count(*)as DayNameCount

    }

    groupby day_name.weekdayname, VIEW_NAME1.description

 

 

Note that I have not used input parameters in the CDS table function. If you are required to use CDS table function with parameters, please include then in the declaration of CDS table function as “define table function  DEMO_TABLE_FUNCTION FnName with parameters() returns()implemented by method MethodName”. 

How to Make All Field in Account Assignment To Be Read only in SAP Purchase Requisition

$
0
0

In some of business case of MM ( Material Management ), you need to make all field in commitment item in SAP Change Purchase Requisition ( ME52N ) to be read only based on condition.

You can configure most of field of SAP Purchase Requisition ( ME52N ) to be  input or read only through SPRO Configuration but SPRO have a limit, it can’t  control all field on Commitment Item Tab.

For this case , i will use example : User cannot change commitment Item data  in purchase Requisition ( Preq ) document after that particular Preq document has been released 5 ( EBAN-BANPR )

Basically, SAP Preq ( ME52N ) commitment item field always input-able although all other field read only. but you can control it by creating an enhancement point .

me52n-sap-purchase-requisition

You need to create enhancement point which located in KACB Function Group and LKACBF01 Include program and in feldmodifikation_normal subroutine.

me52n-enhancement-point-sap

You can see on the picture above, I create enhancement-point and put ths code:

 

 

1

 

2

3

4

5

6

7

8

9

10

 

data:ls_eban type eban.

 

 

select single *from eban into ls_eban where banfn=cobl-awkey.

 

ifls_eban-banpr='05'.

   loop at gt_coblf into l_coblf.

     l_coblf-input=0.

     modify gt_coblf from l_coblf TRANSPORTING input.

   endloop.

endif.


Note : You just need to modify data ( L_COBLF-INPUT = 0 ) in internal table GT_COBLF .

Then the result is :

me52n-enhancement-point-sap

Fanning the Flames: Prefixing Variable/Attribute Names

$
0
0

Trigger Warning: This blog will probably annoy a number of people. It's been lurking in the back of my mind for some time now, originally inspired by Ralf Wenzel's article Hungarian beginner's course - a polemic scripture against Hungarian Notation. Among others, I wouldn't be too surprised to receive critical comments from Matthew Billingham in particular - but then again, maybe not. As Tolkien has put it a long time ago, "So many strange things have chanced that to learn the praise of a fair lady under the loving strokes of a Dwarf's axe will seem no great wonder." I figured I might as well give it a try, even though it might spoily my birthday festivities.

 

To cut to the point: I use prefixes for variables and parameters myself, and I encourage people to use a consistent prefix system throughout their code. In my experience, a sensible naming scheme for variables does help developing and maintaining ABAP code. I don't for a moment believe that I will convince anyone who doesn't want to accept that, but I do believe that in this discussion, there are a number of arguments that are too easily forgotten or cast aside. I'm well aware of the fact that this subject is guaranteed to trigger a lively - hum - discussion, so I will try to keep the tone a bit more formal and a little less emotional than Ralf did in his post. (To be fair - his trigger warning is in the title as well as the introduction.)

 

Perhaps, first and foremost - what is the prefix system I use? It boils down to a combination of one or two letters, an underscore and a sensible name for the variable. I'll focus on the assembly of the prefix here, since that's what the 'hungarian notation' discussion is about - whether you want to name your variables according to the dictionary structures or rather have some 'english' name is another discussion.

 

The first letter designates the origin or scope of the variable of parameter:

 

  • class members:
    • g = instance attribute
    • s = static attribute
  • methods and (where applicable) function modules:
    • l = local variable
    • i = importing parameter
    • e = exporting parameter
    • c = changing parameter
    • r = returning parameter
  • programs, function groups
    • g = global variable
    • p = PARAMETERS (report only)

 

The second letter distinguishes between the data types:

  • (none) - elementary data type
  • s = structure
  • t = internal table
  • r = reference

 

For WebDynpro coding, I use a slightly different notation - basically because I'm too lazy to to through the generated code and adapt it all:

  • v = elementary data type (WebDynpro coding since that's the default in the generated code)
  • o = object reference (WebDynpro)
  • r = data reference

 

Exceptions and inconsistencies I haven't overcome yet:

  • so_ = SELECT-OPTIONS
  • co_ = constants
  • no prefix for public read-only attributes of persistence classes
  • no prefix for constants in "enum interfaces" that only contain constants
  • TABLES - hello, dynpros! - for structures EXCLUSIVELY, NEVER for transparent tables, same name as the structure

 

So we get, for example

  • gt_foo in a class - that would be an instance attribute that is an internal table
  • ls_bar - a structured local variable
  • ir_baz - an importing reference parameter

For FIELD-SYMBOLS, I tend to use the same notation, so you'll see stuff like <ls_foo> or <lr_bar> in my code. Since I tend to avoid global FIELD-SYMBOLS, I could omit the 'l', but decided to keep it for simplicity of the ruleset.

 

To sum it up: nothing too special here. I don't claim to have invented this prefix system, only adopted and adapted it slightly - although it's so generic that probably hundreds of developers around the world use either this system or something very similar. It's slightly more verbose than the scheme presented by Kai Meder in ABAP Modern Code Conventions, but I believe that the differences do justify an extra character.

 

Before discussion the advantages and disadvantages in detail, I would like to point out a simple and important fact that appears to be overlooked in many discussions: There is no perfect life. There is just life. We have to make do with what we have, and while we can (and should!) always aspire to refine our skills and improve ourselves as well as our environment, there are shortcomings, deficiencies and historically-explainable-but-nonetheless-ugly-kludges that we just have to deal with. Dreaming of a world without global variables, methods no longer than (insert number here) lines of code and pure object orientation may be an enjoyable pastime, but it doesn't get the job done. The world is out there, and it contains loooooong methods (many of which started out fairly short and grew through years of maintenance by many hands and a lack of refactoring), insanely complex systems and a weird mix of object-oriented and procedural programming, so we have to deal with stuff like that on a daily basis.

 

We also have to deal with another important fact: Humans make errors. Even the most intelligent and experienced developer will eventually produce a bug (other blog authors and moderators excepted, of course). Since a naming convention is of no use whatsoever to the maching, it has to be optimized to support the human in front of the machine. Ideally, it should aid in understanding the code that is already present and prevent coding errors wherever possible - or at least make them more obvious. So let's take a look at some common mistakes and how the naming conventions above aid in preventing or at least spotting these.

 

Let's start with a common controversy - why use prefixes for local and global variables at all? There are really two arguments that make me recommend using the prefixes 'g' and 'l' respectively, despite many notable voices arguing against their use. First off - shadowingand name masking. These concepts exist in many programming languages, and I have yet to find a real-world application that does not involve a decent amount of sadism towards the poor souls who have to maintain the code afterwards. From my experience, wherever you see variable shadowing actually happening outside of a programming class, something has usually gone south when refactoring or maintaining the code. Variable shadowing produces particularly nasty bugs that can easily take hours or even days to hunt down. Spliting the namespace into distinct partitions for global and local variables neatly solves that problem - if all class attributes start with 'g' (or 's') and all method-internal variables start with 'l', shadowing won't ever be a problem. Other languages cope by adding compiler warnings and smart IDE extensions to help the developer spot variables shadowing each other, which is just another workaround for the same problem, and one we don't have yet (the last time I checked, even the splendid ATC didn't warn about shadowed variables).

 

The second argument for "scope prefixes" I'd like to present is related to the counter-argument "variables are always local, and who get's to say what's called global in an object-orientated aplication anyway?" It certainly isn't easy to answer the latter question, but it helps to approach the issue from a different angle: When I change the contents of that foo thingy over there - then what is the scope (or lifetime, if you prefer) of that change?Am I only affecting the execution context of the method I'm currently in, and whatever I change gets discarded when the method is processed anyway - or am I permanently changing the state of the current instance, or even the entire class? You may want to call these changes "local" and "global", for want of a better name. Of course, this can be easily determined with a simple double-click, but it's much easier to write and maintain code if you've got that information embedded in the identifier. If you use this consistently, especially as an entry-level programmer, you will find that after a time, using something prefixed with 'g' will start to feel slightly awkward - should this really be global, do I need this anywhere else, can't I just make this a local variable? - whereas accessing something prefixed with 's' outside of some specially designated factory methods will set of alarm bells at once just by reading it. I've seen many bugs that ultimately could be traced back to an accidental modification of an attribute value (often after an improperly executed refactoring operation), and again, these bugs are really hard to find because they usually only surface much later in the application, and even then only under particular circumstances.

 

Now for the data type designator - what's the point in distinguishing between elementary fields, structures, tables and references? Convenience, mostly, but there are a few cases where these prefixes really can help you spot problems. ABAP has a history of peculiar conversion rules, and the one I'm after here is the conversion between flat structures and single fields. Let's assume you're coding a data object for some translateable entity (something with a separate language-dependent table that contains the translateable parts of the object), and you're keeping the text in an atribute named description. Some day you note that you've forgotten to add an accessor method for that, so you write a getter that returns a STRING, something as simple as r_result = description. Clean and obvious - until you notice that your application displays '100DEFoobar' instead of 'Foobar'. Whoops. Looks like description wasn't only the text, but you decided to store the entire text table entry instead (which makes perfect sense, especially if you've got multiple translateable fields). If you had named that attribute gs_description, you would have noted that immediately and prevented that mistake. Now this is an easy example, but I've seen this error happen to experienced developers in large applications, and again, it takes time and patience to hunt down that unwanted client number in the outbound message stream.

 

With internal tables, this kind of conversion error will not occur, but there's another quirk in the ABAP syntax that virtually every newbie has to master. Consider the following two examples:

 

DATA: sflight TYPE TABLE OF sflight.

 

" somehow, magically, fill that table with some values

 

LOOP AT sflight ASSIGNING FIELD-SYMBOL(<sflight>).

  " some condition, don't care, whatever...

  DELETE TABLE sflight FROM <sflight>.

ENDLOOP.

 

as opposed to

 

DATA: sflight TYPE TABLE OF sflight.

 

" somehow, magically, fill that table with some values

 

LOOP AT sflight ASSIGNING FIELD-SYMBOL(<sflight>).

  " some condition, don't care, whatever...

  DELETE sflight FROM <sflight>.

ENDLOOP.

 

Now granted, that's taking it to the limits, but the experienced ones among you will know how inventive the inexperienced among you can get when introduced to ABAP. I might have turned that into another trapdoor article, but just to reiterate: the first example will modify the internal table while the second example will actually kill selected contents of the database table. The issue here isn't only that this will be dangerous for the application data - bugs like these are usually so catastrophic in their results that they tend to be found early on - but that it is much harder to determine the intent of the developer and pinpoint the bug. Now let's compare this to a prefixed version:

 

DATA: lt_sflight TYPE TABLE OF sflight.

 

" somehow, magically, fill that table with some values

 

LOOP AT lt_sflight ASSIGNING FIELD-SYMBOL(<ls_sflight>).


  DELETE TABLE lt_sflight FROM <ls_sflight>. " OK - we're modifying the local table

                                             " contents here


  DELETE TABLE sflight FROM <ls_sflight>.    " syntax error: The field "SFLIGHT" is

                                             " unknown, but there is a field with

                                             " the similar name "LT_SFLIGHT".


  DELETE lt_sflight FROM <ls_sflight>.       " ATC complains about "Non-numeric index

                                             " access on internal table"

                                             " and suggests correct variant above.


  DELETE sflight FROM <ls_sflight>.          " OK - changing the database contents


ENDLOOP.

 

Also, after a while, typing LOOP AT lt_ followed by Ctrl+Space becomes a habit, and the code completion will present only the local tables - without robbing you of the flexibility to simply type LOOP AT<Ctrl+Space> and still have access to the full list of options.

 

All things considered, prefixes for variable names aren't as bad as some articles make them. Prefixes are a workaround, a compromise between what we have, what we can get, what we need and most importantly what we don't want. If you've ever ran into any of the bugs I mentioned above, either in your own code or someone else's, you know what I'm talking about. If you haven't (yet?), still please consider the junior members of your team and the people who succeed you and have to maintain your code. Someone may be very grateful for those few extra characters that really take no time either typing or reading.

Dynamic IF Condition

$
0
0

We all have heard about dynamic conditions in WHERE clauses (Dynamic where clause - ABAP Development - SCN Wiki), dynamic READ statements (Dynamic WHERE in READ TABLE | SCN), dynamic internal tables/ structures/ ALV (Tutorial abap - Code for Dynamic Alv grid - Code Gallery - SCN Wiki), in short dynamic programming (Dynamic Programming -  Application Development on AS ABAP - SAP Library)...

 

But, when I searched about dynamic conditions in IF statement, I couldn't find many hits. Some of the hits said, it was not possible, some said to use ranges, and some others said it required dynamic subroutine generation etc.

 

I have used EVAL_FORMULA function module before (Program to evaluate formula in a string - ABAP Development - SCN Wiki). But, I haven't had much success with TEXT operations, using this function module.

 

So, I am here to discuss a method to achieve dynamic IF conditions in ABAP.

 

Interfaces used:

IF_FOBU_CONNECTOR

IF_FOEV_CONNECTOR

 

Classes used:

CL_FOBU_FORMULA - Formula Builder

CL_FOEV_FORMULA - Formula Evaluator

 

This same method can be used to evaluate dynamic formulae, just by changing the return type of the method. Since, I have used return type as BOOLEAN, I expect a TRUE or FALSE value to be returned by the method.

 

Global Class ZCL_EVALUATE_DYNAMIC_CONDITION:

    1  class zcl_evaluate_dynamic_condition definition

    2    public

    3    final

    4    createpublic .

    5 

    6    publicsection.

    7 

    8      interfaces if_fobu_connector .

    9      interfaces if_foev_connector .

   10 

   11      types:begin            of   ty_field_values .

   12      types name             type string.

   13      types data             typerefto data.

   14      types type             type string.

   15      typesend              of   ty_field_values .

   16      types:tyt_field_values typestandardtable

   17            of ty_field_values withnon-uniquekey table_line .

   18 

   19      class-methods factory

   20        returning

   21          value(rv_evaluator) typerefto zcl_evaluate_dynamic_condition .

   22      methods calculate

   23        importing

   24          value(it_dictionary) type tyt_field_values

   25          value(iv_formula)    type string

   26        returning

   27          value(rv_value)      type boolean .

   28    protectedsection.

   29    privatesection.

   30 

   31      data    dictionary       type tyt_field_values .

   32  endclass.

   33 

   34  class zcl_evaluate_dynamic_condition implementation.

   35 

   36    methodcalculate.

   37      data: lr_env     typerefto if_foev_connector,

   38            lr_fenv    typerefto if_fobu_connector,

   39            lr_formula typerefto cl_fobu_formula,

   40            lr_runtime typerefto cl_foev_formula.

   41 

   42      dictionary      =  it_dictionary.

   43      lr_env          ?= me.

   44      lr_fenv         ?= me.

   45      cl_fobu_formula=>create(

   46        exporting

   47          im_tech_names   = abap_true

   48          im_environment  = lr_fenv

   49          im_desired_type ='BOOLEAN' "<<<<<

                 "Change this return type to use for other purposes

   50          io_fobu_storage =cl_fobu_storage=>get_instance()

   51        importing

   52          ex_formula      = lr_formula ).

   53      lr_formula->parse( iv_formula ).

   54      lr_runtime       =cl_foev_formula=>load_from_fobu(

   55                           im_formula     = lr_formula

   56                           im_environment = lr_env ).

   57      data(lv_resuld)  =lr_runtime->evaluate().

   58      assign lv_resuld->* to field-symbol(<res>).

   59      rv_value         =<res>.

   60    endmethod.

   61 

   62    methodfactory.

   63      createobject rv_evaluator.

   64    endmethod.

   65 

   66    methodif_fobu_connector~check.

   67    endmethod.

   68 

   69    methodif_fobu_connector~get.

   70    endmethod.

   71 

   72    methodif_fobu_connector~get_all_operands.

   73    endmethod.

   74 

   75    methodif_fobu_connector~parse_op_get.

   76      readtable dictionary into data(meaning)

   77           withkey name = ch_tech_name.

   78      if sy-subrc eq 0.

   79        ex_type  = meaning-type.

   80        ex_token = cl_fobu_formula=>c_token_appl_1.

   81      endif.

   82    endmethod.

   83 

   84    methodif_fobu_connector~pushbuttons_get.

   85    endmethod.

   86 

   87    methodif_fobu_connector~pushbutton_op_get.

   88    endmethod.

   89 

   90    methodif_foev_connector~evaluate.

   91      readtable dictionary into data(meaning)

   92           withkey name = im_fieldname.

   93      if sy-subrc eq 0.

   94        assign meaning-data->* to field-symbol(<fld>).

   95        createdata re_result like<fld>.

   96        assign re_result->* to field-symbol(<res>).

   97        <res> =<fld>.

   98      else.

   99        raiseexceptiontype cx_foev_formula_invalid

  100          exporting

  101            textid =|{ im_fieldname } field was not found in dictionary|.

  102      endif.

  103    endmethod.

  104  endclass.

 

Explanation:

  • IF_FOBU_CONNECTOR~PARSE_OP_GET helps in specifying the field types and token type, based on DICTIONARY entries.
  • IF_FOEV_CONNECTOR~EVALUATE helps in converting the fieldnames into values, based on the DICTIONARY entries.
  • LT_FIELDVALUES holds the values of all possible fields that may be used in the formula. Here, since I already know what are the fields used in my formula, I am passing only these values. This is stored in the DICTIONARY, so that the above methods may use it.
  • In the code below, I am passing a constant formula to the method, but purpose of this method is to analyze any dynamic text formula.

 

Example usage:

    1  class zcl_evaluate_dynamic_condition

    2        definitionload.

    3  try.

    4    data:lt_fieldvalues

    5      type zcl_evaluate_dynamic_condition=>tyt_field_values.

    6    lt_fieldvalues =value#(( name ='PLAAB'

    7                              type ='MDPS-PLAAB'

    8                              data =ref#( gs_mdps-plaab ))

    9                            ( name ='DELKZ'

   10                              type ='MDPS-DELKZ'

   11                              data =ref#( gs_mdps-delkz ))

   12                            ( name ='LIFNR'

   13                              type ='MDPS-LIFNR'

   14                              data =ref#( gs_mdps-lifnr ))

   15                            ( name ='PLUMI'

   16                              type ='MDPS-PLUMI'

   17                              data =ref#( gs_mdps-plumi ))).

   18 

   19    ifzcl_evaluate_dynamic_condition=>factory()->calculate(

   20      iv_formula     =|PLAAB = '02' AND DELKZ = 'BB'

                            AND LIFNR <> '' AND PLUMI = '-'|

   21      it_fieldvalues = lt_fieldvalues )= abap_true.

   22      continue.

   23    endif.

   24  catch cx_root.

   25  endtry.


Be Nice Now.....

$
0
0

There are some legitimate cases where you may want to wait a short while in your program for something to happen.  One thing I use a wait on is when I have a program running and it encounters a lock.  This is ok when you are displaying a single record - you just tell the user that it's locked.   However,  if you are compiling an editable ALV grid for example you don't want to stop them editing some records because one is locked.

 

What I do is wat a second or so,  and try again.  If the record is still locked I wait a little longer,  before after a certain number of times giving up and continuing on with the next record,  flagging the failed record as not locked.

 

But when you wait are you polite or do you still hog the work process ??

 

Instead of using the 'WAIT' statement use the 'ENQUE_SLEEP function module.  The function module releases the work process for another task to use.  The WAIT does not....

 

Rich

Create an ABAP Reusable Program with 5 easy steps.

$
0
0

Hi

 

This blog will explains

 

How to Create an ABAP Reusable Program with 5 easy steps.

 

Here I would like to demonstrate how we can create Reusable ABAP Custom Program using Custom Patterns.

 

Advantages with this approach:-

 

  1. Developer able to create the Program with just 4 steps with minimum functionality.
  2. We can able to reduce the Naming convention deviation up to 90% with this approach.
  3. For each new program, developer able to save 30 minutes of development effort with this approach.
  4. This approached demo program can be used as base line program for internal code review checks.

 

 

Kindly review the Blog and Share your valuable thoughts / comments on this Blog.

 

 

Thanks ,

Sreeram

 

Blog Content -

 

Step1:-

 

Create 4 Custom Patterns -

 

1.YPROGRAMHEADER

2.YPROGRAMSELECTION

3. YPROGRAMDECLARATIONS

4. YPROGRAMPROCESS

 

How to create custom patterns –

 

Go to Transaction - SE38 -> Program screen -> Utilities -> More Utilities -> Edit Pattern -> Create Pattern.Step1.png

 

When we follow above path, program will pop-up a window like below.


step1_popupwindow.png

 

Give the program code as your wish and click on save.

 

step1_savepattern.png

 

Using above step, create 4 Custom Patterns.

 

1.YPROGRAMHEADER

2.YPROGRAMSELECTION

3. YPROGRAMDECLARATIONS

4. YPROGRAMPROCESS

 

How to use these Patterns –

 

To get the advantage of these patterns, developer just needs to follow the below steps.

 

Step2:-

 

Create a program using SE38 transaction

 

Step2.png

Step3:-

 

Step2 will create the empty screen with Program name.

 

Once we got the program screen, just perform below one –

step3_1stpattern.png

Click on Pattern button and give the Pattern – YPROGRAMHEADER

 

It will create the program like below (Program with all Includes and required Modularizations).

 

insert1stpatterninabapcode.png


Step4:-

 

Double Click on selection-screen include and select the pattern YPROGRAMSELECTION

It will creates the required selection-screens code like below (Selection-screen code with Naming conventions)


create2ndpattern.png

insert2ndpatterninabapcode.png

Step5:-

 

Double Click on Data Declarations include and select the pattern YPROGRAMDECLARATIONS

It will creates the required data declarations like below (All the minimum required Declarations with naming conventions)

create3rdpattern.png

insert3rdpatterninabacode.png

Double Click on Data Declarations include and select the pattern YPROGRAMPROCESS

It will creates the required processing code like below (Processing logic with minimum required comments & conventions )


create4thpattern.png

insert4thpatterninabapcode.png


Advantages with this approach:-

 

  1. Developer able to create the Program with just 4 steps with minimum functionality.
  2. We can able to reduce the Naming convention deviation up to 90% with this approach.
  3. For each new program, developer able to save 30 minutes of development effort with this approach.
  4. This approached demo program can be used as base line program for internal code review checks.

DEALT WITH ‘DUPLICATE ENTRIES’ ABAP RUNTIME ERROR

$
0
0

Hi

 

In this Blog I tried to explain how to dealt with SAP Vistex ABAP Runtime Error - Duplicate Entries.

 

There could be multiple ways to identify & resolve the above issue and I would like to demonstrate 1 quick way to identify & fix.


I'm not sure whether it was already covered or not ? But for sure this is not any copied version and I'm demonstrating based on my personal experience only.


Please share your views / thoughts after review.


Thanks,

Sreeram


Blog Content -


In many of the cases we may experience the below error while updating the attributes using SAP Vistex transaction like /IRM/EPPDM   etc.

 

Error - The ABAP/4 Open SQL array insert results in duplicate database records.

Source ABAP Program for the Error - /IRM/SAPLEPPDU

Exception - CX_SY_OPEN_SQL_DB

Runtime Error - SAPSQL_ARRAY_INSERT_DUPREC


screen1.png

 

There could be multiple ways to identify & resolve the above issue and I would like to demonstrate 1 quick way to identify & fix.

 

Whenever we got the ABAP Runtime Error with error description “The ABAP/4 Open SQL array insert results in duplicate database records” and the Runtime error is - SAPSQL_ARRAY_INSERT_DUPREC,

Please follow below steps for issue fix.


Step1:-


Go to transaction ST22 and look for detailed error.


screen2.png

From above screen search with search term ‘VBHDR’.


screen3.png

You were able to find the VBHDR value as above.

Once you get the VBHDR value

 

Step2:- Go to table VBHDR (Update header) search for table records with the above key term value, you were able to find the record as below.


screen4.png

Step3:- Go to table VBDATA (Update data) search with the above key term value, you were able to find the record as below.


screen5.png

Once you got the VBDATA records in step3, perform step 4.

 

Step4:- Go to transaction SE 37, Run the Function Module TH_DISPLAY_UPDATE_DATA_ALV –

Will get the below screen


screen6.png

For the Import Parameter VB_CNT, pass the VBMODCNT value captured from VBDATA table.

For the Import Parameter VB_KEY, pass the VBKEY value captured from VBDATA table.

For the Import Parameter VB_NAME, pass the value ‘’/IRM/EPPD_UPDATE_DB”.

 

So above screen will be populated as below.



screen7.png

Execute the FM after inputting the data.

Will get the below Output  as below -


screen8.png


From the above screen, double click on the highlighted table – IT_INS_PDATV.


screen9.png


Step 5 –

Now go to table - /IRM/EPPDATV and search for the table entries with above key (PDNUM, ATINN, AMLVL, MLKEY, ATZHL combination),

  If able to find any entries, treat those records as Duplicate entries


screen10.png

If able to find any entries, treat those records as Duplicate entries.

Delete those duplicate entries from the table and try to re-update the attributes from attribute maintenance screen.

Issue should be resolved and able to update the attribute values by now.


Be Nice Now.....

$
0
0

There are some legitimate cases where you may want to wait a short while in your program for something to happen.  One thing I use a wait on is when I have a program running and it encounters a lock.  This is ok when you are displaying a single record - you just tell the user that it's locked.   However,  if you are compiling an editable ALV grid for example you don't want to stop them editing some records because one is locked.

 

What I do is wat a second or so,  and try again.  If the record is still locked I wait a little longer,  before after a certain number of times giving up and continuing on with the next record,  flagging the failed record as not locked.

 

But when you wait are you polite or do you still hog the work process ??

 

Instead of using the 'WAIT' statement use the 'ENQUE_SLEEP function module.  The function module releases the work process for another task to use.  The WAIT does not....

 

Rich

Dealing with ‘DUPLICATE ENTRIES’ ABAP runtime error with reference to SAP Vistex

$
0
0

Hi

 

In this Blog I tried to explain how to dealt with SAP Vistex ABAP Runtime Error - Duplicate Entries.

 

There could be multiple ways to identify & resolve the above issue and I would like to demonstrate 1 quick way to identify & fix.


I'm not sure whether it was already covered or not ? But for sure this is not any copied version and I'm demonstrating based on my personal experience only.


Please share your views / thoughts after review.


Thanks,

Sreeram


Blog Content -


In many of the cases we may experience the below error while updating the attributes using SAP Vistex transaction like /IRM/EPPDM   etc.

 

Error - The ABAP/4 Open SQL array insert results in duplicate database records.

Source ABAP Program for the Error - /IRM/SAPLEPPDU

Exception - CX_SY_OPEN_SQL_DB

Runtime Error - SAPSQL_ARRAY_INSERT_DUPREC


screen1.png

 

There could be multiple ways to identify & resolve the above issue and I would like to demonstrate 1 quick way to identify & fix.

 

Whenever we got the ABAP Runtime Error with error description “The ABAP/4 Open SQL array insert results in duplicate database records” and the Runtime error is - SAPSQL_ARRAY_INSERT_DUPREC,

Please follow below steps for issue fix.


Step1:-


Go to transaction ST22 and look for detailed error.


screen2.png

From above screen search with search term ‘VBHDR’.


screen3.png

You were able to find the VBHDR value as above.

Once you get the VBHDR value

 

Step2:- Go to table VBHDR (Update header) search for table records with the above key term value, you were able to find the record as below.


screen4.png

Step3:- Go to table VBDATA (Update data) search with the above key term value, you were able to find the record as below.


screen5.png

Once you got the VBDATA records in step3, perform step 4.

 

Step4:- Go to transaction SE 37, Run the Function Module TH_DISPLAY_UPDATE_DATA_ALV –

Will get the below screen


screen6.png

For the Import Parameter VB_CNT, pass the VBMODCNT value captured from VBDATA table.

For the Import Parameter VB_KEY, pass the VBKEY value captured from VBDATA table.

For the Import Parameter VB_NAME, pass the value ‘’/IRM/EPPD_UPDATE_DB”.

 

So above screen will be populated as below.



screen7.png

Execute the FM after inputting the data.

Will get the below Output  as below -


screen8.png


From the above screen, double click on the highlighted table – IT_INS_PDATV.


screen9.png


Step 5 –

Now go to table - /IRM/EPPDATV and search for the table entries with above key (PDNUM, ATINN, AMLVL, MLKEY, ATZHL combination),

  If able to find any entries, treat those records as Duplicate entries


screen10.png

If able to find any entries, treat those records as Duplicate entries.

Delete those duplicate entries from the table and try to re-update the attributes from attribute maintenance screen.

Issue should be resolved and able to update the attribute values by now.


Viewing all 948 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>