Article
· 3 hr ago 12m read

Proposal for ObjectScript naming conventions and coding guidelinesContestant

Introduction

What's in a name? That which we call a rose
By any other name would smell as sweet

William Shakespeare, "Romeo & Juliet"

In this article, we will describe a set of naming conventions for ObjectScript code.

Code naming conventions exist for several important reasons:

  1. Readability: Consistent names improve code clarity and comprehension. Following naming conventions makes it easier to identify and remember components.
  2. Maintainability: Properly named code elements ease the maintenance and updating of code and configuration items, reducing confusion and errors.
  3. Collaboration: in a team setting, having a common naming convention ensures that everyone is on the same page. It promotes smoother collaboration and reduces the likelihood of miscommunication.
  4. Debugging: clear and consistent names can help in quickly identifying and fixing issues. Descriptive names can indicate the purpose and scope of a code element, aiding in the debugging process.
  5. Documentation: following naming conventions can simplify the process of creating and maintaining documentation. Names that reflect their function or purpose make it easier to explain and understand the code. 

As IRIS developers and administrators, we are naming various types of objects, some of which have numerous instances. For example, interoperability production with dozens of configurations items and data transformation classes is not at all uncommon in real-world scenarios such as healthcare patient and appointment data routing.

The aim of this article is to compile bits from various sources of information in a consistent proposal for robust naming conventions. As the many names defined are used in ObjectScript code, it also describes some coding guidelines.

 

Compiler items

In this part, we’ll describe guidelines for naming:

  • packages
  • classes, includes and routines
  • class members: parameters, properties, methods and XData blocks
  • local variables and method parameters 

Packages

 Package names use only lower-case letters and decimal digits. The first character must be a lower-case letter.

 The root package has at least one component, and it identifies the source entity, organization or group.

 Examples:

  • dc
  • myorg
  • acme 

Packages under root denote the purpose of the classes. They may have subpackages denoting a finer purpose or domain, and use lower case letters, except for application domains that are spelled in upper case letters. 

The following root subpackage names are reserved 

Compiler item purpose

Package

CSP page

csp

Data models and transfer objects (classes _not_ extending %Persistent)

model

Data type

type

Include resources: macros, constants, …

inc

Interoperability component - business adapter

interop.ba

Interoperability component - business service

interop.bs

Interoperability component - business process

interop.bp

Interoperability component - business operation

interop.bo

Interoperability component - message

interop.msg

Interoperability component - data transform

interop.dt

Persistent entity (classes extending %Persistent)

entity

REST API

api

Routines

rou

Scheduled tasks

tas

Service

service

SOAP Web Service

ws

Utility class

lib

Classes and other elements

Class names use upper camel case, must start with an uppercase letter and use letters and decimal digits. Avoid starting with "%".

Routine names should be all uppercase. Avoid starting with "%".

Item purpose

 Class name pattern

Examples of fully qualified class names

CSP page

<name>

myorg.csp.app.LogonForm

Data models and transfer objects (classes _not_ extending %Persistent)

<name>

myhospital.model.Patient

Data types

<name>

acme.type.FusionReactorType

myhospital.type.patient.MRN

Include resources: macros, constants, …

<name>

myorg.inc.Errors

Interoperability component - inbound business adapter

<name>InboundAdapter

interop.ba.hl7.UNCPathFileInboundAdapter

Interoperability component - outbound business adapter

<name>OutboundAdapter

acme.ba.CustomTCPOutboundAdapter

Interoperability component - duplex business adapter

<name>DuplexAdapter

myorg.ba.CustomTCPDuplexAdapter

Interoperability component - business service

<name>Service

myhosp.interop.bs.FileService

Interoperability component - business process

<name>Process

myhosp.interop.bp.AppointmentCancelProcess

Interoperability component - business operation

<name>Operation

myhosp.interop.bo.WISH.PatientOperation

Interoperability component - request message (extends Ens.Request)

<verb>[<resource>]Request

myorg.interop.msg.patient.GetRequest

myorg.interop.msg.patient.GetEncountersRequest
myorg.interop.msg.patient.Response

Interoperability component - response message (extends Ens.Response)

[<name>]Response

myorg.interop.msg.patient.Response

myhospital.interop.msg.invoicing.BillInsuranceResponse

Interoperability component - other message (extends Ens.MessageBody)

<name>

myhospital.interop.msg.patient.PatientUpdatedEvent

myorg.interop.msg.document.Container

Interoperability component - data transform

[<source-applicaton>]<source-format>To[<target-application>]<target-format>

myorg.interop.dt.ULTRAGENDASIUToAppointmentUpdate

Persistent entities (classes extending %Persistent)

<name>

myorg.entity.Document

REST APIs - generated classes (lowercase letters)

impl
spec

disp

myhospital.api.terminology.impl
myhospital.api.terminology.spec

myhospital.api.terminology.disp

Routines

<name>

myhosp.rou.PHUTL001

Scheduled tasks

<name>Task

myhospital.task.CancelAppointmentsTask

Service

<name>Service

service

SOAP Web Services

<name>WS

acme.ws.accounting.SupplierWS

Utility classes

<name>

myorg.lib.xml.Utils

Class members

Class members names use  upper camel case, must start with an uppercase letter (avoid using '%'), and use letters and decimal digits.

Member

Convention

Example

Parameter

Upper camel case or all uppercase

MRNCODESYSTEM
DocTypeCategory

Properties

Upper camel case

BirthDate
DeathDate

Method

Upper camel case. Favor a <verb><object> pattern

FetchPatient
ProcessMessage
UpdatePractitionerRoles

Xdata

Upper camel case

HL7Mappings

Local variables & method parameters

Local variable names and method parameters use lower camel case and start with a lower-case letter.

Some examples: request, response, patientId, mrn

Instance (i%..., r%...) and process (%...) variables follow the same convention.

 

Coding guidelines

Block syntax

A block statement, or compound statement, lets you group any number of statements (including 0) into one statement.

ObjectScript currently supports two syntaxes for blocks:

  • Curly brace block syntax
  • Dot block syntax

Curly brace block syntax

It is similar to that in C, Java, C#, … making the following short example look very familiar to most programmers:

if a=0 {
   write "foo",!
   write "bar",!
}

Dot block syntax 

This is the original MUMPS block syntax. It is supported for backward compatibility with (very) old code. Its use is strongly discouraged, as it can get quite confusing, especially when combined with the short version of commands and the lack of reserved words, as in the following (intentionally a little mischievous) example: 

j=1:1:d d
 . r i
 . i '$test b
 . i i'="" d
 .. s d=$p(l," ",1) 41)
 .. s w=$p(l," ",2)
 .. w d,?10,$e(^title(d),1,80),!

Post-conditionals

This is an implementation in ObjectScript of the concept of guarded command, as defined by Dijkstra (1975).

It is a conditionally executed statement, where a boolean expression "guards" the execution of the statement. 

<command>:<condition> <command arguments>

 It is functionally equilavent to

if <condition> <command> <command arguments>

Although the concept is well defined, the syntax is not common, so when should it be used instead of an if statement?

  • execution flow control: quit, continue, throw
  • default value assignment: set

some examples:

quit on error, continue on condition

quit:$$$ISERR(sc)

#Dim a as %Integer
while a > 0 {

continue:a=5

}

throw on condition

#Dim obj as %RegisteredObject
throw:'$isobject(obj) ##class(%Exception.General).%New("object not found")
 

Assign default value

#Dim obj as Foo

set:'$isobject(obj) obj = ##class(Foo).%New()

Use return instead of quit for return values

In new code, use return instead of quit, as quit can be used both for exiting current execution context and return a value.

'quit' has two different meanings :

  • when use with no argument, it exits current execution context (e.g. loop)
  • when use with an argument, it exits current routine/method and returns value

 

'return' is an addition to ObjectScript meant to improve code readability, as it implements only the second meaning.

Command arguments

The use of a comma-separated list of command arguments should be avoided, as for some commands, it gets confusing.

For example,

if a=0,b=1 {
...
}

 

It is much less readable (to the 'modern' reader) than

if (a=0)&&(b=1) {
...
}
 

Ternary operator - expressional 'if' 

The $select function can be used as ternary if operator:

$select(<boolean expression>:<true value>,1:<false value>)

 

 Switch/case 

Either

  • $case() when switch intent is to select a value
  • if elseif elseif … when switch intent is to select behaviour


Command keywords

Command keywords are not case-sensitive, and most commands come in two variants, fully named and shorthand.

  • Favor the use of full command keywords, except for the most common ones like 'set' and 'do'
  • Use all lowercase for command keywords
  • Avoid using legacy goto <label> command for flow control

 

Function names

As commands, function names are not case-sensitive and most functions come in two variants, fully named and shorthand.

  • Favor the use of full function names instead of shorthand, e.g. use $extract() instead of $e
  • Use all lowercase for intrinsic function names
  • Use upper camel case for extrinsic function names

Method parameters and return values

  • group optional parameters at the end
  • if the method is a function that returns a data type or OREF, and returns a %Status, the %Status is returned as the last parameter
 

Interoperability production items

Interoperability productions can easily count a sizeable of business hosts. A consistent naming scheme helps a lot with readability across the various actors reading the names: developers, administrators and support staff.

Business services

Propose

Name pattern

Examples

Receive messages from an application

<format>From<application>

SIUFromULTRAGENDA

Receive deferred responses

<application>Response

DOCSHIFTERResponse

REST or SOAP API

<name>Service

TerminologyService

Business processes

Purpose

Name pattern

Examples

Process requests

Orchestration

<name>Process

AppointmentCancelProcess

DocumentProcess

Route messages

<format>Router

ADTRouter
SIURouter

Business operations

Purpose

Name pattern

Examples

Send messages to an application or application component. Optionally use suffixes to denote application subcomponents or environments

<format>To<application>[_<component>]

SIUToWISH
ADTToSOFTALMO_PROD
ADTToSOFTALMO_TEST

HL7ToArchive

Query external system and return responses

<name>Operation

ULTRAGENDAAPIOperation

Duplex operation

<name>Duplex

 

Business duplexes

Classes extending Ens.BusinessDuplex are use

<name>Duplex

 

 

Some examples

Class method

/// <p>Purges all message bodies associated with sessionId and if purgeHeaders is set, purge headers too.</p>
/// <p><b>purged</b> returns the total count of items successfully purged, and the count by class name in the first subscript.</p>
/// <p>Stops and returns error status if any error occurs during purge.</p>
ClassMethod PurgeSessionMessageBodies(sessionId As %Integer, Output purged As %Integer, purgeHeaders As %Boolean = 0, noLock As %Boolean = 1) As %Status
{
  #Dim sc as %Status
  #Dim ex as %Exception.AbstractException
  #Dim stmt as %SQL.Statement
  #Dim rs as %SQL.StatementResult
  s sc = $$$OK
  try {    
    s stmt = ##class(%SQL.Statement).%New()
    s rs = stmt.%ExecDirect(,"select"_$select(noLock:" %NOLOCK",1:"")_" ID as HeaderId,MessageBodyClassName as BodyClass,MessageBodyId as BodyId from Ens.MessageHeader where SessionId=?",sessionId)
    while rs.%Next() {
      if ($length(rs.BodyClass) > 1) && $$$ISOK($classmethod(rs.BodyClass,"%DeleteId",rs.BodyId)) {
        d $increment(purged)
        d $increment(purged(rs.BodyClass))
      }
      if purgeHeaders {
        $$$TOE(sc,##class(Ens.MessageHeader).%DeleteId(rs.HeaderId))
        d $increment(purged)
        d $increment(purged("Ens.MessageHeader"))     
      }
    }
  }
  catch (ex) {
    s sc = ex.AsStatus()
  }
  return sc
}

Outbound adapter

/// HL7 file outbound adapter, using <class>ks.lib.file.ba.UNCOutboundAdapter</class>
/// This adapter also adds expression parsing to <method>CreateFilename</method> : see <method>ks.lib.hl7.Utils.ParseExpressions</method>
Class ks.interop.hl7.ba.FileOutboundAdapter Extends ks.interop.file.ba.UNCOutboundAdapter
{
// keeping parameter names as in superclass for clarity
Method CreateFilename(ByRef pFileName As %String, ByRef pSpec As %String, ByRef pIsVMS As %Boolean, ByRef pDirectory As %String, ByRef pLocal As %Boolean) As %String
{
	#Dim sc as %Status
	#Dim ex as %Exception.AbstractException
	s sc = $$$OK
	try {
  	  if $isobject(..BusinessHost.%RequestHeader) &&
	     $classmethod(..BusinessHost.%RequestHeader.MessageBodyClassName,"%Extends","EnsLib.HL7.Message") {
	    s msg = ##class(EnsLib.HL7.Message).%OpenId(..BusinessHost.%RequestHeader.MessageBodyId)
	    if $isobject(msg) {		  
	  	  s pSpec = ##class(ks.lib.hl7.Utils).ParseExpressions(msg,pSpec,.sc)
		  $$$TRACE("spec after HL7 expressions parsing : "_pSpec)
	    }
	  } 
	}
	catch (ex) {
	  // do nothing, fall back to ##super
	}	
	return ##super(.pFileName,.pSpec,.pIsVMS,.pDirectory,.pLocal)
}

}
/// A string datatype definition which extends <class>%Library.String</class> with additional regex pattern validation. <br />
Class ks.lib.type.RegExString Extends %String
{

/// Set PATTERN to empty and final, as it is not relevant on
/// this type, but is inherited from <class>%Library.String</class>
Parameter PATTERN [ Final ];
/// Set VALUELIST to empty and final, as it is not relevant on
/// this type, but is inherited from <class>%Library.String</class>
Parameter VALUELIST [ Final ];
/// Set DISPLAYLIST to empty and final, as it is not relevant on
/// this type, but is inherited from <class>%Library.String</class>
Parameter DISPLAYLIST [ Final ];
/// Set a valid regex pattern for value validation
Parameter REGEX As STRING;
/// The XMLPATTERN to regex by default. Can be overridden.
Parameter XMLPATTERN = {..#REGEX};
ClassMethod IsValid(%val As %Library.RawString) As %Status [ ServerOnly = 0 ]
{
    #Dim sc as %Status = $$$OK
    #Dim ex as %Exception.AbstractException
    try {
         $$$TOE(sc,##class(%String).IsValid(%val))
         if (..#REGEX '= "") {
            if '$match(%val, ..#REGEX) {
                s sc = $$$ERROR($$$DTPattern, %val, ..#REGEX)
            }
         }       
    }
    catch (ex) {
      s sc = ex.AsStatus()
    }
    q sc
}

}
Discussion (0)2
Log in or sign up to continue