JavaScript 코딩에 대한 모범 사례
코드 구성 및 서식 적용
맨 위에는 코드 논리를 작성하고 맨 끝에는 도우미 함수를 작성합니다.
맨 위에서 전역 변수를 선언합니다.
서비스에 줄이 150개를 초과하는 경우 코드를 함수 또는 여러 서비스로 분할합니다.
주석 수를 줄이고 코드 가독성을 개선하기 위해 변수 및 함수에 적절한 이름을 지정합니다.
ThingWorx Composer에서 기본 제공된 JavaScript 자동 서식 기능을 사용합니다.
코드 작성
조건문에서 true 및 false 사례를 사용합니다.
// *** 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.
자세한 내용은 HA 응용 프로그램에 대한 모범 사례를 참조하십시오.
다음 줄에서 린팅을 억제하려면 의견을 추가합니다. 이렇게 하면 린팅 라이브러리를 수정한 경우에만 처리할 수 있으며 다른 곳에선 계속 린팅되는 린팅 오류가 무시됩니다.
객체
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))를 사용합니다.
변수
var 대신 let을 사용하여 변수를 선언합니다. 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 값 변수에 대한 기본값을 설정하려면 ||(또는) 연산자를 사용합니다.
// *** 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";
* 
이는 false 문을 사용하기 때문에 기본값을 설정하는 이상적인 방법이 아닐 수 있습니다. 예를 들어, 값이 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);
}
도움이 되셨나요?