Best practice per la codifica in JavaScript
Organizzazione e formattazione del codice
Scrivere la logica del codice in alto e le funzioni dell'helper alla fine.
Dichiarare le variabili globali in alto.
Se nel servizio sono presenti più di 150 righe, dividere il codice in funzioni o in più servizi.
Assegnare un nome appropriato alle variabili e alle funzioni per ridurre il numero di commenti e migliorare la leggibilità del codice.
Utilizzare la funzionalità di formattazione automatica JavaScript integrata in ThingWorx Composer.
Scrittura di codice
Utilizzare i casi truthy e falsy nelle istruzioni condizionali.
// *** will act like a false statement ***
if (false)
if (null)
if (undefined)
if (0)
if (-0)
if (0n)
if (NaN)
if ("")
// *** will act like a true statement ***
if (true)
if ({})
if ([])
if (1)
if ("0")
if ("false")
if (new Date())
if (-1)
if (12n)
if (3.14)
if (-3.14)
if (Infinity)
if (-Infinity)
// *** you can force it into a boolean value by using the !! operator ***
// i.e.:
var myNullValue = null;
var myBooleanValue = !!myNullValue; // it will store as false
Eseguire operazioni CRUD in batch per minimizzare le interazioni con il database. Se tuttavia si ricevono troppe informazioni dal database, potrebbe essere necessario effettuare più chiamate singole al database.
// *** bad practice ***
for (let i = 0; i < Things["thingName"].GetPropertyDefinitions().length; i++) {
var newEntry = new Object();
newEntry.name = Things["thingName"].GetPropertyDefinitions().rows[i].name;
newEntry.description = Things["thingName"].GetPropertyDefinitions().rows[i].description;
result.AddRow(newEntry);
}
// GetPropertyDefinitions() is called multiple times because of the looping

// *** preferred practice ***
let propertyDefinitions = Things["thingName"].GetPropertyDefinitions();
propertyDefinitions.rows.toArray().forEach((propertyDefinition) => {
result.AddRow({
name : propertyDefinition.name,
description : propertyDefinition.description
});
});
// By storing the content of GetPropertyDefinitions() ahead of time, you will prevent many unnecessary REST calls.
Evitare di manipolare direttamente le proprietà degli oggetti, in quanto questo può causare problemi di sincronizzazione persistenti e perdita di prestazioni.
// *** common practice ***
var referencedInfoTable = Things["thingName"].myInfoTable;
for (let i = 0; i < 10; i++) {
var row = new Object();
row.field1 = i;
row.field2 = i * 2;
row.field3 = i * 3;
referencedInfoTable.AddRow(row);
}
Things["thingName"].myInfoTable = referencedInfoTable;
// the .AddRow() will implicitely write to cache, thus causing some performance issues

// *** preferred practice ***
var deepCloneInfoTable = Things["thingName"].myInfoTable.clone();
for (let i = 0; i < 10; i++) {
deepCloneInfoTable.AddRow({
field1 : i,
field2 : i * 2,
field3 : i * 3
});
}
Things["thingName"].myInfoTable = deepCloneInfoTable;
// the .AddRow() of the cloned infotable does not have a reference to the original infotable, therefore does not write to cache.

// *** for your information ***
var referencedInfoTable = Things["thingName"].myInfoTable;
var tableLength = referencedInfoTable.rows.length;
for (let i = 0; i < tableLength; i++) {
referencedInfoTable.rows[i] = {
field1 : i,
field2 : i * 2,
field3 : i * 3
};
}
Things["thingName"].myInfoTable = referencedInfoTable;
// because you are not using .AddRow() it doesn't require access to the referenced object's .AddRow() method, therefore it doesn't need to access the cache.
Per eliminare il linting sulla riga successiva, aggiungere un commento. In questo modo vengono ignorati gli errori di linting che possono essere risolti solo correggendo la libreria di linting, ma il linting viene comunque eseguito altrove.
Oggetti
Creare oggetti utilizzando {} anziché new Object();. Provare a creare i valori dell'oggetto in linea, in quanto questo rende più facile la lettura e più veloce la scrittura.
// *** old practice ***
var myObject = new Object();
myObject.item1 = "value1";
myObject.item2 = "value2";
myObject.item3 = "value3";
// *** preferred practice ***
var myObject = {
item1:"value1",
item2:"value2",
item3:"value3"
};
Per gli oggetti di clonazione profonda, utilizzare JSON.parse(JSON.stringify(object)).
Variabili
Dichiarare le variabili utilizzando let anziché var. Utilizzare const per dichiarare le variabili costanti.
L'utilizzo di let impedisce di dichiarare una seconda volta e sovrascrivere le variabili. Impedisce anche di creare funzioni con lo stesso nome.
// *** common mistake ***
var value = 1;
if (true) {
var value = 2;
}
// value = 2;because it was overwritten by `value` inside if statement
// *** how to avoid mistake ***
let value = 1;
if (true) {
let value = 2;
// value = 2; inside the if statement
}
// value = 1;because the `value` inside if statement does not affect the first `value`

// *** you can still access the variable in the outter scope ***
let value = 1;
if (true) {
value = 2;
}
// value = 2;because the content of `value` was replaced
Assegnare un nome alle variabili utilizzando il camel case.
Per impostare un valore di default per una variabile per un valore undefined o null, utilizzare l'operatore || (OR).
// *** traditional way ***
var myNullValue; //undefined
if (myNullValue == null || myNullValue == undefined || myNullValue == "") {
myNullValue = "something";
}
var output = myNullValue;
// *** alternate way with ternary operator ***
var myNullValue; //undefined
var output = (myNullValue == null || myNullValue == undefined || myNullValue == "") ? "something" : myNullValue;
// *** preferred practice ***
var myNullValue; //undefined
var output = myNullValue || "something";
* 
Potrebbe non essere il modo ideale per impostare un valore di default perché utilizza un'istruzione falsy. Ad esempio, in presenza di un valore 0, l'operatore || potrebbe impostare un altro valore anziché 0.
Utilizzare la notazione dot per accedere alle variabili. Utilizzare la notazione con parentesi quadra solo se nella chiave sono presenti caratteri speciali, ad esempio: object.row["field name with space"].
Creare una variabile per memorizzare un oggetto e utilizzarla in un loop.
// *** common mistake ***
for (let i = 0; i < 100; i++ ){
Things["thingName"].myProperty;
}
// *** best practice practice ***
let myThing = Things["thingName"];
for (let i = 0; i < 100; i++){
myThing.myProperty;
}
Funzioni
Assegnare un nome alle funzioni utilizzando il camel case.
Dichiarare le funzioni come dichiarazioni di funzione e non come variabili.
Non è possibile spostare espressioni di funzione e frecce di funzione nella parte inferiore del codice.
// *** function declaration (Preferred practice) ***
function doSomething1 (param) {
return param;
}
// *** function expression (Avoid) ***
const doSomething2 = function (param) {
return param;
}
// *** function arrow expression (Can be used inside .map(), .redude(), .filter()) ***
const doSomething3 = (param) => {
return param;
}
Utilizzare return nelle funzioni per interrompere il flusso della logica del codice. Ciò consente di risparmiare alcune operazioni della CPU e di favorire la leggibilità.
// given that we have multiple validations, you can save some operations by leveraging a function return
// given dice1 = undefined, dice2 = null
// common way
let outputMessage;
if (dice1 > 0 && dice1 < 7) {
if (dice2 > 0 && dice2 < 7) {
if (dice1 + dice2 == 7) {
outputMessage = SUCCESS;
} else {
outputMessage = ERROR_NOT_SEVEN;
}
} else {
outputMessage = ERROR_BAD_DICE2;
}
} else {
outputMessage = ERROR_BAD_DICE1;
}
result = outputMessage;
// preferred way
result = rollSeven(dice1, dice2);
function rollSeven (dice1, dice2) {
if (!(dice1 > 0 && dice1 < 7)) {
return ERROR_BAD_DICE1;
}

if (!(dice2 > 0 && dice2 < 7)) {
return ERROR_BAD_DICE2;
}

if (dice1 + dice2 != 7) {
return ERROR_NOT_SEVEN;
}

return SUCCESS;
}
// the preferred way promotes readability. By using a return statement and only testing for failing cases it reduces the need for nested if statements.
Servizi
Assegnare un nome ai servizi utilizzando la notazione Pascal.
Utilizzare un parametro in linea per chiamare i servizi. Se tuttavia i parametri di input sono grandi o se si intende riutilizzarli, memorizzare i parametri di input come variabile e quindi passare la variabile nella chiamata al servizio.
// *** old practice ***
var params = {
"param1" : "paramValue1",
"param2" : "paramValue2"
}
Thing["thingName"].CallServiceName(params);
// *** preferred practice ***
Thing["thingName"].CallServiceName({
"param1" : "paramValue1",
"param2" : "paramValue2"
});
Evitare di utilizzare i servizi che chiamano il database all'interno di un loop.
Infotable
Per creare una infotable, utilizzare il metodo seguente:
// common way
var table = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName : "InfoTable",
dataShapeName : "GenericStringList"
});
// easier way
var table = DataShapes["GenericStringList"].CreateValues();
Per ottenere la lunghezza o la riga di una infotable, utilizzare il metodo seguente:
// fetching a row
myInfoTable[i] = myInfoTable.rows[i] = myInfoTable.getRow(i)
// getting the length
myInfoTable.length = myInfoTable.rows.length = myInfoTable.getRowCount()
Utilizzare la notazione dot per accedere ai dati della prima riga della infotable:
// if you have an infotable with rows
// classical way to access the first row field
myInfoTable.row[0].field
// trick to access the first row field
myInfoTable.field
Utilizzare metodi di array, ad esempio .map(), .reduce(), .filter(), .some(), .every(), per modificare le infotable o più righe di infotable. È possibile utilizzare questi metodi per le infotable se si usa infotable.rows.toArray().[map() | reduce() | filter()].
Utilizzo di .map():
// *** What you have ***
var officers = [
{ id: 20, name: 'Captain' },
{ id: 24, name: 'General' },
{ id: 56, name: 'Admiral' },
{ id: 88, name: 'Commander' }
];
// *** What you need ***
//[39, 44, 57, 95]
var officersIds = officers.map((officer) => officer.id);
result = officersIds;
Applicazione di modifiche a più righe di infotable:
var table = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName : "InfoTable",
dataShapeName : "GenericStringList"
});
table.AddRow({item:1});
table.AddRow({item:2});
table.AddRow({item:3});
table.AddRow({item:4});
Array.from(table.rows).map(row => {row.item = row.item * 2 + "hello";});
result = table;
Utilizzo di .reduce():
var table = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName : "InfoTable",
dataShapeName : "GenericStringList"
});
table.AddRow({item:1});
table.AddRow({item:2});
table.AddRow({item:3});
table.AddRow({item:4});
// total sum should be 1 + 2 + 3 + 4 = 10
result = Array.from(table.rows).reduce((total, row) => total + parseInt(row.item), 0);
Per iterare e scorrere in loop i dati nelle infotable, utilizzare un loop .forEach(). Questo migliora le prestazioni.
table.rows.toArray().forEach(row => {
data += row.item;
});
Per la clonazione profonda delle infotable, utilizzare var deepClonedInfoTable = myInfoTable.clone().
Messaggi logger
Nelle istruzioni try … catch assegnare all'oggetto di eccezione il nome err.
try {
Things["thingName"].CallServiceName();
} catch (err) {
logger.error(err);
} finally { // finally is optional
// will go here whether an exception occured or not
}
* 
Si consiglia di non registrare l'errore (in questo caso err) in un formato JSON e di utilizzare invece err.message come best practice.
Ogni volta che si crea un oggetto, circondarlo sempre con un'istruzione try catched eliminarlo in caso di errore nell'istruzione catch. In questo modo viene eliminata la creazione di entità fantasma. Se nel sistema sono presenti entità fantasma, vedere Servizi entità fantasma per eliminarle.
Ogni volta che un servizio crea un'entità, assicurarsi di gestire un caso di utilizzo di entità fantasma.
try {
// Successfully creates the GhostThing entity.
Resources["EntityServices"].CreateThing({
name: "GhostThing" /* STRING */,
description: "Ghost" /* STRING */,
thingTemplateName: "GenericThing" /* THINGTEMPLATENAME */
});
} catch (err) {
// If an exception is caught, we need to delete the entity
// that was created to prevent it from becoming a Ghost Entity
Resources["EntityServices"].DeleteThing({
name: "GhostThing" /* THINGNAME */
});
}
In presenza di log di errori che si desidera visualizzare nei log degli script di monitoraggio, includere informazioni quali il nome dell'oggetto, il nome del servizio, il numero di riga e il messaggio di errore originale. Tenere presente che è possibile utilizzare {} come segnaposto per la formattazione delle stringhe nel log. Il messaggio formattato può rivelare informazioni non necessarie che potrebbero causare problemi di protezione. Evitare di mostrare troppi dettagli se si gestiscono servizi che verrebbero utilizzati in un mashup o in sistemi esterni.
In un oggetto di eccezione sono disponibili le proprietà seguenti:
err.lineNumber - Numero di riga in cui è stata generata l'eccezione
err.name - Tipo di eccezione, ad esempio JavaException
err.fileName - Nome del servizio dal quale è stata attivata l'eccezione
err.message - Messaggio di eccezione
try {
// error is fired here
} catch (err) {
let errMsg = "Thing [{}] Service [{}] error at line [{}] : {}";
logger.error(errMsg, me.name, err.fileName, err.lineNumber, err);
throw err; // throw original error message
}
// log output => Thing [PTC.SCA.SCO.MyThing] Service [CallMyService] error at line [2]: TypeError: Cannot find function CallMyService in object com.thingworx.things.MyThing@251c8397.
A seconda delle esigenze di granularità, utilizzare il livello di logger appropriato.
Scrivere i logger solo se necessario. Troppi messaggi di log rendono impossibile il debug.
Livello
Descrizione
DEBUG
Specificare gli eventi informativi granulari più utili per il debug di un'applicazione.
// for example, show the length of time a function takes to run.
let startTime = new Date().getTime();
// do something
let endTime = new Date().getTime();
let time = endTime - startTime;
logger.debug("How long did it take to run something (ms) : {}", time);
INFO
Specificare i messaggi informativi che evidenziano lo stato di avanzamento dell'applicazione a un livello più generale.
// for example, you wish to track the progress of your service
logger.info("Step 1 - creating variable");
let step1 = "do something";
logger.info("Step 2 - run a function");
myFunctionDoSomething(step1);
WARN
Specificare le situazioni potenzialmente pericolose.
// for example, you see a potential issue here
let resultFromService = Things["thingName"].CallServiceName();
if (resultFromService == undefined) {
logger.warn("My variable is undefined... strange");
}
myFunctionDoSomething(resultFromService);
ERROR
Specificare gli eventi di errore che potrebbero consentire comunque l'esecuzione dell'applicazione.
// for example, a service was not suppose to throw an error
try {
var resultFromService = Things["thingName"].CallServiceName();
} catch (err) {
logger.error("Something really bad happened: {}", err);
}
È stato utile?