使用 JavaScript 编码的最佳做法
组织和格式化代码
• 在顶部编写代码逻辑,在末尾编写 helper 函数。
• 在顶部声明全局变量。
• 如果服务中的行数超过 150,请将代码分为函数或多个服务。
• 适当命名变量和函数以减少备注数,并提高代码的可读性。
• 使用 ThingWorx Composer 中内置的 JavaScript 自动格式化功能。
编写代码
• 在条件语句中使用 truthy 和 falsy 情况。
// *** 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
• 执行批处理 CRUD 操作以最小化与数据库的交互作用。但是,如果从数据库接收到过多信息,则可能希望对数据库执行多次单一调用。
// *** 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.
• 避免直接处理事物属性,否则可能会导致持续同步问题以及性能损失。
// *** 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.
• 要在下一行中隐含 linting,请添加备注。这会略过只能通过修复 linting 库来解决的 Linting 错误,而仍然在其他地方获取 Linting。
对象
◦ 用 {} 代替 new Object(); 来创建对象。尝试创建内联对象值,因为它更易于读取且写入速度更快。
// *** 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"
};
◦ 对于深层复制对象,请使用 JSON.parse(JSON.stringify(object))。
变量
◦ 用 let 代替 var 来声明变量。用 const 来声明常量变量。
使用 let 会阻止您重新声明和覆盖变量。这样做也会阻止创建具有相同名称的函数。
// *** 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
◦ 使用骆驼式命名法命名变量。
◦ 要为 undefined 或 null 值设置默认变量值,请使用 || (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";
|
这可能不是设置默认值的理想方式,因为它使用了 falsy 语句。例如,如果您的值为 0,则 || 运算符可能会设置另一个非 0 值。
|
◦ 使用点符号来访问变量。括号符号仅在键中存在特殊字符时使用,例如:object.row["field name with space"]。
◦ 创建变量以存储事物,并于循环中使用此变量。
// *** 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;
}
函数
◦ 使用骆驼式命名法命名函数。
◦ 将函数声明为函数声明,而非变量。
您无法将函数表达式和函数箭头表达式移至代码底部。
// *** 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;
}
◦ 利用函数中的 return 来中断代码逻辑流。这样做有助于保存某些 CPU 操作并提高可读性。
// 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.
服务
◦ 使用帕斯卡命名法命名服务。
◦ 使用内联参数调用服务。但是,如果输入参数很大,或者希望重用这些参数,那么请将输入参数存储为变量,然后将该变量传递至服务调用。
// *** old practice ***
var params = {
"param1" : "paramValue1",
"param2" : "paramValue2"
}
Thing["thingName"].CallServiceName(params);
// *** preferred practice ***
Thing["thingName"].CallServiceName({
"param1" : "paramValue1",
"param2" : "paramValue2"
});
◦ 避免使用在循环中调用数据库的服务。
信息表
◦ 使用以下方法创建信息表:
// common way
var table = Resources["InfoTableFunctions"].CreateInfoTableFromDataShape({
infoTableName : "InfoTable",
dataShapeName : "GenericStringList"
});
// easier way
var table = DataShapes["GenericStringList"].CreateValues();
◦ 使用以下方法获取信息表的长度或行:
// fetching a row
myInfoTable[i] = myInfoTable.rows[i] = myInfoTable.getRow(i)
// getting the length
myInfoTable.length = myInfoTable.rows.length = myInfoTable.getRowCount()
◦ 使用点符号访问信息表中第一行的数据:
// 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
◦ 使用数组方法,例如 .map()、.reduce()、.filter()、.some() 或 .every(),来操控信息表或对多个信息表行进行更改。如果使用了 infotable.rows.toArray().[map() | reduce() | filter()],则可将这些方法用于信息表。
使用 .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;
将更改应用于多个信息表行:
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;
使用 .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);
◦ 要迭代和遍历信息表中的数据,请使用 .forEach() 循环。这样一来,性能便会得以提高。
table.rows.toArray().forEach(row => {
data += row.item;
});
◦ 对于深层复制信息表,请使用 var deepClonedInfoTable = myInfoTable.clone()。
记录器消息
◦ 在 try … catch 语句中,将异常对象命名为 err。
try {
Things["thingName"].CallServiceName();
} catch (err) {
logger.error(err);
} finally { // finally is optional
// will go here whether an exception occured or not
}
|
建议不要将错误 (err) 记录为 JSON 格式,且必须改为将 err.message 用作最佳做法。
|
◦ 每次创建事物时,均在其周围使用
try catch 语句,并在
catch 语句失败的情况下将其删除。这样做可消除幻影实体的创建。如果您的系统中有幻影实体,请参阅
幻影实体服务以删除幻影实体。
每当有创建实体的服务时,都要确保处理幻影实体用例。
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 */
});
}
◦ 如果您有任何要在监控脚本日志中显示的错误日志,那么请包括事物名称、服务名称、行号和原始错误消息等信息。切记,您可在日志中将 {} 用作字符串格式的占位符。请注意,经格式化的消息会泄露可能导致安全问题的不必要信息。如果您正在处理将在混搭或外部系统中使用的服务,请避免显示过多的详细信息。
异常对象中提供了如下属性:
▪ err.lineNumber - 引发异常的行号。
▪ err.name - 异常类型,例如 JavaException
▪ err.fileName - 从中触发异常的服务名称。
▪ err.message - 异常消息。
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.
◦ 请根据您的粒度需求使用适当的记录器级别。
仅在必要时写入记录器。日志消息过多会导致调试无法进行。
级别
|
说明
|
DEBUG
|
指定最适用于调试应用程序的细粒度信息性事件。
// 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
|
指定在粗粒度级别突出显示应用程序进度的信息性消息。
// 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
|
指定潜在的有害情况。
// 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
|
指定可能仍允许应用程序继续运行的错误事件。
// 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); }
|