24
Tue, Dec
1 New Articles

Practical Examples of Code Without Indicators, Part II

RPG
Typography
  • Smaller Small Medium Big Bigger
  • Default Helvetica Segoe Georgia Times
Indicatorless programming isn't just about removing indicators; it's about using Boolean values properly. Today's article will show you how.

By Joe Pluta

In the previous article in this series, I showed you how to use the INDARA to remove the *INxx indicator syntax from your programs when dealing with display files. One of the techniques was to assign a block of contiguous indicators (in my case, indicators 41-60) to act as error indicators and then redefine that block as an array of indicators. This allowed me to change a field's attributes by simply setting on the appropriate entry in the error indicator array. In today's article, I'll expand on that technique and explain how to use named indicators to streamline your programs, such as removing as much as two-thirds of your code in your screen editing routines.

Wait! Are You Saying Indicators Are Good?

I suppose I should come clean. In the most expansive use of the term, I do indeed still use indicators. However, I use the new style of indicators, named indicators, not the old-fashioned 01-99 (and KA and OF and so on). Thanks to a solid computer programming background, I am very familiar with Boolean logic, and thanks to a long history of programming in C-style languages, I am comfortable using Boolean variables. Until the advent of named indicators, though, it was rather difficult to make full use of those capabilities in RPG programs. No matter how you sliced it, using an indicator meant at some point coding the dreaded *INxx, an arcane syntax unique to the RPG language and one that frankly makes programmers from other languages raise their eyebrows (and we don't even bring up left-handed indicators in polite programming discussions).

 

Using numbered indicators dates back to the days of punchboards and is a technique whose time has passed. Even the slightly more civilized *INxx still has fatal flaws, whether it's the limit of 99 flags or the fact that you have to somehow know what indicator 64 is supposed to represent. Code that uses numbered indicators is very unreadable.

 

However, unlike the obscure *INxx, named indicators make for some very readable code. First, define the variable to have the same name as the condition it represents. For example, let's say you want to define a variable that indicates whether you have a Customer master record or not. Create it as follows:

 

     dCustomerFound                    n

 

Defining a variable as type n specifies it as a named indicator. No length can be specified, since a named indicator must be a one-character field. In this case, if the variable is true (or *ON), it specifies that the customer was indeed found and must be updated. Otherwise, it's a new customer and the record must be added. This is a very standard design pattern in create/read/update/delete (CRUD) maintenance programs:

 

       if CustomerFound;

         UpdateCustomer();

       else;

         AddCustomer();

       endif;

 

Typically, you would set this variable from the results of a CHAIN operation, like so:

 

       custno chain CUSTMAST;

       CustomerFound = %found(CUSTMAST);

 

Please note that I try very hard to keep the names "positive"; that is, they don't usually have words like "not" in them. In the simplest case, either would work. Let's "reverse the polarity" of the variable:

 

       custno chain CUSTMAST;

       CustomerNotFound = not %found(CUSTMAST);

       if CustomerNotFound;

         AddCustomer();

       else;

         UpdateCustomer();

       endif;

 

This is just as good, right? However, the problem arises when you have to test for the opposite condition. In my opinion, "if not CustomerNotFound" is the kind of programming that can be very hard to understand. Polarity is a rarely discussed but very important aspect of Boolean logic, and we'll see it again shortly.

Indicators and Business Logic

Another place you might see named indicators is as a shortcut for long expressions. For example, take this credit check logic:

 

       if (CMSTAT <> 'H'

         and (OHAMOUNT + CMCURBAL) <= CMCRDLMT);

 

Basically, it checks that the customer is not on hold and that the purchase plus their current on-hand amount doesn't exceed their credit limit. Simple enough checks, but copying them over and over takes time and also introduces a place for inconsistencies. One option is to do the check in an edit routine and set a variable:

 

       PassedCreditCheck = (CMSTAT <> 'H'

         and (OHAMOUNT + CMCURBAL) <= CMCRDLMT);

          (...)

       if PassedCreditCheck;

 

The variable PassedCreditCheck (a named indicator) is used to store the state of the credit check algorithm. Note that you can set the value of a named indicator to any expression that returns *ON or *OFF. This powerful technique allows you to store the results of as complex a test as you want in a variable. Then, even as business rules change and regardless of how complicated the test becomes, you only have one place to change the code; everywhere else, you can just code the simple "if PassedCreditCheck." Another way to do this would be to create a subprocedure named PassedCreditCheck that does the same computation and returns the result as an indicator variable. I'll go into the pros and cons of that approach in another article.

Reducing the Code in an Edit Routine

Today, though, I want to show you how this same technique of treating a complex expression as a simple Boolean variable can reduce your editing logic significantly. Let's take a standard maintenance panel editing screen:

 

       if XXCUST = 0;

         Errors(F_XXCUST) = *on;

         SendError(E_ZEROCUST);

       endif;

 

Even if your code doesn't look exactly like this, you should be able to recognize what it does. If the customer number is 0, it sets on an error indicator. (This uses the INDARA technique from my previous article to create an array of Boolean indicators, each one representing a specific field.) I advanced the technique a little; for readability, I defined constants, one for the index of each field. So F_XXCUST represents the position in the array of the indicator for the XXCUST field. Next, I send an error message. In my programs, I use a message subfile. The SendError subprocedure accepts a message ID and does all the rest of the grunt work to send the message. I use a constant to identify the message ID.

 

So, including the IF and ENDIF, every test requires at least four lines of code. And even if I combined setting the error indicator with sending the message functions into a single SetError procedure, I'd still end up with code like this:

 

       if XXCUST = 0;

         SetError(F_XXCUST : E_ZEROCUST);

       endif;

       if XXNAME = *BLANKS;

         SetError(F_XXNAME : E_NAMEBLANK);

       endif;

       if XXCITY = *BLANKS;

         SetError(F_XXCITY : E_CITYBLANK);

       endif;

 

If a master record has 20 tests, at three lines per test, the comparisons alone could expand to well over 50 lines of code, not counting the additional business logic to chain out to other files to verify key values and so on. However, I have a way to reduce that code to just one line per test:

 

       TestError((XXCUST = 0) : E_ZEROCUST: F_XXNAME);

       TestError((XXNAME = *BLANKS) : E_NAMEBLANK : F_XXNAME);

       TestError((XXCITY = *BLANKS) : E_CITYBLANK : F_XXCITY);

 

What's this? Well, in this technique you can write a procedure that takes three parameters: the field whose error to set, the message ID to send, and the condition to test. I think the last two parameters are pretty straightforward, but what is the first one? Well, remember that anywhere you have a named indicator, you can substitute a Boolean expression: one that evaluates to *ON or *OFF. For example, (XXCUST = 0) will either return *ON if customer is 0 or *OFF if it is not. The cool part is that if you define a parameter to a procedure as a named indicator, then you can specify an expression on the call. The system will evaluate the expression at runtime and pass the result, *ON or *OFF, to the procedure (note that to allow this automatic evaluation, the parameter must also be a constant parameter identified by the keyword CONST as shown).

 

The code for the TestError procedure is very straightforward:

 

     pTestError    b

     d                 pi              

     d  Condition                      n   const

     d  MsgID                         7a   const

     d  FieldIndex                    3u 0 const

      /free

       if condition;

         SendError(MsgID);

         Errors(FieldIndex) = *on;

       endif;

      /end-free

     p                 e

 

It takes three parameters: the condition, the message ID, and the field index. If the condition is true, the message is sent and the field's indicator is set on. This little routine can make your programming much more compact and easy to read, and with a liberal application of constants to identify the field indices and error message IDs, you can also insulate yourself from changes quite nicely.

 

Please note that the routine above is pretty simple. It assumes a one-to-one relationship between errors, messages, and fields: it always sends a single message and sets a single field for each error. I usually use a slightly more robust technique in production programs:

 

     pTestError    b

     d                 pi              n

     d  Condition                      n   const

     d  MsgID                         7a   const

     d  FieldIndex1                   3u 0 const options(*nopass)

     d  FieldIndex2                   3u 0 const options(*nopass)

     d  FieldIndex3                   3u 0 const options(*nopass)

      /free

       if condition;

         if MsgID <> *blanks;

           SendError(MsgID);

         endif;

         if (%parms > 2);

           Errors(FieldIndex1) = *on;

         endif;

         if (%parms > 3);

           Errors(FieldIndex2) = *on;

         endif;

         if (%parms > 4);

           Errors(FieldIndex3) = *on;

         endif;

       endif;

       return condition;

      /end-free

     p                 e

 

These changes allow a little more flexibility in how the routine is called and also allow some serious chaining of conditions as needed. For example:

 

       if TestError(cond1 : E_MSG1 : F_FLD1)

          or TestError(cond2 : E_MSG1 : F_FLD2A : F_FLD2B) 

          or TestError(cond3 : E_MSG3);

         (... errors ...)

       else;

         (... no errors ...)

       endif;

 

I can test multiple conditions, sending the error messages and setting fields in error. Note that condition two sets the error flag on two different fields (for example, two UOM fields that have no cross-reference or two fields in a multi-field key), while condition three only sends a message, setting no fields (a record lock or some other unexpected error occurs). But because the TestError procedure returns the condition, I can test it, and if any of the error conditions exist, I will execute the error-handling logic; otherwise, I handle the success logic.

Final Note

In reality, the code is often not as simple as this; you might find yourself executing chains to other files to get information or calling other programs or procedures. Some errors are so severe as to stop processing altogether. Other messages need to be sent back to the calling program (another great topic for another day). But no matter what, the idea of using expressions as variables--whether they are passed as parameters or used as Boolean variables later in your code--can simplify your RPG code and make it more readable.

BLOG COMMENTS POWERED BY DISQUS

LATEST COMMENTS

Support MC Press Online

$

Book Reviews

Resource Center

  • SB Profound WC 5536 Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application. You can find Part 1 here. In Part 2 of our free Node.js Webinar Series, Brian May teaches you the different tooling options available for writing code, debugging, and using Git for version control. Brian will briefly discuss the different tools available, and demonstrate his preferred setup for Node development on IBM i or any platform. Attend this webinar to learn:

  • SB Profound WP 5539More than ever, there is a demand for IT to deliver innovation. Your IBM i has been an essential part of your business operations for years. However, your organization may struggle to maintain the current system and implement new projects. The thousands of customers we've worked with and surveyed state that expectations regarding the digital footprint and vision of the company are not aligned with the current IT environment.

  • SB HelpSystems ROBOT Generic IBM announced the E1080 servers using the latest Power10 processor in September 2021. The most powerful processor from IBM to date, Power10 is designed to handle the demands of doing business in today’s high-tech atmosphere, including running cloud applications, supporting big data, and managing AI workloads. But what does Power10 mean for your data center? In this recorded webinar, IBMers Dan Sundt and Dylan Boday join IBM Power Champion Tom Huntington for a discussion on why Power10 technology is the right strategic investment if you run IBM i, AIX, or Linux. In this action-packed hour, Tom will share trends from the IBM i and AIX user communities while Dan and Dylan dive into the tech specs for key hardware, including:

  • Magic MarkTRY the one package that solves all your document design and printing challenges on all your platforms. Produce bar code labels, electronic forms, ad hoc reports, and RFID tags – without programming! MarkMagic is the only document design and print solution that combines report writing, WYSIWYG label and forms design, and conditional printing in one integrated product. Make sure your data survives when catastrophe hits. Request your trial now!  Request Now.

  • SB HelpSystems ROBOT GenericForms of ransomware has been around for over 30 years, and with more and more organizations suffering attacks each year, it continues to endure. What has made ransomware such a durable threat and what is the best way to combat it? In order to prevent ransomware, organizations must first understand how it works.

  • SB HelpSystems ROBOT GenericIT security is a top priority for businesses around the world, but most IBM i pros don’t know where to begin—and most cybersecurity experts don’t know IBM i. In this session, Robin Tatam explores the business impact of lax IBM i security, the top vulnerabilities putting IBM i at risk, and the steps you can take to protect your organization. If you’re looking to avoid unexpected downtime or corrupted data, you don’t want to miss this session.

  • SB HelpSystems ROBOT GenericCan you trust all of your users all of the time? A typical end user receives 16 malicious emails each month, but only 17 percent of these phishing campaigns are reported to IT. Once an attack is underway, most organizations won’t discover the breach until six months later. A staggering amount of damage can occur in that time. Despite these risks, 93 percent of organizations are leaving their IBM i systems vulnerable to cybercrime. In this on-demand webinar, IBM i security experts Robin Tatam and Sandi Moore will reveal:

  • FORTRA Disaster protection is vital to every business. Yet, it often consists of patched together procedures that are prone to error. From automatic backups to data encryption to media management, Robot automates the routine (yet often complex) tasks of iSeries backup and recovery, saving you time and money and making the process safer and more reliable. Automate your backups with the Robot Backup and Recovery Solution. Key features include:

  • FORTRAManaging messages on your IBM i can be more than a full-time job if you have to do it manually. Messages need a response and resources must be monitored—often over multiple systems and across platforms. How can you be sure you won’t miss important system events? Automate your message center with the Robot Message Management Solution. Key features include:

  • FORTRAThe thought of printing, distributing, and storing iSeries reports manually may reduce you to tears. Paper and labor costs associated with report generation can spiral out of control. Mountains of paper threaten to swamp your files. Robot automates report bursting, distribution, bundling, and archiving, and offers secure, selective online report viewing. Manage your reports with the Robot Report Management Solution. Key features include:

  • FORTRAFor over 30 years, Robot has been a leader in systems management for IBM i. With batch job creation and scheduling at its core, the Robot Job Scheduling Solution reduces the opportunity for human error and helps you maintain service levels, automating even the biggest, most complex runbooks. Manage your job schedule with the Robot Job Scheduling Solution. Key features include:

  • LANSA Business users want new applications now. Market and regulatory pressures require faster application updates and delivery into production. Your IBM i developers may be approaching retirement, and you see no sure way to fill their positions with experienced developers. In addition, you may be caught between maintaining your existing applications and the uncertainty of moving to something new.

  • LANSAWhen it comes to creating your business applications, there are hundreds of coding platforms and programming languages to choose from. These options range from very complex traditional programming languages to Low-Code platforms where sometimes no traditional coding experience is needed. Download our whitepaper, The Power of Writing Code in a Low-Code Solution, and:

  • LANSASupply Chain is becoming increasingly complex and unpredictable. From raw materials for manufacturing to food supply chains, the journey from source to production to delivery to consumers is marred with inefficiencies, manual processes, shortages, recalls, counterfeits, and scandals. In this webinar, we discuss how:

  • The MC Resource Centers bring you the widest selection of white papers, trial software, and on-demand webcasts for you to choose from. >> Review the list of White Papers, Trial Software or On-Demand Webcast at the MC Press Resource Center. >> Add the items to yru Cart and complet he checkout process and submit

  • Profound Logic Have you been wondering about Node.js? Our free Node.js Webinar Series takes you from total beginner to creating a fully-functional IBM i Node.js business application.

  • SB Profound WC 5536Join us for this hour-long webcast that will explore:

  • Fortra IT managers hoping to find new IBM i talent are discovering that the pool of experienced RPG programmers and operators or administrators with intimate knowledge of the operating system and the applications that run on it is small. This begs the question: How will you manage the platform that supports such a big part of your business? This guide offers strategies and software suggestions to help you plan IT staffing and resources and smooth the transition after your AS/400 talent retires. Read on to learn: