Bonnes pratiques de codage en JavaScript
Organisation et formatage du code
Placez la logique du code en haut et les fonctions d'assistance en bas.
Déclarez les variables globales en haut.
Si votre service contient plus de 150 lignes, divisez votre code en fonctions ou en plusieurs services.
Nommez vos variables et fonctions de manière appropriée afin de réduire le nombre de commentaires et d'améliorer la lisibilité de votre code.
Utilisez la fonctionnalité de formatage automatique pour JavaScript intégrée dans ThingWorx Composer.
Ecriture du code
Tenez compte des cas évalués à vrai et faux suivants dans vos instructions conditionnelles.
// *** 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
Effectuez vos opérations CRUD par lot pour minimiser vos interactions avec la base de données. Toutefois, si vous recevez un trop grand nombre d'informations de la base de données, vous pouvez être amené à effectuer plusieurs appels uniques à celle-ci.
// *** 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.
Evitez de manipuler les propriétés des objets directement dans la mesure où cela peut entraîner des problèmes de synchronisation persistants et des pertes de performance.
// *** 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.
Pour plus d'informations, consultez la rubrique Bonnes pratiques pour les applications haute disponibilité.
Pour inhiber le linting à la ligne suivante, ajoutez un commentaire. Cela permet de contourner les erreurs de linting qui n'ont pu être traitées qu'en corrigeant la bibliothèque de linting tout en continuant d'obtenir du linting partout ailleurs.
Objets
Créez vos objets en utilisant {} plutôt que new Object();. Essayez de créer vos valeurs d'objet en ligne car la lecture s'en trouve simplifiée et l'écriture accélérée.
// *** 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"
};
Pour les objets de clonage profond, utilisez JSON.parse(JSON.stringify(object)).
Variables
Déclarez vos variables en utilisant let plutôt que var. Utilisez const pour déclarer vos variables constantes.
L'utilisation de let vous empêche de redéclarer et d'écraser vos variables. La création de fonctions portant le même nom est par ailleurs impossible.
// *** 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
Utilisez la casse mixte pour nommer vos variables.
Pour fixer la valeur par défaut d'une variable de valeur undefined ou null, utilisez l'opérateur || (OU).
// *** 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";
* 
Cette approche peut ne pas être idéale pour définir une valeur par défaut dans la mesure où elle utilise une instruction évaluée à faux. Par exemple, avec une valeur 0, l'opérateur || pourra définir une autre valeur que 0.
Utilisez la notation "point" pour accéder aux variables. Utilisez la notation "crochets" uniquement si vous avez des caractères spéciaux dans votre clé, par exemple : object.row["field name with space"].
Créez une variable pour stocker un objet et utilisez cette variable dans une boucle.
// *** 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;
}
Fonctions
Utilisez la casse mixte pour nommer vos fonctions.
Déclarez vos fonctions en tant que déclarations de fonction et non en tant que variables.
Vous ne pouvez pas déplacer les expressions de fonction et les expressions de fonction fléchée en bas de votre code.
// *** 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;
}
Utilisez return dans vos fonctions pour interrompre le flux de la logique de code. Vous économisez de la sorte certaines opérations UC et améliorez la lisibilité.
// 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.
Services
Utilisez la casse Pascal pour nommer les services.
Utilisez un paramètre en ligne pour appeler les services. Toutefois, si les paramètres d'entrée sont conséquents ou si vous souhaitez réutiliser les paramètres, stockez les paramètres d'entrée sous forme de variable, puis transmettez la variable dans l'appel de service.
// *** old practice ***
var params = {
"param1" : "paramValue1",
"param2" : "paramValue2"
}
Thing["thingName"].CallServiceName(params);
// *** preferred practice ***
Thing["thingName"].CallServiceName({
"param1" : "paramValue1",
"param2" : "paramValue2"
});
Evitez d'utiliser des services qui appellent la base de données à l'intérieur d'une boucle.
Tables d'informations
Pour créer une table d'informations, utilisez la méthode suivante :
// common way
var table = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName : "InfoTable",
dataShapeName : "GenericStringList"
});
// easier way
var table = DataShapes["GenericStringList"].CreateValues();
Pour obtenir la longueur ou la ligne d'une table d'informations, utilisez la méthode suivante :
// fetching a row
myInfoTable[i] = myInfoTable.rows[i] = myInfoTable.getRow(i)
// getting the length
myInfoTable.length = myInfoTable.rows.length = myInfoTable.getRowCount()
Pour accéder aux données de la première ligne de la table d'informations, utilisez la notation "point" :
// 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
Utilisez des méthodes de tableau, telles que .map(), .reduce(), .filter(), .some(), .every() pour manipuler les tables d'informations ou pour apporter des modifications à plusieurs lignes de table d'informations. Vous pouvez recourir à ces méthodes pour les tables d'informations si vous utilisez infotable.rows.toArray().[map() | reduce() | filter()].
Utilisation de .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;
Application de modifications à plusieurs lignes de table d'informations :
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;
Utilisation de .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);
Pour itérer et parcourir en boucle les données d'une table d'informations, utilisez une boucle .forEach(). Cela améliore les performances.
table.rows.toArray().forEach(row => {
data += row.item;
});
Pour les tables d'informations de clonage profond, utilisez var deepClonedInfoTable = myInfoTable.clone().
Messages des enregistreurs
Dans vos instructions try … catch, nommez votre objet d'exception err.
try {
Things["thingName"].CallServiceName();
} catch (err) {
logger.error(err);
} finally { // finally is optional
// will go here whether an exception occured or not
}
* 
La journalisation de l'erreur (err ici) dans un format JSON n'est pas recommandée. Les bonnes pratiques recommandent plutôt d'utiliser err.message.
* 
Pour plus d'informations sur la journalisation JavaScript, consultez la rubrique Journalisation d'exécution JavaScript.
Chaque fois que vous créez un objet, entourez-le toujours d'une instruction try catch et supprimez-le en cas d'échec dans l'instruction catch. Cela vous évitera de créer des entités fantômes. Si vous avez des entités fantômes dans votre système, référez-vous à la section Services de gestion d'entités fantômes pour supprimer vos entités fantômes.
Chaque fois qu'un service crée une entité, assurez-vous de gérer un cas d'utilisation d'entité fantôme.
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 */
});
}
Si vous souhaitez afficher des journaux d'erreurs dans les journaux du script de surveillance, incluez entre autres informations le nom de l'objet, le nom du service, le numéro de ligne et le message d'erreur d'origine. N'oubliez pas que vous pouvez utiliser {} comme marque de réservation pour le formatage de chaînes dans votre journal. Ayez conscience que le message formaté peut révéler des informations superflues susceptibles d'entraîner des problèmes de sécurité. Evitez d'afficher trop de détails si vous faites appel à des services qui seront utilisés dans une application composite ou des systèmes externes.
Les propriétés suivantes sont disponibles dans un objet d'exception :
err.lineNumber : numéro de la ligne où l'exception a été levée.
err.name : type d'exception, par exemple, JavaException.
err.fileName : nom du service qui a déclenché l'exception.
err.message : message de l'exception.
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.
En fonction de vos besoins de granularité, utilisez le niveau de consignation approprié.
Veillez à limiter l'écriture dans les enregistreurs au strict nécessaire. Un trop grand nombre de messages de consignation rendent les débogages impossibles.
Niveau
Description
DEBUG
Evénements d'information à granularité fine essentiellement utiles pour le débogage d'une application.
// 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
Messages d'information décrivant le comportement de l'application à un niveau de granularité grossier.
// 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
Situations potentiellement dommageables.
// 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
Evénements d'erreur qui n'empêchent pas forcément l'application de continuer à s'exécuter.
// 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);
}
Est-ce que cela a été utile ?