Best Practices for Coding

Separate the user interface (UI) from implementation.

This is an example of the Single Responsibility Principle where each section of the code should fulfill a clear requirement. Separating concerns such as UI and logic allows for easier testing.

All variables and modules should be declared by default as PROTECTED.

This gives a visual guide as to which elements of a module are not intended for public use. Note that PROTECTED does not actually restrict the calling of Modules. To inform others, declarations that are intended to be public should have a { PUBLIC } comment prefix.

Subroutines shall preferably have one exit point at the end of the script block.

This prevents code not running due to an earlier return. Note that this can also complicate code. For example, parameter error handling must be propagated down to the final return statement.

Don’t use !False() to validate arguments.

The logical contortions required to understand the effect are too confusing.

Use True(X) instead of PickValid(X, False).

Simpler and clearer.

Restrict usage of InsertArrayItem for large numbers of inserts.

InsertArrayItem has performance of O(n) as it has to create a new larger array and copy the existing values from the old array into the new one; performance suffers when items are continuously inserted and the array size grows. Items can be accumulated into a dictionary (O(log n)) using an increasing integer for the “key” and then converted to an array if required.

Don’t use Watch(0, …).

Either use Watch(1, ...) or capture the values of the watched values before entering the state and check if they've changed. Watch(0, ...) has the potential of missing a change that occurs after starting a state but before Watch(0, ...) runs.

Don't prefix references to Code, VTSDB, or System with a leading Scope operator \.

They are statically defined in AppRoot and the leading \ adds additional code and execution time unnecessarily.

Exceptions apply. To reference the Code object itself, use \Code. To reference a system library module or variable use \System.ModuleName.

Don't pre-increment or pre-decrement the loop index in a WhileLoop.

Doing so is simply over-complicated.

Don't use the same variable for two (or more) entirely different purposes within the same module.

For example, using I as a loop index in different loops is fine, but also using it to hold text is not.

Always use the trigger parameters on input controls.

This allows for the correct detection of changes to the control.

Do not ignore launched objects.

For example, where the caller doesn't pay attention to the lifetime of the callee.

If the caller should be slain before the callee finishes, then the callee will be slain prematurely. Typically, it is best to watch for the callee's completion unless you have a good reason not to. Typically, this also means that you'll avoid launching two of the callees in parallel, which may also be undesirable.

Avoid data races.

Avoid parallel data flows that ultimately have convergent results, as this is the source of race conditions. For example, if you set some values and they go off to their own script and affect other things, and then the results of both come back together (or perhaps one at a time) to affect something else. Often it is less risky and easier to follow just to serialize actions in a script than to try to run them "in parallel" across multiple steady-state statements and script blocks.

Use ForceState with caution.

This is the equivalent of a GoTo in other languages and should be used with caution for similar reasons. The compiler does no checking of the target state so typos won’t be detected. Note that ForceState does not make an immediate jump; script execution continues to the end of the block, so code that should not be run after the call to ForceState should have suitable guards.