version 2003 (Modified)
It is difficult to state a definitive "good-programming" method, but we wish to stress the advantages of well-structured programs. The capacity for structured programming in 4D can be a great help.
The compilation of a well-structured database can yield much better results than the same effort performed in a poorly-designed one. For instance, if you write a generic method to manage n object methods, you will get higher quality results in both interpreted and compiled modes than in a situation where n object methods comprise n times the same set of statements.
In other words, the quality of the programming does have an impact on the quality of the compiled code.
With practice, you can gradually improve your 4D code. Frequent use of the compiler gives you corrective feedback that enables you to reach instinctively for the most efficient solution.
In the meantime, we can offer some advice and a few tricks that will save you time in performing simple, recurring tasks.
Using comments in code
Certain programming techniques may make your code less readable both for yourself or another person at a later time. Because of this, we encourage you to comment your methods with a lot of detail. In fact, while excessive comments have a tendency to slow down interpreted databases, they have absolutely no influence on the execution time in a compiled database.
Using compiler directives to optimize code
Compiler directives can help you speed up your code considerably. When typing variables on the basis of their use, the compiler uses the data type with the largest scope possible so as not to penalize you. For example, if you do not type the variable defined by the statement: Var:= 5, the compiler will type it as Real, even if it could be declared an Integer.
Numeric Variables
The compiler gives numeric variables (not typed by compiler directives) the default data type Real if the Preferences are not set to anything else. But calculations performed on a Real are slower than on a Longint. If you know that a numeric variable will always be an integer, it is to your advantage to declare it through the compiler directives C_INTEGER or C_LONGINT.
For example, it is good practice to declare your loop counters as Integers.
Some 4D functions return Integers (e.g., Character code, Int...). If you assign the result of one of these functions to an untyped variable of your database, the compiler types it as Real rather than as Integer. Remember to declare such variables with compiler directives whenever you are sure that they will not be used in a different context.
Here is a simple example of a function that returns a random value with a given range:
$0:=Mod(Random;($2-$1+1))+$1
It will always return an integer. Written this way, the compiler will type $0 as Real rather than Integer or Longint. It is preferable, therefore, to include a compiler directive in the method:
C_LONGINT($0) $0:=Mod(Random;($2-$1+1))+$1
The parameter returned by the method will take less space in memory and will be much faster.
Here is another example. Suppose you typed two variables as Longint:
C_LONGINT($var1;$var2)
and a third non-typed variable receives the sum of the other two variables:
$var3:=$var1+$var2.
The compiler will type the third variable, $var3, as Real. You will have to explicitly declare it as Longint if you want the result to be a long integer.
Note: Be careful with the computation mode in 4D. In compiled mode, it is not the data type of the variable that receives the calculation which determines the computation mode, but rather the data types of the operands.
In the following example, the calculation is based on long integers:
C_REAL($var3) C_LONGINT($var1;$var2) $var1:=2147483647 $var2:=1 $var3:=$var1+$var2
$var3 is equal to 2147483648 in both compiled mode and interpreted mode.
However, in this example:
C_REAL($var3) C_LONGINT($var1) $var1:=2147483647 $var3:=$var1+1
for optimization reasons, the compiler considers the value 1 as an integer. In compiled mode, $var3 is equal to 2147483648 because the calculation is based on Longints. In interpreted mode, $var3 is equal to 2147483648 because the calculation is based on Reals.
Buttons are a specific case of a Real that can be declared as Longint.
Strings
The default type assigned to alphanumeric variables is Text if the Preferences are not set to anything else. For example, if you write:
MyString:="Hello", MyString would be typed as a Text variable by the compiler.
If this variable will be processed frequently, it is worthwhile to declare it using C_STRING. Processing is much faster with String type variables, which have a defined maximum length, than with Text variables. Keep in mind the rules governing the behavior of this directive.
If you want to test the value of a character, make the comparison on its Character code value rather than on the character itself. The regular character comparison procedure considers all of the character's alternatives, such as diacritical marks.
Various observations
Two-dimensional arrays
The processing of two-dimensional arrays is better managed if the second dimension is larger than the first.
For example, an array declared as:
ARRAY INTEGER(Array;5;1000)
will be better managed than an array declared as:
ARRAY INTEGER(Array;1000;5)
Fields
Whenever you need to perform several calculations on a field, you can improve performance by storing the value of that field in a variable and performing your calculations on the variable rather than the field. Consider the following method:
Case of : ([Client]Dest="New York City") Transport:="Messenger" : ([Client]Dest="Puerto Rico") Transport:="Air mail" : ([Client]Dest="Overseas") Transport:="Express mail service" Else Transport:="Regular mail service" End case
This method will take longer to execute than if it were written:
$Dest:=[Client]Dest Case of : ($Dest="New York City") Transport:="Messenger" : ($Dest="Puerto Rico") Transport:="Air mail" : ($Dest="Overseas") Transport:="Express mail service" Else Transport:="Regular mail service" End case
Pointers
As is the case with fields, it is faster to work with variables than with dereferenced pointers. Whenever you need to perform several calculations on a variable referenced by a pointer, you can save time by storing the dereferenced pointer in a variable.
For example, suppose you use a pointer, MyPtr, to refer to a field or to a variable. Then, you want to perform a set of tests on the value referenced by MyPtr. You could write:
Case of : (MyPtr-> = 1) Sequence 1 : (MyPtr-> = 2) Sequence 2 End case
The set of tests would be performed faster if it were written:
Temp:=MyPtr-> Case of : (Temp = 1) Sequence 1 : (Temp = 2) Sequence 2 End case
Local variables
Use local variables wherever possible to structure you code. Using local variables has the following advantages:
Local variables take less space when used in a database. They are created when the method in which they are used is entered, and they are destroyed when the method finishes executing.
The code generated is optimized for local variables, especially for those of the type Longint. This is useful for counters in loops.
See Also
Error messages, Syntax Details, Typing Guide, Using Compiler Directives.