关于前端业务层自定义规范

500 阅读7分钟

目录规范和说明

业务层

面向对象设计,不依赖于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 **】生成注释