Loops and conditions
Topics:
CASE statement only with enumeration instance
In a CASE statement, an enumeration instance including corresponding enumerators should be used to define the state machine, instead of "magic numbers". This makes the values of the state machine self-speaking and no "magic numbers" are used.
Also note the following topics of the programming conventions:
Negative sample:
PROGRAM Sample_neg
VAR
nState : INT; // Used to operate the sample state machine
END_VAR
// NON COMPLIANT: Integer variable and "magic values" are used to define state machine
CASE nState OF
0: F_DoSomethingUsefulHere();
1: F_DoSomethingUsefulHere();
2: F_DoSomethingUsefulHere();
ELSE
F_DoSomethingUsefulHere();
END_CASE
Positive sample:
// Enumeration for the positive sample
{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_SampleState :
(
Entry, // Entrance part of a state
Work, // Action part of a state
Finish // Exit part of a state
);
END_TYPE
PROGRAM Sample_pos
VAR
eState : E_SampleState; // Used to operate the sample state machine
END_VAR
// COMPLIANT: Enum instance and enum values are used to define state machine
CASE eState OF
E_SampleState.Entry:
F_DoSomethingUsefulHere();
E_SampleState.Work:
F_DoSomethingUsefulHere();
E_SampleState.Finish:
F_DoSomethingUsefulHere();
END_CASE
Declare limits of a loop as a constant
Constant variables should be used as limits of a loop. If an array is accessed within the loop, the array should be declared using the same constant variables.
- Lower limit of the array = lower limit of the loop = constant variable 1
- Upper limit of the array = upper limit of the loop = constant variable 2
Also note the following recommendations:
Static Analysis:
Thematically recommended Static Analysis rules:
- SA0072: Invalid uses of counter variable
- SA0080: Loop index variable for array index exceeds array range
- SA0081: Upper limit is not a constant
General program elements for the following samples:
TYPE ST_Object :
STRUCT
sName : STRING;
nID : UINT;
END_STRUCT
END_TYPE
PROGRAM Sample
VAR CONSTANT
cMin : UINT := 1;
cMax : UINT := 10;
END_VAR
VAR
aObjects : ARRAY[cMin..cMax] OF ST_Object;
bInitDone : BOOL;
nForIdx : UINT;
END_VAR
Negative sample:
VAR
nUpperBorder : UINT;
END_VAR
// Initialize objects
IF NOT bInitDone THEN
nUpperBorder := cMin;
FOR nForIdx := nUpperBorder TO 10 BY 1 DO
aObjects[nForIdx].sName := CONCAT('Object_', UDINT_TO_STRING(nForIdx));
aObjects[nForIdx].nID := nForIdx;
END_FOR
bInitDone := TRUE;
END_IF
Positive sample:
// Initialize objects
IF NOT bInitDone THEN
FOR nForIdx := cMin TO cMax BY 1 DO
aObjects[nForIdx].sName := CONCAT('Object_', UDINT_TO_STRING(nForIdx));
aObjects[nForIdx].nID := nForIdx;
END_FOR
bInitDone := TRUE;
END_IF
Handle all enumeration values of the enumeration variables in CASE statement
In a CASE statement you should handle all enumeration values of the enumeration variables. If this does not make sense for a large number of identical or unused enumeration values, an ELSE branch can be used for these cases.
Static Analysis:
Verify using the following Static Analysis rules:
General program elements for the following samples:
// Enumeration for all samples in this rule
{attribute 'qualified_only'}
{attribute 'strict'}
TYPE E_ColorTrafficLight :
(
Red := 0,
Yellow,
Green
);
END_TYPE
PROGRAM Sample
VAR
eColorTrafficLight : E_ColorTrafficLight; // Used to handle the state of the traffic light
END_VAR
Negative sample:
CASE eColorTrafficLight OF // NON COMPLIANT: enum value Green is not handled
E_ColorTrafficLight.Red:
SetLightRed();
E_ColorTrafficLight.Yellow:
SetLightYellow();
END_CASE
Positive sample:
CASE eColorTrafficLight OF // COMPLIANT: all enum values are handled
E_ColorTrafficLight.Red:
SetLightRed();
E_ColorTrafficLight.Yellow:
SetLightYellow();
E_ColorTrafficLight.Green:
SetLightGreen();
END_CASE
IF-ELSIF statements with ELSE branch
For safety reasons, it is advisable to add ELSE to an IF statement, if ELSIF is available. An ELSIF statement should be followed by ELSE, in order to demonstrate that all possible cases have been considered. If no statements are planned for the ELSE branch, a semicolon should follow after ELSE. During acceptance/commissioning of the program, a comment can be used to explain that the ELSE case was considered and why there are no statements for it.
General program elements for the following samples:
PROGRAM Sample
VAR
nTest : INT:= 10; // Test value for sample
END_VAR
Negative sample:
IF nTest < 10 THEN // NON COMPLIANT: the ELSE should be used in any way
nTest := 20;
ELSIF nTest > 20 THEN
nTest := 10;
END_IF
Positive sample 1:
IF nTest < 10 THEN // COMPLIANT with any action in ELSE
nTest := 20;
ELSIF nTest > 20 THEN
nTest := 10;
ELSE
F_DoSomethingUsefulHere(); // it's just a sample
END_IF
Positive sample 2:
IF nTest < 10 THEN // COMPLIANT with a comment in ELSE
nTest := 20;
ELSIF nTest > 20 THEN
nTest := 10;
ELSE
;(* No action needed for the case that nTest is >= 10 and <= 20 *)
END_IF
Order of IF conditions
Arrange the order of IF/ELSIF/ELSE conditions according to the probability of occurrence. Thus, the most likely case should be handled first. This improves both readability and, in some cases, performance because non-matching queries are processed less frequently.
Positive sample:
IF SUCCEEDED(hrErrorCode) THEN
IF nReqIdx < cIdxMax THEN // requested index in range
; // handle request
ELSIF nReqIdx = cIdxMax THEN // requested index in range but at upper limit
; // handle request
ELSE
; // add error output (error caused by invalid index)
END_IF
ELSE
; // add error handling (error in previous step)
END_IF