Loops and conditions

Topics:

  1. CASE statement only with enumeration instance [++]
  2. Declare limits of a loop as a constant [+]
  3. Handle all enumeration values of the enumeration variables in CASE statement [+]
  4. IF-ELSIF statements with ELSE branch [+]
  5. Order of IF conditions [+]

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.

Also note the following recommendations:

Static Analysis:

Thematically recommended Static Analysis rules:

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