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_CASEPositive 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_TYPEPROGRAM 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_CASEDeclare 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_TYPEPROGRAM Sample
VAR CONSTANT
    cMin         : UINT := 1;
    cMax         : UINT := 10;
END_VAR
VAR
    aObjects     : ARRAY[cMin..cMax] OF ST_Object;
    bInitDone    : BOOL;
    nForIdx      : UINT;
END_VARNegative 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_IFPositive 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_IFHandle 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_TYPEPROGRAM Sample 
VAR 
    eColorTrafficLight : E_ColorTrafficLight; // Used to handle the state of the traffic light
END_VARNegative sample:
CASE eColorTrafficLight OF      // NON COMPLIANT: enum value Green is not handled 
    E_ColorTrafficLight.Red: 
        SetLightRed(); 
    E_ColorTrafficLight.Yellow:    
        SetLightYellow();
END_CASEPositive sample:
CASE eColorTrafficLight OF      // COMPLIANT: all enum values are handled 
    E_ColorTrafficLight.Red: 
        SetLightRed(); 
    E_ColorTrafficLight.Yellow: 
        SetLightYellow(); 
    E_ColorTrafficLight.Green: 
        SetLightGreen(); 
END_CASEIF-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_VARNegative sample:
IF nTest < 10 THEN             // NON COMPLIANT: the ELSE should be used in any way
    nTest := 20;
ELSIF nTest > 20 THEN
    nTest := 10;
END_IFPositive 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_IFPositive 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_IFOrder 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