|
|
|
Download the Style Guide HERE!
Updated October 10, 2010
Here's some of the guidelines I use for my programming style. Some of these are well known, whereas some are rather new. I try to constantly add to this document as I think of new items to include.
It's important to note that may be multiple ways of doing something that could be correct. For instance, in the document below I will say that you should indent data structure field names from the data structure name to make them stand out, like the following:
d ApiError Ds inz qualified
d Bytes 10i 0 inz( %Size( ApiError ))
d BytesAvail 10i 0 inz
d ErrorID 7a inz
d Reserved 1a inz(x'00')
d MessageData 128a inz
In the above example, I have indented one space to the right for the data structure field names. One shop may adopt a standard for indenting one space to the right, and another shop may adopt the standard to indent 2 spaces. The important style guide to adopt is to indent the field names, but whether to indent one or two spaces is up to the shop. I find that indenting two spaces gives a better visual separation of the fields from the data structure name. Either way is correct, and one way should be adopted and used.
So in the above case, indenting the field names should be a style that is adopted. The number of spaces to indent is up to each individual shop. And just like this example, some other style ideas have multiple ways of accomplishing the same thing. So whereas I may give a style point, there may be another way of performing a similar task that is just as good as the one I presented. The important concept is to adopt a standard and stick to it, but not to get mired down in opinions on the different ways to accomplish the same standard. Evaluate the options, and then choose one.
There are basically 3 types of styles that I am presenting:
Now, there are some standards that have multiple ways of being implemented, but one way is technically wrong, and is not just a matter of opinion. For instance, as a standard, I do not allow programmers to use bif's such as %eof() WITHOUT expressly using them with a filename.
INCORRECT CODING STANDARD chain CustomerNumber Customer; if %eof(); leave; endif; CORRECT CODING STANDARD chain CustomerNumber Customer; if %eof( Customer ); leave; endif;
It's important to note that BOTH versions of this code are functionally identical and correct. The problem with the first example is that the %eof() is a "land mine" that is waiting to blow up on an unsuspecting programmer who makes changes to this program in the future. %eof() without a file name specified simply states that the program should check the end of file flag for the last file that was processed.
If a program change was made in the future such as this:
chain CustomerNumber Customer; if OrderStatus = OpenOrder; chain OrderNumber Order; endif; if %eof(); leave; endif;
Now the program will NOT operate like it did before. The %eof() bif will be checking the end of file flag for the ORDER file any time the OrderStatus = OpenOrder. The big problem with this is that the programmer may have done nothing wrong in making the coding changes. The code he added could be 100% correct. Yet the changes caused program problems because of the way the program was originally written.
Therefore, even though a programmer is allowed to code either way in the RPG language, a standard should be developed that forbids the use of %eof() without specifying a filename.
And now, onto the style guide.
Free Format RPG
Programmers should be performing all new development in free format RPG. Free format RPG is more self-documenting, versus fixed format RPG which can be very cryptic. You can also do much more in much fewer lines of code in free format. Fixed format RPG was great back in the day, and when I say back in the day, I'm referring to the mid-1990's. But it simply can not compare to the productivity gains a programmer can experience with free-format.
Pointer and Data Structure Names
A naming standard that I have begun to follow in the past few years, and in which appears to work out really well, is to attach an uppercase "DS" to data structure names, and "PTR" to pointer field names. This allows a field to be easily identifiable in the code as either a data structure or pointer. Previously, I had used an underscore (name space) to indentify these fields. ( ex: Inventory_DS ) But I have always disliked this approach because I preferred to only use underscores to indentify a prototype / function / procedure call. The new naming scheme allows me to easily indentify special names such as pointers and data structures, without any special characters in the name.
d HostEntryPTR S *
d HostEntryDS DS Based( HostEntryPTR )
d Name 20A
d Aliases 10A
Use the += and -= Operators
One of the nagging problems we still have with our source editors, whether it be PDM, Websphere, or rdI, is that we have an 80 column limitation per line of code. By using these operators, we can greatly cut down on the length of code and code faster.
HtmlOutputBuffer = HtmlOutputBuffer + CarriageReturn + LineFeed; becomes HtmlOutputBuffer += CarriageReturn + LineFeed; LineCounter = LineCounter + 1; becomes LineCounter += 1;
Logical NOT, OR, or AND
If you use the logical word NOT, OR, or AND, capitalize the word so that it
stands out from the rest of the expression.
if not %found( Customer );
becomes
if NOT %found( Customer );
if TotalPay > MyPay and TotalHours < MyHours;
becomes
if TotalPay > MyPay AND TotalHours < MyHours;
KeyLists
Avoid using program described key lists in your program. Instead, use an
ad-hoc key list. Ad-hoc key lists are immediately clear to a programmer as to what is being
utilized to perform a file positioning function.
For example, instead of:
setll OrdKey OrderD;
use
setll ( OrderNumber : ItemNumber ) OrderD;
If you utilize key lists, then someone maintaining the program must search and locate where the key list is described. In the example above, it would not be clear on the setll statement that two fields are used in the key to position the file. By using the ad-hoc key list, someone maintaining the program knows immediately what is being used to position the file. This is particularly helpful when partial keys are used, in which a program may have several key lists described to handle different file positioning operations.
For example:
setll OrdKey1 OrderD;
reade OrdKey OrderD;
In the above example, we simply know that a different key list is being used to position the file than the one used to read the file. Or is it? These two key fields could be described identically. Or they could be different. Here's how the code looks in ad-hoc form.
setll ( OrderNumber : ItemNumber );
reade ( OrderNumber ) OrderD;
In the ad-hoc version, we can clearly tell that we are positioning the file based upon the order number and inventory number fields, then we are reading the file as long as the order number in the OrderD file matches our order number field. This code is self documenting, and doesn't require research in order to see what fields are used in each key list.
Even using the %kds bif , which allows you to specify a data structure and the number of fields that are to be used in the key, is not as clear as the ad-hoc method.
setll %kds( OrderKey : 2 ) OrderD;
versus
setll ( OrderKey.OrderNumber : OrderKey.ItemNumber ) OrderD;
End OpCode
Always qualify an end opcode with its appropriate suffix. For
example, use endif, enddo, endsl, etc. instead of simply "end".
This not only makes your code more readable, but when combined with line
indentation, helps you visually make sure you have the correct code within the
the correct end statement group. It also helps people maintaining your
code determine if they are within an IF group, or a DO group, etc.
Code Indentation
Indent your code two spaces for each conditional statement.
Code indentation allows conditioning groups to be visually separated. One space
is usually not enough to allow a section to visually stand out. More than
three spaces will cause nested groups to bang against the 80 column barrier too
frequently. Upon each end(xx) opcode, indent the code back to the left 2 spaces.
The end opcode should align vertically with its conditioning operation.
if CustomClassCode = GoldClassMember; BonusPointsEarned = 0; setll ( CustomerNumber ) BonusFile; reade (Customer Number ) BonusFile; dow NOT %eof( BonusFile ); BonusPointsEarned += PointTotal; reade( CustomerNumber ) BonusFile; enddo; endif;
Special Characters In Field Names
Avoid using special characters for field names:
Back when we were severely restricted on the length of a field name, it made sense to use special characters in some cases. Special characters today simply clutter up the field name, and make the source as a whole illegible. You can use long field names, as well as qualified data structures, in order to make meaningful field names. Both of these negate any need for special characters.
if YTD$ > @YTD$;
@SAVECD = CLASSCD
Endif;
becomes
if CustomerSales > GoldMemberSales;
CustomerClassCode = GoldMemberClassCode;
endif
Avoid using the underscore character "_" in field names. The place I use an underscore character is for adding the service program name prefix to a function.
/Copy Members
All prototypes, common constants, and data structures which can be used in multiple programs should be placed in their own copy members.
Use self-contained compiler directives to prevent the source statements in a copy member from being copied twice by the compiler. In other words, each copy member should be able to keep itself from being copied more than once into the same member at compile time. We don't want to have to put this logic in each program which uses the copy member. The only line of code that should be required in a program which uses the copy member is the /copy directive.
The following is an example of a source member for the ApiError data
structure, which is an error data structure used by many of the IBM api's.
Since this data structure is common to most of the api's, we should create
a copy member for it, and utilize the /copy directive to place the data
structure into our program. We should not hard code this data structure in each
program.
/if defined(ApiError)
/eof
/endif
/define ApiError
d ApiError Ds inz qualified
d Bytes 10i 0 inz( %Size( ApiError ))
d BytesAvail 10i 0 inz
d ErrorID 7a inz
d Reserved 1a inz(x'00')
d MessageData 128a inz
*Note - indenting of the compiler directives is not currently supported. Although you can indent them, they cause errors if one copy member attempts to include an embedded copy member with line indentation.
If we named this member APIERROR, then all we would have to do to use the ApiError data structure in any program is to perform the following statement:
/copy *libl/QRPGLESRC, ApiError
The copy member itself, by using the compiler directives "if defined" and "define", will prevent this data structure from being copied more than once into a program.
Use Naming Standards for Procedures / Service Programs / ProtoTypes
It can be very frustrating to come across some code in a program that calls a function you are unfamiliar with, and you have no idea where the source for the function exists. For example, suppose we have the following code in a program we are maintaining:
RetrieveYtdSales( Account );
A problem we immediately have is locating the source for this function. It can take some time to do locate the source unless we give ourselves a naming standard to help us out.
The naming standard I use is:
.
By following this standard, the above function would be renamed with a prefix
(Name Space).
For example:
Customer_RetrieveYtdSales( Account );
Now I know everything I need to know about this function. I know that the function resides in a service program called Customer_s. The member Customer_s will contain the program code for the function. The binder source for the service program will be in a member called Customer_s in QSRVSRC file. The prototypes will be located in a member called Customer_p.
Not only does this naming standard make a persons life easier, but it helps avoid procedure naming collisions. For example, without a prefix on our function, we might find that we have 3 different procedures on our system that are named RetrieveYtdSales.
So, by prefixing the service program to the function name, we can create
meaningful function names that avoid naming collisions between application
departments, and all while gaining the immediate benefit of knowing where everything
is located.
Use Bif's Instead Of Custom Code
Bifs (built in functions) are universally known to RPG programmers. If bif is available, use them instead of custom code, which is not universally known.
Make Use Of The Latest Date, Time And
TimeStamp bif's
RPGIV and
RPG free are very robust in date, time and timestamp handling. Bif's allow
you to quickly add some days to a date, some minutes or hours to a time, or to
quickly find the duration between two times or timestamps. Use the bif's
that come with rpg instead of using your own custom code. For one, the bif's are
tested by IBM and work. Your code may not work, and certainly must be
tested. Second, these bif's handle
complex date functions, like leap years, without blinking an eye and in one line
of code.
For example, don't use custom code to retrieve the system date. Simply use the %date built-in function;
SystemDate = %date;
Want to know how many hours lapsed between a certain timestamp time/date and now?
HoursLapsed = %diff( %TimeStamp : StartDateTime : *hours );
Anyone who maintains the above code can easily tell what the code is doing. And it's doing it in one line of code, not a 400 line subroutine. Ok, you could probably do it in less than 400 lines, but you couldn't do it in one.
Parenthesis and Parameter Spacing
Space after a beginning parenthesis, before a closing parenthesis, and around the parameter separator ":" in order to make your code more readable.
chain (CustomerNumber) Customer; becomes chain ( CustomerNumber ) Customer; ---------------------------------------------------------- setll (CustomerNumber:InventoryNumber) OrderDetail04; becomes setll ( CustomerNumber : InventoryNumber ) OrderDetail04; ---------------------------------------------------------- ArrayIndex = %int(%subst(%char(Amount):1:1)); becomes ArrayIndex = %int( %subst( %char( Amount ) : 1 : 1 ) );
Use Upper and Lower Case In Field and Procedure Names
By utilizing upper and lower case letters in field names, you can make multiple words of a field name stand out. (Known also as camel hump or camel case notation)
For example, annualcustomersales becomes AnnualCustomerSales
gettotalpartsales( partnumber );
becomes
GetTotalPartSales( PartNumber );
Long Field Names
Don't restrict your field name lengths. Make your field names long enough to be self-documenting. They should not be so long as to be unwieldy, and constantly cause a line of code to bump up against the 80 column barrier that currently exists. Otherwise, make them long enough to be meaningful.
For example, TOTISAL is useless as far as a field name. Does this field describe the total item sales? Does it described the "total inventory scrapped in Alabama"? Is it a field that says the "tote is allocated"? How about "Toenail Trimmer Is Available" ?
How about this field: TotalItemSales I'm fairly sure this field is used for the total item sales. It almost certainly should have nothing to do with toenail trimmers.
Use Qualified Data Structures
Qualified data structures have been around for a while, and they certainly are the wave of the future. They are a great way to handle "work fields" in a program. The old method of handling work fields from a file was to rename all of the fields in the file to be prefixed with "WK" or "@" or some other special character. ( ie: INITEM# would be renamed to WKITEM#)
By using qualified data structures, you can quickly create the work fields for a file in one line of code. (by defining the data structure using the LIKEREC keyword)
You can move the fields of the data structure into the files data structure, or vice-versa, by simply coding a single eval-corr operation.
eval-corr InventoryWorkFieldsDS = InventoryDS;
Use Binding Directories Instead Of The BNDSRVPGM KeyWord At Compile Time
Service programs should be placed in a binding directory. If a program
needs to execute a procedure or function located inside of a service program, it
only needs to make sure that the BNDDIR control options is specified. For
example, if a procedure we want to call is located in a service program that has
been added to the CF binding directory, we can simply specify the following in
our program:
h BndDir( 'CF' )
If we need to access more than one binding directory, we can separate the binding directories with a colon.
h BndDir( 'BndDir1' : 'BndDir2' )
Someone who maintains a program that specifies the BndDir keyword can simply execute a standard compile option against the program after they have completed their changes. But if BNDSRVPGM is used at compile time, any future compiles will have to specify the list of service programs on the BNDSRVPGM keyword on the compile command. This is not only a manually intensive process, but each service program must be documented inside the program comments so that someone else in the future knows how to add these service programs at compile time. It is a lot of work to find the correct service program for a given procedure, and it's a nightmare if there are a lot of them.
Get Rid Of Un-Needed OpCodes In Free Format
OpCodes like eval and callp are not usually needed, and simply clutter up a line of code.
Eval NetPay = GrossPay - TotalTaxes;
becomes
NetPay = GrossPay - TotalTaxes;
callp CurrencyToWords( CheckAmount );
becomes
CurrencyToWords( CheckAmount );
Use Free Format SQL In Your RPGLESQL Programs
There was probably no code that was downright uglier than fixed format RPGSQLE code. Convert any fixed format SQL code to free format as you maintain an existing SQLRPGLE programs. The retina's of anyone who will read the code after you will thank you.
Convert RPG400 (RPGIII) Programs To ILE
There is nothing that is difficult in this task. You simply run the CVTRPGSRC command over an existing RPG400 source member. Have the converted RPGIV source placed into QRPGLESRC file. It's as simple as that, and you can begin to immediately take advantage of all of the modern RPG language enhancements, such as built in functions and free format code.
RPG400 hasn't been updated in over a decade, so if you are stuck programming in this mode, you truly are riding a dinosaur. Given how easy it is to move to the ILE environment in OPM compatibility mode, there's no excuse to be stuck coding in a language that was rendered dead in 1995.
Use The Const Keyword On Procedure Parameters
The Const keyword does a few things. First, it ensures that our field will not be changed during the call to the procedure.
Second, since the field can not be changed in the procedure, this means that we don't have to specify a variable on the procedure call. We can use literals and string concatenations.
Do Not Use Indicators
Free format RPG is an indicator-less language. In fact, free format RPG has built in functions that not only negate the need for indicators, but are self-documenting. For example, a fixed format chain operation might look like this:
C
CusNbr CHAIN CUSTOMER
50
C *in50
IFEQ *OFF
do stuff
C
ENDIF
The free format version would be:
chain ( CusNbr ) Customer;
if %found( Customer ) ;
do stuff;
endif;
The "not found" indicator on a chain has been replaced with the %found bif. The end of file indicator on the read statements is now %eof. Error and status indicators have the built in functions %error and %status. You can also specify the logical "NOT" when specifying these built in functions.
For example:
if NOT %found( Customer );
or
dow NOT %eof( Customer );
You can also use the INDDA keyword to map display file indicators to field names, so that the program code only references the field names.
Manipulate Character Fields With String Concatenation, Not Data Structures
Use string concatenation to change a fields value. Do not use individual fields within a data structure to change a field.
Basically, you want the value of a field to change on a line of code. You don't want someone who maintains the program behind you to have to surf d-spec's to find the subfield in a data structure, and then understand that a change to a field has also changed the value of another field.
Use Lower Case Letters For The Different Program Specifications ( Column 6 )
The specifications h, d, f, c, p, b, etc. should be in lower case. This
will help visually separate the specification from filenames and other
definitions.
Make Column 7 a Blank Whenever You Can
The specifications h and d should contain a blank in column 7 in order to
make any directives or definitions stand out.
HDFTACTGRP(*NO) BNDDIR('CF')
becomes
h DftActGrp( *no ) BndDir( 'CF' )
Capitalize The First Letter Of File Names To Make The Stand Out
Since column 7 can not be blank for a file spec, use a lower case "f" of the spec and capitalize the first letter of the file name. This allows the file name to stand out better.
Instead of:
FINVENTORY
or
finventory
do this:
fInventory
Indent Data Structure Fields From The DS Name
Indent the fields within a data Structure by one from the Data Structure name. This allows the visual grouping of the fields within a data structure to be seen.
d ApiError ds inz qualified
d Bytes 10i 0 inz( %Size( ApiError ))
d BytesAvail 10i 0 inz
d ErrorID 7a inz
d Reserved 1a inz(x'00')
d MessageData 128a inz
BECOMES
d ApiError ds inz qualified
d Bytes 10i 0 inz( %Size( ApiError ))
d BytesAvail 10i 0 inz
d ErrorID 7a inz
d Reserved 1a inz(x'00')
d MessageData 128a inz
Comments And Program Documentation
Place a documentation section at the top of every program, subroutine, and procedure. This documentation should include:
Well written free format RPG code requires little in-line documentation or comments, as the code itself should be self-documenting. If a comment is needed, make sure the comment starts with "//". Do not clutter the program source up with comments that simply state the obvious. Here's an example of a useless comment:
// chain to the vendor file
chain ( VendorNumber ) Vendor;
A person maintaining this code can just as quickly and easily tell that we are chaining to the vendor file by looking at the code rather than the comment.
Do include comments that explain a section of code that is not going to be self-evident as to what it is doing, or which is not quickly understood. For example, the following line of code is more complex because of the number of functions that calling other functions. Although a person could study the code and figure it out, it is not immediately evident as to whats going on.
ArrayIndex = %int( %subst( %char( Amount ) : 1 : 1 ) );
A comment before this line can help state what is being accomplished here, and do so much more quickly.
// Set the array index variable to the first
non-zero number in the Amount field
ArrayIndex = %int( %subst( %char( Amount )
: 1 : 1 ) );
Comments should be placed before the code. Do not place comments at the end of a line of code, even though it is allowed This clutters up the code, making it more difficult to read, and you will likely not have enough room to put any sort of meaningful comment there anyway.
Closing All Open Files In A Program Or Procedure
Use the Close opcode with *all specified to close all open files in the program or procedure.
close *all
This is much preferred over comparing each file to see if its open.
if %open (File1 );
close File1;
endif;
if %open (File2 );
close File2;
endif;
Not only is more code involved, but each file must be checked and closed separately. Also, any new files added to the program will also have to have a close section added for it. The close *all eliminates all of this unnecessary code overhead.
Use Structured Loops That Have Meaningful Loop Processing
Loops should have meaningful loop processing. Here's an example of how not to code a loop:
dow 1=1;
read Customer;
if %eof( Customer )
leave;
endif;
some code...
enddo;
Notice that our loop is conditioned as dow 1=1. This means that the loop should never end, since one will always be equal to one. Then, in the middle of the loop, we state a reason for exiting the loop. This is a poor method, as the loop condition itself should state why we are looping, and when we end the loop.
read Customer;
dow NOT %eof( Customer );
some code...
read Customer;
enddo;
Notice how the above method is much better, as it describes why we are looping, and when we should stop looping. We will perform the loop as long as we are not at the end of the customer file. We do not require any middle of the loop exit strategy, such as using the leave op code. There may be cases of where using a "leave" or "iter" opcode is proper, but if we can code in a method that doesn't require it, then that is all the better.
Constant Values
Constant values should be defined as constants in the "D" specs.
Define true constants in the "D" specs, and make a copy member for them if they can be utilized in more than one program.
HtmlString += x'15';
BECOMES
d LineFeed c x'15'
/free
HtmlString += LineFeed;
Notice that the first line of code isn't very self documenting as to what the hex 15 value is. By defining the constant value in the "D" spec and giving it a field name, the code now documents itself that we are adding a line feed character to the end of the HtmlString field.
Vertically Align Parameters And Key Fields If More Than One Line Of Code Is Needed
If the parameters or key fields of a statement cause the line to overflow, then use only one line per parameter / key field, and align the fields up.
chain ( stock_ds.stgenus : stock_ds.stspecies : stock_ds.sttype :
stock_ds.stsize) inventory inv1_ds;
BECOMES
chain ( stock_ds.stgenus
: stock_ds.stspecies
: stock_ds.sttype
: stock_ds.stsize
) inventory inv1_ds;
This method structures the parameter so that they become a true vertical list, which is easily read. Also, note how we space the closing parenthesis out beyond the longest parameter name. This is so that the file and data structure "tail behind" the parameters just like they would in a single parameter list, which allows them to stand out. Otherwise, they would get hidden in the parameter list.
Correctly Coding Never-Ending Loops
The correct way to code a never ending loop is to first recognize that there is no such thing as a never ending loop. Program loops must end, whether they like it or not, when the subsystem the job is running in ends, or when the system is shut down. There is a very easy way to code for never ending loops that end when a shutdown request is made, and that is to use the %shtdn bif.
dow 1=1;
do stuff...
enddo;
BECOMES
dow NOT %shtdn;
do stuff...
enddo;
There's two flaming differences between the two pieces of code. The first piece of code is not self documenting, and the program will end abnormally if the subsystem or system attempts to shut it down. It will end, but it will end abnormally.
The second code snippet is self documenting, and will allow the program to end normally if a shutdown request is encountered.
Allow Jobs To End Normally Upon A ShutDown Request
Programs should check for a shutdown request ( %shtdn ) so that they can end quickly and normally. This is especially true of auto-start jobs, server jobs, and long running batch jobs.
Varying Length Fields
Utilize varying field lengths, especially for longer field lengths. You wont need to trim the varying lenth fields n order to move their contents into another field. Operations, such as concantenation, scanning, lookup, etc. perform much faster on varying length fields. RPGIV now supports 16MB field lengths, and performance can seriously be impacted if you attempt to trim, scan, or lookup a large field that is not varying in length. This is especially true of large fields that contain little data.
Embrace a Modular Approach, With Re-Usable Code
Program maintenance is normally not one of the most desirable job functions of a programmer. But when you break down where that mentality comes from, it's normally because what should be a quick little change can turn into a research project and overall headache. When you begin to write your programs with a modular and structured approach, program maintenance is quick and easy. This is because the code that needs to be changed is isolated in a structured program. Instead of dealing with a 2000 line behemoth program, you may be dealing with a 20 line procedure. It's very easy to visually digest what a 20 line procedure is doing, and where a change needs to be made. It's also very easy to test.
Returning Large Fields In Functions
There is a tremendous performance impact in returning large data fields in functions. For instance, consider the following two examples:
EXAMPLE 1
p InsertText b
d InsertText pi
d TextString a varying( 4 ) len( 1000000 )
/free
TextString += 'NewText';
return;
/end-free
p e
==================================
EXAMPLE 2
p InsertText b
d InsertText pi a varying( 4 ) len( 1000000 )
d ReturnString s a varying( 4 ) len( 1000000 )
/free
ReturnString = 'NewText';
return ReturnString;
/end-free
p e
Each of the 2 above subprocedures do the same thing, they append the text string 'NewText' to the end of the data in a field. In example 1, we would call the subprocedure in this way:
InsertText( StringData );
In example 2, we would call it as a function:
StringData += InsertText();
Both ways perform the same thing. But both ways do not perform the same way. In a test scenario, I called the subprocedure in example 1 100,000 times on an IBM i515 express, and it took a total of 10.6 CPU seconds to process, or 106 microseconds per call. That's blazingly fast considering we're moving a 1 MB data field back and forth to the procedure.
I then called the example 2 function 100,000 times, and it took a total of 116 CPU seconds to process. That is roughly 11 times the amount of processing time to accomplish the same thing, a startling difference.
When dealing with large field strings, such as building Html / xHtml strings to output to CGI or XML data, the amount of processing time needed to build up the string thru many calls to subprocedures can make a big difference in both response times and CPU utilization.
So the lesson here is that, when dealing with large amounts of data passed to a subprocedure, it is far better performance-wise to change the data passed to the subprocedure rather than passing the changed data back as a function.