目录规范和说明
业务层
面向对象设计,不依赖于Vuex。主要负责业务逻辑操作。
类大写,其连字符小写 如create_order
|-- Service
|-- domain 服务领域
|-- README.md
|-- Trade 交易领域
|-- module
|-- trade
|-- OrderModule
|-- PayModule
|-- helpers
|-- error.ts
|-- proxy.ts
状态层
vuex编写
|-- fontend_api
|-- customer
|-- Trade 交易领域
|-- module
|-- OrderModule
|-- PayModule
|-- managerment
命名规范
变量命名
使用驼峰式命名(constiableName)
常量(使用可搜索的名称)
推荐使用全大写+下划线隔开
//bad
//看代码的内心os: 86400000 是什么鬼
setTimeout(blastOff, 86400000);
// good
// 将它们声明为全局常量 `const` 。
const MILLISECONDS_IN_A_DAY = 86400000;
setTimeout(blastOff, MILLISECONDS_IN_A_DAY);
枚举
大驼峰命名
public enum UserRole {
ROLE_ROOT_ADMIN, // 系统管理员
ROLE_ORDER_ADMIN, // 订单管理员
ROLE_NORMAL // 普通用户
}
类名
- 承载了核心业务数据和核心业务逻辑,其命名要充分体现业务语义。
- 职责要求清晰,粒度精细,最好不要太过于宽泛,比如 ServicerModule, 或者出现base,common这种太宽泛的概念,应该具体描述这个类的功能。如TrainningClass(表明培训班类)。类名应该有该类所定义的属性来决定,比如类里面描述了一个培训班具体的信息,那他就是一个TrainningClass。而不是Commodity
- 对于辅助类(工具类),尽量不要用Helper、Util之类,因为其含义太过笼统。可以具体功能(处理的目标)为名。如Error.ts
构造函数
- 尽量不在里面写入业务逻辑
方法
-
偏业务化,尽量不要使用插入(删除,筛选)数据这种命名,例如
selectOrderByUserIdAndState尽量不使用by,userId,state这种数据库关键字或者字段化的概念。 可以使用queryUserOrders -
避免没有意义的命名如
processData()这样的命名并没有表明要做的事情,可以描述整个业务或者方法的实际意义如validateUserCredentials() -
方法名约定(待定)
所有action业务动作以do开头,如创建订单
doCreateOrder所有查询以query开头,如查询用户订单
queryUserOrder操作 方法名约定前缀 新增 doCreate 删除 doRemove 取消 doCancel 更新 doUpdate 查询(单个结果 doQuery 查询(多个结果) list(或者queryList) 分页查询 page(queryPage) 统计 [xxx]count 上传 doUpload 下载 doDownLoad 验证 doValidate
| 动词 | 含义 | 返回值 |
|---|---|---|
| can | 判断是否可执行某个动作 ( 权限 ) | 函数返回一个布尔值。true:可执行;false:不可执行 |
| has | 判断是否含有某个值 | 函数返回一个布尔值。true:含有此值;false:不含有此值 |
| is | 判断是否为某个值 | 函数返回一个布尔值。true:为某个值;false:不为某个值 |
| get | 获取某个值 | 函数返回一个非布尔值 |
| set | 设置某个值 | 无返回值、返回是否设置成功或者返回链式对象 |
| validate | 验证有效性 | 函数返回一个布尔值。true:有效;false:无效 |
访问控制
- 业务层提供给状态层的方法尽量只有一个入口,比如只暴露了init方法,至于函数里面的细节函数 private 私有化。尽量排除使用方的干扰。
- 业务模块化,一个模块负责一段具体的业务操作。至于业务操作的内部细节不对外暴露。只提供业务接口
类开发建议
使用方法链
在的类/方法中, 简单的在每个方法的最后返回 this , 然后就能把这个类的 其它方法链在一起。
class Car {
constructor() {
this.make = 'Honda';
this.model = 'Accord';
this.color = 'white';
}
setMake(make) {
this.make = make;
return this;
}
setModel(model) {
this.model = model;
return this;
}
setColor(color) {
this.color = color;
return this;
}
save() {
console.log(this.make, this.model, this.color);
return this;
}
}
const car = new Car()
.setColor('pink')
.setMake('Ford')
.setModel('F-150')
.save();
开闭原则 / 单一职责(合一起说说策略模式)
策略模式:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
开闭原则:对于扩展是开放的,但是对于修改是封闭的”
单一职责:一个函数或者类最好只负责一件事。和开闭相关,避免后续需求增加破坏原来的代码逻辑。导致回归测试。
/*
当价格类型为“预售价”时,满 100 - 20,不满 100 打 9 折
当价格类型为“大促价”时,满 100 - 30,不满 100 打 8 折
当价格类型为“返场价”时,满 200 - 50,不叠加
当价格类型为“尝鲜价”时,直接打 5 折
*/
//bad 违背了“单一功能”原则,违背了“开放封闭”原则,后续增加xx价,只能继续if-else()
// 询价方法,接受价格标签和原价为入参
function askPrice(tag, originPrice) {
// 处理预热价
if(tag === 'pre') {
if(originPrice >= 100) {
return originPrice - 20
}
return originPrice * 0.9
}
// 处理大促价
if(tag === 'onSale') {
if(originPrice >= 100) {
return originPrice - 30
}
return originPrice * 0.8
}
// 处理返场价
if(tag === 'back') {
if(originPrice >= 200) {
return originPrice - 50
}
return originPrice
}
// 处理尝鲜价
if(tag === 'fresh') {
return originPrice * 0.5
}
}
//good
// 定义一个询价处理器对象
// 这时候如果有新的需求增加,只需要以添加代替修改。在priceProcessor增加一对映射关系。老逻辑不用管!
// 获得成就【测试之友】
const priceProcessor = {
pre(originPrice) {
if (originPrice >= 100) {
return originPrice - 20;
}
return originPrice * 0.9;
},
onSale(originPrice) {
if (originPrice >= 100) {
return originPrice - 30;
}
return originPrice * 0.8;
},
back(originPrice) {
if (originPrice >= 200) {
return originPrice - 50;
}
return originPrice;
},
fresh(originPrice) {
return originPrice * 0.5;
},
};
// 询价函数
function askPrice(tag, originPrice) {
return priceProcessor[tag](originPrice)
}
关于引用类型的数据返回个UI层
因为引用类型的地址一样,当UI层修改了业务层返回的数据(比如Ui需要展开关闭的效果,他可能往数据上添加一个expand的字段)这会影响到业务层。所以当能确定返回的数据不允许双向绑定时,返回深克隆后的对象。
错误拦截
-
try..catch 捕获到异常代码之后切记在开发环境把错误日志打印出来。
-
无法捕获的异常,异步回调中,回调函数的执行栈与原函数分离开,导致外部无法抓住异常。
-
function fetch(callback) { setTimeout(() => { throw Error('请求失败') }) } try { fetch(() => { console.log('请求处理') // 永远不会执行 }) } catch (error) { console.log('触发异常', error) // 永远不会执行 } // 程序崩溃 // Uncaught Error: 请求失败function fetch(callback) { return new Promise((resolve, reject) => { setTimeout(() => { throw Error('用户不存在') }) }) } fetch().then(result => { console.log('请求处理', result) // 永远不会执行 }).catch(error => { console.log('请求处理异常', error) // 永远不会执行 }) // 程序崩溃 // Uncaught Error: 用户不存在 -
使用类装饰器业务场景统一异常捕获(做不做待定)
//类级别装饰器 const asyncClass = (errorHandler?: (error?: Error) => void) => (target: any) => { Object.getOwnPropertyNames(target.prototype).forEach(key => { const func = target.prototype[key] target.prototype[key] = async (...args: any[]) => { try { await func.apply(this, args) } catch (error) { errorHandler && errorHandler(error) } } }) return target } // 我是分割线 const iAsyncClass = asyncClass(error => { console.log('统一异常处理', error) // 统一异常处理 b }) const successRequest = () => Promise.resolve('a') const failRequest = () => Promise.reject('b') @iAsyncClass class Action { async successReuqest() { const result = await successRequest() console.log('successReuqest', '处理返回值', result) } async failReuqest() { const result = await failRequest() console.log('failReuqest', '处理返回值', result) // 永远不会执行 } async allReuqest() { const result1 = await successRequest() console.log('allReuqest', '处理返回值 success', result1) const result2 = await failRequest() console.log('allReuqest', '处理返回值 success', result2) // 永远不会执行 } } const action = new Action() action.successReuqest() action.failReuqest() action.allReuqest()
-
需要了解的工具库
- lodash
- 开发中最常见的工具库了,没有之一。
- 常用方法深拷贝(
cloneDeep),是否为空(isEmpty)
- moment
- 日期处理工具
注释(包含插件)
-
仅仅对包含复杂业务逻辑的东西进行注释
-
注释是代码的讲解, 不是逐行翻译。
// bad function hashIt(data) { // The hash let hash = 0; // Length of string const length = data.length; // Loop through every character in data for (let i = 0; i < length; i++) { // Get character code. const char = data.charCodeAt(i); // Make the hash hash = ((hash << 5) - hash) + char; hash &= hash; } } // good function hashIt(data) { let hash = 0; const length = data.length; for (let i = 0; i < length; i++) { const char = data.charCodeAt(i); hash = ((hash << 5) - hash) + char; // Convert to 32-bit integer hash &= hash; }
-
-
注释规范
-
行内注释
// -> 用来显示表达式的结果, // >用来显示 console 的输出结果 function test() { // 测试函数 console.log('Hello World!'); // >Hello World! return 3 + 2; // ->5 } -
单行注释
// 我是一个莫得感情的函数,我在哪,我要干嘛? testFn(); -
多行注释
/* * 代码执行到这里后会调用setTitle()函数 * setTitle():设置title的值 */ setTitle(); -
函数注释
/** * @description: 跳转函数 * @param {string} url 跳转的地址 * @return {void} */ navigate(url: string) { uni.navigateTo({ url: url }) }
-
-
不要有日志式的注释(有git就够了)
-
尽量不要有注释的旧代码。理由同上。
-
关于插件,VScode的小伙伴可以使用 【 **koroFileHeader **】生成注释