团队代码该如何规范?

下面这段代码是同事写的(用了ts),代码能读懂,功能也实现了,但我觉得这种代码不易理解,代码注释少,不知道为什么要这么做,个人感觉这种代码时间一久自己都会忘记当初为什么要这么写

export function initFormConfig (vm: any, ret: any = {}, conf: any = {},  type: any = 'fill') {
  PopButton.getBtns(vm, ret, conf, type === 'edit' && !conf.id ?  'new' : type);
  let formConfig:any = [];
  let viewFormConfig: any = [];
  if (['new', 'edit'].includes(type)) {
    formConfig =  [
      { label: 'ID', key: 'id', compType: 'text' },
      // { label: i18n.t('autoPage.Market_Price'), key: '', compType: 'textInput' },
      { label: i18n.t('autoPage.Treasure_Picture'), key: 'thumb', compType: 'imageList', limit: 5, require: true },
      { label: i18n.t('autoPage.Treasure_Price'), key: 'price', compType: 'textInput', require: true },
      { label: i18n.t('autoPage.Treasure_Currency'), key: 'coinId', compType: 'dropdown', i18nMap: ret.coinI18n || {}, require: true },
      { label: i18n.t('autoPage.Commodity_Status'), key: 'status', compType: 'radio', i18nMap: optionMaps.enable, require: true },
      { label: i18n.t('autoPage.Product_Details'), key: 'details', compType: 'tinymce' , require: true},
      {
        key: 'multiLang', compType: 'multiLang', require: true, forms: [
          { label: i18n.t('autoPage.Baby_Name'), key: 'title', compType: 'textInput' },
          { label: i18n.t('autoPage.Subtitle'), key: 'description', compType: 'textInput' },
        ],
      },
    ];
  }
  if (['view', 'viewGoods'].includes(type)) {
    viewFormConfig =  [
      { label: 'ID', key: 'id', compType: 'text' },
      // { label: i18n.t('autoPage.Market_Price'), key: '', compType: 'textInput' },
      { label: i18n.t('autoPage.Treasure_Picture'), key: 'thumb', compType: 'imageList', limit: 5, disabled: true, require: true},
      { label: i18n.t('autoPage.Treasure_Price'), key: 'price', compType: 'text', require: true },
      { label: i18n.t('autoPage.Treasure_Currency'), key: 'coinId', compType: 'dropdown', i18nMap: ret.coinI18n || {}, disabled: true, require: true },
      { label: i18n.t('autoPage.Commodity_Status'), key: 'status', compType: 'radio', i18nMap: optionMaps.enable, disabled: true, require: true },
      { label: i18n.t('autoPage.Product_Details'), key: 'details', compType: 'tinymce', disabled: true, require: true },
      {
        key: 'multiLang', compType: 'multiLang', require: true, forms: [
          { label: i18n.t('autoPage.Baby_Name'), key: 'title', compType: 'text', require: true },
          { label: i18n.t('autoPage.Subtitle'), key: 'description', compType: 'text', require: true },
        ],
      },
    ];
  }
  if (type === 'editStage') {
    formConfig =  [
      { label: i18n.t('autoPage.Period_Management'), key: 'stage', compType: 'stage', require: true},
    ];
  }
  return {
    viewForm: viewFormConfig,
    checkForm: [],
    editForm: formConfig,
  };
}
// 初始化表单数据方法
export function initFormData (vm: any, ret: any = {}, conf: any = {}, type: string = 'edit') {
  ret.title = conf.id ? `编辑商品` : `新增商品`;
  type === 'editStage' && (ret.title = i18n.t('autoPage.Edit_Product_Period'));
  if (type === 'editStage') {
    return {
      id: conf.id || null,
      stage: {
        count: conf.count || 0,
        period: conf.period || 0,
        productId: conf.id || null,
        stageNo: conf.stageNo || null,
      },
    };
  } else {return {
    thumb: conf.thumb && conf.thumb.split(/::divide::/).map((item: string) => ({url: item, name: item})) || [],
    thumbInitCopy: conf.thumb && conf.thumb.split(/::divide::/).map((item: string) => ({url: item, name: item})) || [],
    id: conf.id || null,
  };}
}
export function saveOrUpdate (vm: any, ret: any, conf?: any, type?: string, otherParams?: any, formData?: any) {
  let sentDate;
  if (['new', 'edit'].includes((type || ''))) {
    sentDate = mergeAndDelete(
      formData,
      ['coinId', 'details', 'id', 'price', 'status', 'thumb'],
      (res: any, obj: any) =>({
        thumb: obj.thumb && obj.thumb.map((item: any) => item.url).join('::divide::'),
        // startDate: obj.date[0],
        // endDate: obj.date[1]
      }),
      (res, obj) =>({}),
    );

    if (!checkBeforeSave(vm, sentDate,[
      {check: checkNotNull(sentDate.thumb), fail: '请上传夺宝商品图片'},
      {check: checkNotNull(sentDate.coinId), fail: '请选择夺宝币种'},
      {check: checkNotNull(sentDate.price), fail: '请输入夺宝价格'},
      {check: checkNotNull(sentDate.details), fail: '请输入夺宝详情'},
      {check: checkI18nKey(sentDate, 'title', true), fail: '宝贝名称未填写完整'},
      {check: checkI18nKey(sentDate, 'description', true), fail: '副标题未填写完整'},
    ])) {
      return true;
    }
    Api.products.product.post(sentDate).then((res) => {
      ret.visible = false;
      vm.submitForm('ruleFormRef');
    });
  } else {
    sentDate = mergeAndDelete(
      formData,
      ['id', 'stage', 'flag'],
      (res: any, obj: any) =>({}),
      (res, obj) =>({}),
    );

    if (!checkBeforeSave(vm, sentDate,[
      {check: checkNotNull(sentDate.stage.count), fail: '请输入开奖注数'},
      {check: checkNotNull(sentDate.stage.period), fail: '请输入开奖间隔'},
    ])) {
      return true;
    }
    formData.flag || Api.products.stage.put({...sentDate.stage}).then((res) => {
      ret.visible = false;
      vm.submitForm('ruleFormRef');
    });
  }
  return undefined;
}

下面这段代码是我写的,我的代码虽然没有牛逼,但看上去算简洁,每一步都知道在干嘛,为什么要这样做

// 获取alln旅行计划信息
    getAllnPlan(){
      this.getAllnProductList_action()
        .then(({data}) => {
          this.loading = false;
          let allnProductInfo = {
            ...data
          };
          delete allnProductInfo.allnProductConfigList;
          this.allnProductInfo = allnProductInfo;
          this.allnProductList = data.allnProductConfigList;

          let choosedProduct = this.allnProductList[this.activeTab];
          this.choosedProduct = choosedProduct;

          let investPopupData = this.investPopupData;
          investPopupData.minInvestAmount = allnProductInfo.minIncomeRate;
          investPopupData.maxInvestAmount = allnProductInfo.maxIncomeRate;
          investPopupData.investRate =  choosedProduct.investIncome;
          investPopupData.investDays = choosedProduct.investDay;
          investPopupData.productId = choosedProduct.id;
          investPopupData.allnId = choosedProduct.allnId;

          this.$nextTick(() => {
            this.setTrianglePosition();
          });
        })
        .catch(() => {
          this.loading = false;
        })
    },
    // 设置产品列表小三角的位置
    setTrianglePosition(){
      if(!this.$refs.product_tabs){
        return;
      }
      let productTabs = this.$refs.product_tabs.$el;
      let vanTabsNav = productTabs.querySelector('.van-tabs__nav');
      let vanActiveTab = productTabs.querySelector('.van-tab.van-tab--active');
      let triangles = productTabs.querySelectorAll('.bubble-box-triangle');
      let vanTabsNavScrollLeft = vanTabsNav.scrollLeft; // 获取.van-tab父容器滚动条的位置
      let vanActiveTabLeft = vanActiveTab.offsetLeft; // 获取当前选中的.van-tab距离父容器最左侧的位置
      let vanActiveTabWidth = vanActiveTab.offsetWidth; // 获取当前选中的.van-tab的宽度
      let triangleWidth = (triangles[0]).offsetWidth; // 获取三角的宽度
      let left = vanActiveTabLeft - vanTabsNavScrollLeft + (vanActiveTabWidth / 2) - (triangleWidth / 2);

      /*console.log('vanTabs left',vanActiveTab.offsetLeft)
      console.log('vanTabsNavWidth',vanTabsNavScrollLeft)
      console.log('left',left)*/
      for(let i = 0, len = triangles.length; i < len; i++){
        (triangles[i]).style.left = left + 'px';
      }
    },
    // 立即投资按钮点击事件
    async investBtnClick(){
      let allnAccount = this.get_allnAccount;
      if(!allnAccount){
        this.$toast(this.$t('common.loadingWait')); //数据加载中,请稍后!
      }else {
        if(allnAccount.balance === 0 || allnAccount.balance < this.allnProductInfo.minIncomeRate){
          this.$toast(this.$t('common.loadingWait')); //资金不足!
          return;
        }
        // 表单校验
        let valid = await this.$validator.validate('product_index_investAmount.common_investAmount');
        if(!valid){
          this.$toast(this.errors.first('product_index_investAmount.common_investAmount'));
          return;
        }
        // 如果popup还未被加载出来,则将popup加载出来
        if(!this.investPopupShow){
          this.investPopupShow = true;
        }
        // 取最小投资数量整数倍
        this.investAmount = this.$intMultiple(this.investAmount, this.allnProductInfo.minIncomeRate);
        this.investPopupVisible = true;
      }
    },
    // 投资成功回调
    onInvestSuccess(){
      this.getAllnPlan();
    },
    // 头部组件右上角按钮点击事件
    onClickRight(){
      this.$router.push({name: 'alln_investAccount'});
    }

特此想问问大神们,代码规范应该怎么定?有什么原则?

阅读 3.4k
2 个回答

别的我就不多说了,你们这个用 ts 的话,全部都是 any 是什么鬼?这样的话,用和不用有什么区别?

一般来讲,如果不是公司有统一的规范要求,就直接用 standard 或者 google 以及 airbnb 这些比较成熟的代码规范,配合 eslint/tslintprettier 就可以了,如果觉的规则太繁琐,可以适当的自定义一下,改成适合自己项目和团队的风格即可。

一般这种规范属于锦上添花的东西,说句实话,老板是不会看代码写的好不好的,他们只关心代码能不能跑,项目能不能按时上线罢了。以我个人的经验,推行一套规范最难之处在于实施,所以为了保证良好的实时性,最好搭配 CI 等自动化流程工具来约束代码的提交,不要妄想每个人都有意识在提交代码的时候会 format 再提交。

最后再说点别的,其实我觉的好的代码,或者说可读性高的代码,不是说写满一堆注释,函数划分的无比细致,就是“好”的代码。我所认为的好,通常应该是在设计层和抽象层下功夫,就比如你同事的代码,虽然用了 ts,全篇都是 any,那直接写 js 好了,何必上 ts?反之,如果类型的设计和语义十分准确,同时业务层代码也引用了这些类型,那即使没有任何注释,相同水平的程序员也可以根据这些类型猜个八九不离十了。

所以我觉的,正确的使用工具,才是你们团队现在最应该考虑的问题。

规范一定是自上而下的。如果你和对方水平差不多,那么你想强推自己的习惯为规范,就很困难。

基本上,规范分为三类:

  1. 有巨大好处。以命名为例,变量用名词,函数用动词,布尔加 ishas 前缀等,类首字母大写,实例首字母小写等,这些一说大家就明白,使用规范比乱命名 abcd 好得多,基本很快就能接受。
  2. A方案与B方案差不多,但统一好处很大。比如用不用分号,哪里能换行,分号括号加在哪里等。这个因为好处不明显,需要领导强推。不过好在工具可以自动格式化。
  3. 逻辑比较复杂,难以规范。只能通过 code review 人工纠正,无法落实。

操作上,一般来说,随便找个 Google、Airbnb 的规范,就涵盖了上面的(1),然后领导再根据个人喜欢选择一些(2),就可以了。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进