Scripting Best Practices

Introduction

Although Iguana scripts are written in the Lua language and follow universal Lua scripting practices, there are additional coding conventions that you should follow to take full advantage of Iguana’s features. By applying these best practices, you will maximize the value of Iguana’s most useful tools: auto-completion, annotations, and context-senstive help.

Before You Start

Our best practices are written for users who are comfortable with coding terminology and Lua conventions. If you are unsure of these concepts, check out the following resources:

What are “Best Practices”?

“Best Practices” are the recommended methods and techniques for laying out and writing your scripts. These recommendations have evolved over time and represent our extensive understanding of the following:

  • The best way to make your code easier to read (“readability”). This is important not just for you now but also for the poor guy who has to fix it in two years!
  • Valuable hints that will help you work better and become more familiar with some of the “interesting” things about Lua
  • Important ways maximize Iguana features, making the Iguana IDE even more useful and powerful

Indentation [top]

It goes without saying that proper indentation is critical for proper readability. Good indentation breaks up heavy blocks of code into logical groups and obvious relationships that are easy to scan and even easier to understand.

Use Auto-indentation

Did you know that Iguana comes equipped with an auto-indentation feature? Simply select a block of code and press Tab, and Iguana will intuitively apply the correct indentation structure for you. We strongly recommend that you use this feature as part of your regular coding practice.

Include Additional Comments

Indents are particularly useful when writing loop/block structures, but what if you are working with a particularly complex logic path? By adding a brief “end” comment to all the ‘end’s in a loop, you provide valuable visual cues that can boost indentation’s benefits:

   if isValid then 
      for i=1, 100 do
         for k,v in ipairs(custTable) do

            -- code goes here!

         end -- for k,v in ipairs(custTable)
      end -- for i=1, 100 
   end -- if isValid

Variable Names [top]

When considering variable names, we often default to “short and sweet” names rather than more expressive choices. Short names are useful as they do provide more space for Iguana’s annotation blocks. Alas, their brevity means that they have poor readability and cannot take full advantage of Iguana’s auto-completion tool.

General Rules

To help you design better variable names, follow these rules:

  • Use “Camel Case” for all variables
  • Type all constants in ALLCAPS
  • Choose intuitive names that are representive of the contents
  • Prepend Boolean variable with is (for example, use isValid rather than Valid)
  • Restrict the use of underscores as they can be confusing
  • Do not start names with underscores (internal Lua rule)
  • Only use single character variables for:
    • Counters (i)
    • Iterators (k,v)
    • Passed variables in short functions

Maximizing Auto-completion

To take full advantage of Iguana’s auto-completion tool, most variable names should be 4 to 8 characters in length.

If possible, prefix names with their logical “class” or “group”. For example, the variables name, number, and phone are quite easy to read and understand, but they are too generic and will not maximize auto-completion’s searching capability. By adding the prefix “pt” (representing “patient”), we create much more useful, meaningful names: ptName, ptNumber, and ptPhone. Now we can type “pt” and auto-completion will find these specific patient values instantly.

Variable Scope [top]

Keep the scope of your variables as limited as possible.

Use “local” as much as possible. Not only does this make debugging easier, it will make the Lua script run faster as Lua does not handle global variables very well.

Commenting [top]

In theory, commenting is like cheese on a pizza: you just can’t have too much! In practice, you must find a balance between too much and too little. What’s more, any documentation should be modified as a part of maintenance or additional development. Specifically, comments should not be used to duplicate the code.

It is acceptable to assume that any future reader has a basic understanding of Lua, so we will use comments in three areas:

  • To document the history and ownership of the code
  • To provide general guidance as to how the code works
  • To explain “tricky bits”

Ownership and History

At the top of each module/function, you should provide the following “header” comment block to document the history of the code:

-- ----------------------
-- This module is created to do this and that
--
-- (c) 2012 iNTERFACEWARE Inc.  All rights reserved
--
-- 01 Jan 2012 Bob Smith first iteration
-- 02 Feb 2012 Ian Flemming  Added HL7 Spy information
--
-- ----------------------

This is particularly useful for production code as it gives us insight into a) who owns the code, and b) who has worked on it.

Note: For Iguana functions, the header comment should not re-articulate the information that would be included in customized help.

General Guidance

Significant blocks of code should be commented with what they accomplish, not how they accomplish it. This focus helps follow-on programmers find areas of concern or interest.

Tricky Bits

Most of the time, Lua is quite readable. As such, it’s safe to assume a certain level of understanding from all of its readers. That said, with developers being developers, sometimes some rockstar programmer will create code that is too elegant or too cool to be readily readable. In such cases, you must use comments to clear up any confusion that us mere mortals might encounter.

Modules [top]

Modules have been depreciated by the Lua committee as they expose too much and create a number of global variables that programmers may not be aware of.
Solution? Rather than using require 'moduleName' to call a module and its associate functions, include the following variable in your script: local exampleModule = require 'moduleName'.

This technique requires that you call the module functions exampleModule.foo rather than moduleName.foo.

This technique also requires that you set up your module a little differently:

-- define a local table to hold all of the references to functions and variables

local moduleTable = {}

-- create a local function
-- NOTE: "moduleTable.foo()" is local because it is an element in a local table (moduleTable)

function moduleTable.foo (fname, lname)
 -- script goes here
end

-- Create the a table for customized help
local addFooHelp = {
       Title="foo";
       Usage="moduleTable.foo('John', 'Doe')";
       Desc="This will do something with a first and last name. It will be awesome.";
       Returns= {
          {Desc="Awesome Number(will be negative if failed 'integer'"},
          {Desc="Optional error message 'string'"}
       };
       ParameterTable= true,
       Parameters= {
           {fName= {Desc="The given name or first name of the patient John, Mary etc."}},
           {lName= {Desc="Family name of the patient - i.e. Smith"; Opt=false}},
       };
       Examples={
           "e.g. local PatientId, Status = moduleTable.foo('John', 'Doe')"     
       };
       SeeAlso={
           {
               Title="Patient foo stuff operations.",
               Link="http://mydomain/my_extra_help.html"
           }
       }
   }

-- call the custom help functions for autocompletion support
help.set{input_function=moduleTable.foo,help_data=addFooHelp}

--return the table to be used locally.
return moduleTable

Note: This script also contains the required support elements for both customized help and customized auto-completion.

Modules are usually situated in the global name space; however, this technique keep them in the local name space includes in the table. The result? This script is faster to execute and encounters fewer memory issues. This technique also prevents confusion between the names of modules and the names of variables.

Note: Remember that module names should be intuitive to the functions that are included inside of it.

Functions [top]

Functions are the key to any program! Within the Translator, they become even more important because of how Iguana’s annotation blocks track their usage.

General Rules

For purposes of readability, every significant code block should be set up as a function. This technique has several advantages:

  • It makes the main function easier to read
  • It makes tracking logic paths within the annotation blocks much easier
  • It is easier to maintain
  • It is easier to modify
  • It imakes production message management much easier

Naming Conventions

When choosing function names, follow the same rules as when naming variables:

  • Names should express what the function will do (intuitive)
  • Names should maximize auto-completion by using meaningful prefixes
  • Function names should be longer than variable names

Passing Parameters

Stringing a long number of parameters into a function call will restrict the amount of space available to the annotation block. This negatively impacts readability. To address this, when passing more than three or four parameters, it may be useful to use a table construct: simply place the values into a Lua table and pass the table. This also make future maintenance easier!

Creating Custom Help

Anytime you create user-facing functions, you should define custom help functions for them. More information, see help in the API reference.

Returning Values

Functions fall into two categories: calculation or processing:

  • Calculation functions are functions that perform logical calculations on input parameters, then return a/some value(s) that it computes. Examples are sine(), stripNulls(), or removeCR(). Normally, calculating functions return only a single or small number of values. These are pretty well understood and are fairly good at self-documenting, as long as the naming is intuitive and useful.
  • Processing functions (or workflow functions) manage programmatically built tasks or workflows. Examples are patient.addReport or emr.createBilling. These tend to be more complex. They also tend to fail! When using these functions, we strongly suggest that you perform the following:
    • Report the activity and status as a string in the return statement rather than a simple ‘OK’ (i.e. return blah, blah, blah, ‘Billing invoice submitted’)
    • Report the activity and status as a iguana.logdebug entry that can be turned on if there are problems

Logic-Enabled Functions

Scripts often include loops or conditional statements. In both cases, the repeating or conditional logic should surround the function and not the other way around:

for k,v in impairs(patientTable) do 
   validateValue(v)
end

function validateValue(x)
   if x == nil then error ‘Null value found’ end
end -- function

The above example is obviously more readable and more expandable than the following:

validateValue(patientTable)

function validateValue(t) 
   for k,v in impairs(t) do 
      if v == nil then error ‘Null value found’ end
   end -- for
end -- function

Performance vs. Readability

In long loops, we recommend that you assign a function to a local variable (as the local function will run about 30% faster):

-- Global function
for i= 1, 1000000 do
   local x = math.sin(i)
end

-- Local Function use
local sin = math.sin
for i= 1, 1000000 do
   local x = sin(i)
end

In this case, we sacrifice a bit of readability in favour of performance. This should be a case-by-case decision.

Use local functions [top]

If you define a variable in a function always remember to prefix it with the keyword “local” otherwise it will have global scope in the program.

This code fragment illustrates the issue:

Here you can see that because the variable A was not explicitly defined as being local in scope, the fooBar() function can access the value it was set to in the foo() function.

More information [top]