Computers: Universe: Basic Good Practice

Efficiency

'Go faster' operators

Always use VAR1 += VAR2 in place of VAR1 = VAR1 + VAR2: it is appreciably faster. Favour -= and := similarly.

For next loops

Don't use expressions as terminating values in FOR/NEXT loops: use only constants or variables. For instance, rewrite:

FOR RECORD.NUMBER = 1 TO DCOUNT(RECORD.LIST, @FM)
   * Do something
NEXT RECORD.NUMBER

...as...

NUMBER.OF.RECORDS = DCOUNT(RECORD.LIST, @FM)
FOR RECORD.NUMBER = 1 TO NUMBER.OF.RECORDS
   * Do something
NEXT RECORD.NUMBER

This prevents UniVerse recalculating the terminating value every time round the loop. If you cannot restructure your FOR/NEXT loop because you are relying on the recalculation, replace the FOR/NEXT loop with a LOOP/REPEAT loop. It may be faster, but it will definitely be more readable.

CASE constructs

If your CASE conditions are mutually exclusive, their order will make no difference to the behaviour of your code, but may make a difference to its performance. Even where the conditions are not mutually exclusive, they can sometimes be rearranged without affecting the logic of the program, to increase its speed.

This is because UniVerse evaluates each condition in turn until it encounters one which is true: the more often it finds that the first CASE is true, the fewer comparisons it need make in total. (See Basic Conditions for a detailed example).

Testing for existence

The quickest way to test a record for existence (assuming you don't need to read any of the information it contains) is to use READV to read field 0 thus:

READV DUMMY FROM VOC.FILE, 'RECORD.BEING.TESTED', 0 ELSE PRINT "It's missing!"

Looping through dynamic arrays

Never loop through a dynamic array using a subscript to address each field. Addressing individual fields by subscript within a dynamic array takes time: the higher the subscript, the more time. Use LOOP/REPEAT around a REMOVE statement instead. REMOVE is specifically designed to remove consecutive dynamic array fields without searching from the beginning of an array to resolve a subscript each time.

Creating select lists

Use the UniVerse Basic SELECT statement instead of EXECUTE 'SELECT...' wherever possible. If the reason you did not use it originally is because you wanted a sorted list, ask yourself if your program really needs to sort the list: quite often batch programs which need not sort are given sorted lists merely because it makes the display of keys 'currently being processed' neater: this is an expensive luxury.

If you already have a dynamic array, and you wish to turn it into a select list, you need NOT write it to &SAVEDLISTS& and then issue an EXECUTE 'GET.LIST...'. You may sometimes find this approach taken in early UniVerse Basic (or PICK or INFO Basic) because it was at one time the only available method. You may now, though, simply say SELECT DYNAMIC.ARRAY: the select statement, when followed by a dynamic array, will turn the array into a select list, and save a great deal of time.

Arrays

Use dimensioned rather than dynamic arrays unless there is a good reason not to: the elements of a dimensioned array are far quicker to address than the fields of a dynamic array.

If you need a dynamic array, keep it one-dimensional by using only field marks: not value or subvalue marks. Most of the things which dynamic arrays are good at (being searched, sorted, grown, shrunk and traversed) are made difficult or possible once it contains mixed markers. Think of dynamic arrays as a 'list' type.

Growing dynamic arrays

For some reason...

DYNAMIC.ARRAY := @FM : 'NEWFIELD'

...is generally considerably faster than...

DYNAMIC.ARRAY<-1> = 'NEWFIELD'

...despite the fact that they are nearly identical. Both add a field to a dynamic array. The <-1> syntax is slightly neater, as if DYNAMIC.ARRAY is empty above, the := @FM syntax will add an empty field before the new field. However, this can easily be dealt with by writing...

DYNAMIC.ARRAY = DYNAMIC.ARRAY[2, LEN(DYNAMIC.ARRAY) - 1]

The time this takes to execute is usually far less than the time gained by using := @FM, paricularly when building a long dynamic array in a loop. You need only trim the first character once.

Removing empty fields

The CONVERT statement changes all the occurences of a character in a string to another character. If the 'from' and 'to' strings contain more than one character, the first character of the first string is converted to the first character of the second, and so on. The TRIM function not only trims leading and trailing spaces from a string, but replaces all multiply occurring spaces within the string with single spaces. You can use CONVERT and TRIM together to remove empty fields from a dynamic array as follows:

CONVERT @FM TO ' ' IN DYNAMIC.ARRAY
DYNAMIC.ARRAY = TRIM(DYNAMIC.ARRAY)
CONVERT ' ' TO @FM IN DYNAMIC.ARRAY

This relies on the fact that an empty field is represented by two field marks next to each other. When they are converted to spaces and trimmed, they are replaced with a single space.

Note that this code assumes that your fields do not already contain spaces. If they do, you must find at least one character which your string could not contain (which we'll represent by the variable SAFE.CHARACTER) and safe gaurd the 'real' spaces as follows:

CONVERT ' ' : @FM TO SAFE.CHARACTER : ' ' IN DYNAMIC.ARRAY
DYNAMIC.ARRAY = TRIM(DYNAMIC.ARRAY)
CONVERT SAFE.CHARACTER : ' ' TO ' ' : @FM IN DYNAMIC.ARRAY

Cleaning strings

To 'clean' a string of any characters which do not want, first define a variable which contains all the characters which you do want. For instance, if the surnames in your system must be purely alphabetic, try the following:

LEGAL.CHARACTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
JUNK.CHARACTERS = SURNAME
CONVERT LEGAL.CHARACTERS TO '' IN JUNK.CHARACTERS ; * So anything left is junk...
CONVERT JUNK.CHARACTERS TO '' IN SURNAME ; * ...which can be got rid of.

Quality

FOR/NEXT counter variables

Never rely on the value of a counter variable outside a FOR/NEXT loop. It is undefined. Even if you find that it reliably takes a particular and useful value, that could change with the next release of the UniVerse Basic compiler.

Subroutine parameters

Don't use subroutine parameters as 'working variables' within your subroutine. Assign them new values only if it is clearly the purpose of the subroutine to do so.

When calling a subroutine, put brackets around naked variables if you do not wish them to be changed by the subroutine you are calling: this converts them to 'expressions' and is sufficient to prevent them being changed even if the subroutine you are calling attempts to change them.

Record locking

1. Never create, update, or delete a record without first obtaining an update lock with READU, MATREADU or READVU.

2. Never assume that a record has remained unchanged since you read it unless you obtained an update lock (READU) or shared lock (READL) which you still hold.

3. Never keep records locked longer than you have to.