Table of Contents

Coding Guidelines

When you implement your own applications or libraries with the Zeugwerk Framework, following these guidelines keeps your code consistent with the framework and works well with our tooling and CI. The conventions are aligned with modern practice, similar to the Google C# Style Guide.

Cheatsheet

  • Names of function_blocks, function, methods, enumerations, public fields, enum fields, public properties, var_input, var_output and namespaces (e.g. everything that is available in the external Api): PascalCase.
  • Names of local variables (e.g. var_input, var, var_output of methods), parameters: camelCase, prefix: no, suffix: no.
  • Names of public member variables (e.g. variables in VAR block of PRG or VAR_INPUT/OUTPUT of FB): PascalCase, prefix: no, suffix: no.
  • Names of private, protected, internal and protected internal fields and properties of function_blocks: _camelCase, prefix: _, suffix: no.
  • Naming convention for static variables: s_camelCase, prefix: s_, suffix: no.
  • For casing, a “word” is anything written without internal spaces, including acronyms. For example, MyRpc instead of MyRPC.
  • Names of interfaces start with I, e.g. IInterface, prefix: I, suffix: no.
  • Naming for methods that cause an object to get busy (the action will take a while to complete) verb Action [Optional] Async, e.g. OpenFileAsync, RunAutomaticAsync, HaltAsync, HomingAsync

Indentation

Use 2 spaces per indentation level. Continuation lines should align with the opening delimiter if possible or you should add 2 extra spaces (an extra level of indentation) to distinguish arguments of a method or class from the rest of your code.

// Aligned with opening delimiter.
foo = _veryLongInstanceName.MethodName(var1, var2,
                                       var3, var4);
                                       
// Add 2 extra spaces to distinguish arguments from the rest of your code
foo = _veryLongInstanceName.MethodName(
    var1, var2,
    var3, var4);

IF foo
THEN
  ; // do something else
END_IF

Use spaces instead of tabs. Your editor should be configured to automatically convert tabs to spaces.

Maximum Line Length

Some editors allow you to limit the length and for those we propose a length of 130 characters. For all other editors it is a good approach not to write lines greater than this. If you someday need to edit one of those long lines and you are only working with a small monitor or laptop display size, you will be happy about this rule.

Blank Lines

After If clauses, For loops, Do While loops, Case statements or simple to indicate logical sections it is a good approach to leave one blank line for better readability. Also in sequences it is common to leave one line after a sequence step and stick the code of this step to the current step itself.

CASE _step.Index OF
  (* --------------------------------------------- *)
  MyStep.FirstStep:
  (* --------------------------------------------- *)
    IF( _step.OnEntry() )
    THEN
      _actuator.RunAsync();
    END_IF
    
    Await(_actuator, MyStep.SecondStep);
  
  (* --------------------------------------------- *)
  MyStep.SecondStep:
  (* --------------------------------------------- *)
    IF( _step.OnEntry() )
    THEN
      _logger.Debug('one debug line');
      _timer.RunAsync(2.0);
    END_IF
    
    Await(_timer, MyStep.ThirdStep);
    
  (* --------------------------------------------- *)
  MyStep.ThirdStep:
  (* --------------------------------------------- *)

Comments

Comments should always be in addition to code, describing an algorithm or basic concept - not the code. Also, comments that contradict the code are worse than no comments. Always look after your comments when changing some of your code or if not needed anymore, delete the comment. Comments should be written in complete sentences and most of the time describe a deeper understanding of the following algorithm or logical section

// No Comment needed here
IF _axis.DriveEnabled 
THEN
  DoSomeFancyStuff();
END_IF

// Add the reference distance to the position move command to avoid a crash, 
// if position of axis b is greater than 20mm
IF _axis.ActPosition() > 20.0
THEN
  _axis.MoveAbsolute(cmdPosition+referenceDistance, speed);
END_IF

In general to indicate a comment double slashes // should be used through the application no matter how many lines the comment has or if it is a inline comment. Most editors are supporting adding or removing multiple lines of comments by just clicking a shortcut and highlighting those lines.

Naming Conventions

Datatypes

Names should contain nouns or noun phrases. It is not recommended to use prefixes such as "C" or similar. Consider starting the name of the of a class with the name of its base class. This is more readable and shows clearly the relationship. For example, if the base class name is Logger and the derived class is logging into a file based system, then the final class name could be LoggerFile. For Interface names there are similar rules as for class names, but also include a Prefix I for better differentiation and proper indication of its interface type. For example, ILogger (descriptive noun). As usual with other type naming, avoid abbreviations.

Block type Notation Prefix Suffix Example
ENUM upper camel case - - ActuatorDigitalStep
STRUCT upper camel case - - Vector4
UNION upper camel case - - Matrix4
FUNCTION_BLOCK upper camel case - - ActuatorDigital
PROGRAM upper camel case - - ConversionConstants
INTERFACE upper camel case - - IActuatorDigital

Member Variables

Private variables should be easily distinguished by their name. Use the prefix _ with instantiated member variables.

FUNCTION_BLOCK PickerSequence EXTENDS Base IMPLEMENTS IPickerSequence
VAR
  _counter : INT;
  _busy : BOOL;
  _timer : ZAux.Timer;
END_VAR

Static variables

Statically instantiated variables or classes are not used that frequently in structured text programs, however sometimes it is useful and therefore we use the s_ prefix and camelCase as proposal for coding guidelines like:

FUNCTION_BLOCK PickerSequence EXTENDS Base IMPLEMENTS IPickerSequence
VAR
  _busy : BOOL;
  _timer : ZAux.Timer;
END_VAR
VAR_STAT
  s_interfaceCount : DINT;
END_VAR

Public variables

For public member variables use PascalCase, without any suffix or prefix. The declaration of a public member of a function blocks are variables, which are placed in VAR_INPUT, VAR_OUTPUT and VAR_IN_OUT blocks, in contrast to private variables, which are placed in VAR blocks. The latter have a _ as Prefix and are written in camelCase

FUNCTION_BLOCK FileWrite
VAR_INPUT
  FileName : STRING;
END_VAR
VAR_OUTPUT
  WrittenBytes : UDINT;
END_VAR
VAR
  _fileHandle : UDINT;
END_VAR

Programs are used like singletons in Structured Text and cannot be instantiated and also variables placed in a VAR block are public therefore are written in PascalCase.

PROGRAM MainProgram
VAR
  Application : ZApp;
END_VAR

Method and properties

Should be named in PascalCase without any pre- or suffix. Method names should have verb or verb phrase names (preferably in the start) like set, start, update and delete with the exception of getter methods. However, prefer properties over getter methods. Properties describe some detail about an object and therefore, should not contain any verbs. Methods, which start an action that may take some time to complete should have an additional Async suffix.

METHOD StartActionAsync // starts an action, which can be polled for completion 
METHOD Duration : LREAL // getter method
METHOD SetDuration // setter method
PROPERTY Duration : LREAL // property

Variables in the scope of a method or property do not have any prefix or suffix and are declared with camelCase.

METHOD PROTECTED ThisIsAMethodName
VAR
  counter : INT;
END_VAR
VAR_INST
  val : LREAL;
END_VAR

VAR Constant

Constant Variables can be instantiated throughout the application and the naming scheme is defined as PascalCase.

VAR CONSTANT
  NotUsed : INT := -2;
  LastItem : INT := -1;
END_VAR

Global variable lists

Global variable lists should always be used with namespace (attribute qualified_only). The defined naming scheme is PascalCase for instantiated variables or function blocks in a GVL.

{attribute 'qualified_only'}
VAR_GLOBAL
  WeirdOffset : ULINT := 42; //< Offset to calculate the truth
END_VAR

Parameter lists

Parameter lists should always be used with namespace (attribute qualified_only). The defined naming scheme is PascalCase for instantiated variables or function blocks in a ParameterList.

{attribute 'qualified_only'}
VAR_GLOBAL
  DefaultEthercatSlaveTimeout : LREAL := 5; //< Timeout for unresponsive ethercat slaves
END_VAR

Names to Avoid in general

Do not add the type of the variable als prefix or suffix in names, because modern IDEs should have a proper coding support to indicate the variable type with auto completion or tool tips. So this would be a Do:

VAR
  _parent : IManagedObject; // parent of this object
  _name : ZCore.ZString; // objects name
END_VAR

Here as an example a Dont:

VAR
  _boInput AT %I* : BOOL;
  _strName : STRING(80); 
END_VAR

Commit Guidelines

To make the commit log easy to read, it is a good approach to be clear about common commit guidelines. Its good for filtering or also to be very fast in detecting features, bugfixes or also API changes/brakes while reading through the commit log.

  • Describe at least the work which has been done, a commit message with doc in its text is not enough. Describe the overall components which are effected and in this doc example which doc has been changed.
  • Do not go to much into detail. As a general hint, 10 characters or 2-3 words is to less, 70 characters or more than 15-20 words is too much. To be more precise with the commit message use the commit description field or if it is a larger commit always use a ticket of the bug and feature tracking system to describe in detail what has been done
  • Make your commits short but also feature complete, do not mix up one commit with changes for a completely different copy
  • Every commit should be done in a way that your system is buildable

For every commit that is merged into main or a release, the commit message must have the following format. Note that this does not include commits other branch IF the commits are squashed before merging. A typical commit message should have the following structure.

<type>(<scope>): <description>

[optional body]
[optional tags like #nodoc]

[optional footer(s)]

The <type> and <description> fields are mandatory, the (<scope>) field is optional.

  • Type: The type is the most important keyword in the commit message. It is at the first position of the message. It has to be one of the following:

    • ci: Changes to our CI configuration files and scripts
    • doc: Documentation only changes
    • feat: A new feature
    • fix: A bug fix
    • refactor: A code change that neither fixes a bug nor adds a feature
    • test: Adding missing tests or correcting existing tests

    If the commit is changing the API or ABI an exclamation point should be appended to the type (i.e. feat!). Note that API changes are usually only allowed in the development branch (main).

  • Scope: The scope is an optional field and in our case used to describe the object/class or in a bigger scope collection of objects. For example, if there is a folder with severall different control algorithms, the folder is called controller and you have to change something in every controller-class then use

    feat(Controller) : add name to controller classes
    
  • Description and body: Use the description and body fields to provide a precise description of the change

    • use the imperative, present tense: "change" not "changed" nor "changes"
    • don't capitalize the first letter
    • no dot (.) at the end
    • the CI/CD server by default uses the git changelog to create a changelog summary. Here, commits are filtered for fixes, features and documentation changes. If, for any reason, a entry should be excluded, use the tag #nodoc in the the short summary.
    • If the commit is changing the API or ABI the phrase BREAKING CHANGE has to appear in the body of the commit, including a description what changed.
  • Footer: This is usually generated by git automatically.