let oneComments = newComments();
– 变量为单数时,命名包含对象名称,如:level1Catalog、level2Catalog。
– 变量为复数时,命名包含集合名称,如:attributeRelationRuleList。
2.2.2 注释
本节包含脚本注释的原则和写法。
总体原则
*/@action.method({ input: "Input", output: "Output", label: 'queryProductDetailForCart' })
● 方法内关键业务语句前必须添加注释。
let password = input.password;
if (accountRecord["Password"] != password) { error.name = "CM-001003";
error.message = "Invalid loginId or password.";
throw error;
}
2.2.3 引用
本节包含脚本间相互引用的原则。
● 脚本中不要包含没用到的标准库或者对象的引用。
例如,如果实际没用到sys模块的任何方法,则不要包含如下语句:
import * as sys from 'sys';
如果实际上却只用到了CA_PC_Offering对象,则示例语句中其他对象需要删除:
@useObject(['CA_PC_FeeItemDef', 'CA_PC_Offering', 'CA_PC_CatalogOffering', 'CA_PC_Catalog', 'CA_PC_OfferingAttribute'])
● 只引用用到的对象,而不用import *。
【推荐】:
import { OfferingObjectQuery } from './ca_pc__getOfferingObject';
import { setI18nError } from 'context';
import { sql } from 'db';
【不推荐】:
import * as queryOfferingObjectAction from './ca_pc__getOfferingObject';
import * as context from 'context';
import * as db from 'db';
● @action.method属于必须的方法注解,写在方法上面。
@action.method({ input: "Input", output: "Output", label: 'queryClassification' }) queryClassification(input: Input): Output {
...}
● 为方便页面直接调用脚本,仅在脚本最后统一导出需要的对象,而不是导出所有 对象。如果不需要可以不用导出。
例如,getOfferingObject脚本中最后一行导出OfferingObjectQuery对象:
export let theAction = new OfferingObjectQuery();
● 可以把公共的对象Object定义按分类放在一些公共的脚本里(例如pc_XXX.ts、
cm_XXX.ts),其他需要的脚本直接引用即可。
2.2.4 定义
本节包含字段、对象、结构体等的定义规则。
● 每个字段的定义,均需要定义type、label、description、required、
isCollection。有默认值的非必填。
● 当字段为集合类型时候,需要定义成[]。
@action.param({
type: "Attribute", label: "Attribute",
description: "attributeList", required:false
isCollection: true })attributeList: Attribute[];
● @action.object时,需要在脚本中详细定义清楚Object。不要引用其他脚本的 Object。
● 如果是嵌套结构体则从下到上粒度依次变小。
export class ProductObject { //ignore
}
export class StockObject { //ignore
}
export class ProductStock {
@action.param({ type: 'Struct', label: "ProductObject", isCollection: false }) productInfomation: ProductObject;
@action.param({ type: 'Struct', label: "StockObject", isCollection: false }) stock: StockObject;
}
@action.object({ type: "param" }) export class Input {
@action.param({ type: 'String', label: "productId", isCollection: false }) productId: String;
}
● 不需要多定义Output对象,可以直接在方法使用定义的对象出参。
【不推荐】:但是如果出参对象包含从外部引入的对象,则还是要按该方式定 义。
@action.object({ type: "param" }) export class Input {
@action.param({ type: 'String', label: "productId", isCollection: false }) productId: String;
}
@action.object({ type: "param" }) export class Output {
@action.param({ type: 'Object', isCollection: false }) ProductStock: ProductStock;
}
@action.object({ type: "param" }) export class getProductStock {
@action.method({ input: "Input", output: "Output", label: 'getProductStock' }) getProductStock(input: Input): Output {
【推荐】:
@action.object({ type: "param" }) export class Input {
@action.param({ type: 'String', label: "productId", isCollection: false }) productId: String;
}
@action.object({ type: "param" }) export class getProductStock {
@action.method({ input: "Input", output: "ProductStock", label: 'getProductStock' }) getProductStock(input: Input): ProductStock {
● 除非业务有特殊要求,增修改脚本不返回结果码和结果信息。
2.2.5 调试
本节提供调试、日志代码的规则。
● try、catch
不需要做统一异常处理的话,都不用写try、catch语句,只需抛出统一的错误码。
【推荐】:
if (input.customerId == "") {
context.throwError("STO-001003");
};
【不推荐】:
if (input.customerId == "") {
context.setError('STO-001003', 'You have not login or the session has expired, please login again.');
return;
};
● 打印日志(Console.log())
看业务场景需要来控制打印日志语句,建议一个脚本最多不要超过三个打印日志
● 数字类型统一定义成Number、日期类型定义成Date。
@action.param({
type: "Date", label: "effectiveTime", description: "Date."
})effectiveTime: Date;
@action.param({
type: "Number", label: "salePrice", description: "salePrice."
})salePrice: number;
● 变量或者数组定义时要说明类型,集合需要加上泛型。
let isDone:Boolean = false;
let decLiteral : number =6;
let productList = new Array<productObject>();
● 遍历循环推荐用forEach,不推荐用for…in。
testArray.forEach((value, index, array )=>{
//ignore });
● 推荐用let变量声明,不推荐用var。
【不推荐】:
var offeringId = “aaa”;
【推荐】:
let offeringId : String = “aaa”;
● 前段代码有对象声明时,推荐使用.fieldName获取对象的字段,而不是用 [‘fieldName’]。
【不推荐】:
offeringStruct.id = result[i][‘base’][‘offeringId’];
【推荐】:
offeringStruct.id = result[i].base.offeringId;
● 在需要默认值的情况下,使用“||”代替“if”判断。
let getOfferingIdByConditionInput = { "OfferingIdRequest": {
let offeringIdRequest = new OfferingIdRequest();
offeringIdRequest.catalogList = catalogList;
offeringIdRequest.classificationList:=classificationList;
let getOfferingIdByConditionInput = { "OfferingIdRequest": offeringIdRequest }
● 函数定义
let traitRec = function(xxxx,xxx) { //ignore
}
只能在函数定义后的语句中使用该函数。
● 没有初始化值的变量申明,使用undefined,不要使用null。
let object = undefined;
● 局部变量需要在class内定义,不要在全局命名空间内定义类型/值(即不要在class 外定义变量),常量可以定义成全局。
let identityIdList = [];
● 使用lambda表达式代替匿名函数。
只有需要的时候才把arrow函数的参数括起来。下面是正确使用arrow的做法:
x => x + x (x,y) => x + y
<T>(x: T, y: T) => x === y
2.2.7 SQL
本节包括脚本中SQL的规则。
1. 不推荐用拼接SQL方法,避免注入风险。
【不推荐】:
let sql = "select id,name,ExternalCode,FeeType,FeeItemName,FeeItemDescription,Remark,Status from CA_PC_FeeItemDef where 1=1";
if (!InputParams.id && !InputParams.name && !InputParams.feeType && !InputParams.feeItemName
&& !InputParams.status) {
context.setI18nError("ca_pc__001013");
return;
}if (InputParams.id) {
sql += "and id ='" + InputParams.id + "'"
}
2. 多表复杂查询建议用sql.exec()或者sql.excute()方法。excute()方法比exec()多返 回字段集和操作成功数。
let result = execsql.exec("select
id,name,OfferingId,ParentId,SkuCode,ChannelId,Status,ProductLabel,PriceCode,DefaultChoose,Payment Type from CA_PC_Product where id in (" + str + ")",
{ params: productId });
多表复杂查询只能用拼接SQL方法,但是有限制,例如示例中的str要求如下:
– str如果来源于入参,则入参在拼接SQL之前需要进行校验,以免引入SQL注 入攻击;如果来源于内部数据,可以不进行校验。
– 不推荐直接在SQL中拼接入参,应该采用在SQL中拼接占位符,然后把入参放 入参数数组中的方式,例如:
attriSql = "select AttrDef from DE_DeviceDefAttri where DeviceDef=? and ExternalCode=? and AttrType='DYNAMIC' and ValueType='1'";
attriRecords = db.sql().exec(attriSql, { params: [deviceDefId, defExternalCode]
});
3. 对于单表查询和增删改SQL推荐用Orm接口方法。
Options选项可以选择返回字段、排序、聚合运算和分页等功能。
当value不存在时,默认为null类型。
let CA_PC_Stock = db.object('CA_PC_Stock');
let amountCount = 0;
if (UpdateStock.amount) {
let record = { Amount: UpdateStock.amount };
amountCount = CA_PC_Stock.updateByCondition({
"conjunction": "AND", "conditions": [{
"field": "SkuCode", "operator": "eq",
"value": UpdateStock.skuCode
}]
}, record);
}
4. 避免在循环中调用方法和操作数据库,可以用in来查询在集合中的结果。
let productIdList = input.productId;
let str = "";
let arr = [];
for (let i = 0; i < productIdList.length; i++) { arr[i] = "?";
}str = arr.toString();
let result = execsql.exec("select
id,name,OfferingId,ParentId,SkuCode,ChannelId,Status,ProductLabel,PriceCode,DefaultChoose,Payment Type from CA_PC_Product where id in (" + str + ")",
{ params: productIdList
var s = db.object('Customer__CST');
var records = [];
};records.push(record);
var ids = s.compositeInsert(records);
console.log("id list = ", ids);
count = s.count();
console.log("record count = ", count);
7. 匹配查询推荐用like,日期比较推荐用‘<’、‘>’
【不推荐】:
select id from t where substring(name,1,3)=’abc’
select id from t where datediff(day,createdate, ‘2005-11-30’)
【推荐】:
select id from t where name like ‘abc%’
select id from t where createdate>=’2005-11-30’ and createdate<’2005-12-1’
8. 使用exists替代in,使用not exists替代not in
【不推荐】:
SELECT * FROM EMP (基础表) WHERE EMPNO > 0 AND DEPTNO IN(SELECT DEPTNO FROM DEPT WHERE LOC =’MELB’)
【推荐】:
SELECT * FROM EMP (基础表) WHERE EMPNO > 0 AND EXISTS (SELECT ‘X’FROM DEPT WHERE DEPT.DEPTNO = EMP.DEPTNO AND LOC = ‘MELB’)
9. 避免在索引列上使用is null和is not null,会造成索引失效
【不推荐】:
SELECT … FROM DEPARTMENT WHERE DEPT_CODE IS NOT NULL
【推荐】:
SELECT … FROM DEPARTMENT WHERE DEPT_CODE >=0
10. 注意事项:
a. 尽量避免Select字句中使用“*”;
b. 尽量避免在where子句中使用!=或<>操作符;
c. 尽量避免在where子句中对字段进行函数操作;
d. 尽量避免在SQL中使用 “!=” 、“||”、“+”符号;
e. 尽量避免在where条件中做数据筛选;
f. 尽量避免查询中按字段排序;
l. 尽量利用对象做临时缓存:例如查询到DeviceDef后,按照id:Object的方式存 起来,后续查询时先判断缓存对象中是否已经存在,如果存在则直接获取不 再查询。
2.2.8 代码风格
本节包括代码风格建议。
● 每句话后面加分号,脚本写完右键选择“Format Document”统一格式。
● string类型赋值统一使用双引号,获取字段统一使用单引号。
● 相关代码写在一起,不相关逻辑最好以空行隔开。
function f(x: number, y: string): void { }
● 每个变量声明语句只声明一个变量
● 一个函数仅完成一件功能,即使是简单功能也应该编写单独的方法实现。
● 禁止通过字符串串联直接使用用户输入构造可执行SQL语句,降低SQL注入攻击的 风险。
● 禁止在代码和日志中存储敏感数据。
● 禁止密钥或帐号的口令以明文形式存储在数据库或文件中。
● 禁止使用自己开发的加密算法,必须使用公开、安全的标准加密算法。
2.2.10 调用约束
本节包括脚本的调用约束。
● 不允许在应用项目的Script中调用BO的script
例如, 设备管理应用的Script不可调用设备BO内的任何Script。
● 不允许跨BO调用script
例如,人员BO的Script不允许调用设备BO内的任何Script。
2.2.11 代码规范案例汇总
1. 脚本中的class和脚本名应该一致。
2. 不要把整个处理逻辑使用try catch包起来,这样会导致报错时无法知道原始错误 行,需要去掉try catch。
不推荐以下写法:
3. APP、BO中需要使用rest或者sdk来直接调用其它BO的内部服务编排。
4. APP、BO中需要使用rest或者sdk来直接调用其它BO的内部脚本。
5. 需要先定义错误码,才能正确的抛出错误码信息,否则会随意抛出错误。
6. 不允许跨BO、APP直接操作其它BO、APP的对象,应该调用rest或者sdk。
不推荐以下写法:
7. 对于确实需要查询所有数据的场景,需要考虑平台的查询记录数限制,并自行控 制游标查询。