likes
comments
collection
share

代码精进之路 if/else 优化

作者站长头像
站长
· 阅读数 16

代码精进之路 if/else 优化

为了使代码更符合可阅读、可维护和可扩展性,可以考虑以下几点建议:

背景

事情还要从钉钉群里说起,平时喜欢在群里潜水的我,无意中看到了一位群友发布了一张公司内部的业务逻辑图,这里多的if else心里当时就激动起来,如果是我,应该如何优雅的对这段代码进行优化重构~

if (!this.data.info.name) {
  wx.showToast({ title: "请输入商品名称", icon: "none" });代码精进之路 if/else 优化
} else if (this.data.actived == 2 && this.data.isRawxx.a) {
  wx.showToast({ title: "智能推荐距离不能位空", icon: "none" });代码精进之路 if/else 优化
} else if (!thisdata.info.category) {
  wx.showToast({ title: "规格不能为空", icon: "none" });
} else if (!this.data.info.goodsArea) {
  wx.showToast({ title: "请选择商品地址", icon: "none" });
} else if (!this.data.info.goodsDetailedAddress) {
  wx.showToast({ title: "请输入商品详细地址", icon: "none" });
} else if (!this.data.info.content) {
  wx.showToast({ title: "请输入商品描述", icon: "none" });
} else if (this.name === 2 && this.c.r == 2) {
  wx.showToast({ title: "请输具体方案", icon: "none" });
} else {
  console.log("结束了,头都已经大了");
}

阅前必读

首先需要阐述一个事实就是,方案一定不是最完美和最合适的,不过初衷是想在文章当中体现个人的解决思路,针对这类的代码如何进行合理重构和优化

勿喷~,仅持有个人观点进行客观分析~

实现思路概念

有很多方法可以进行if逻辑优化,以下是几个常用的手段:

  • 策略模式 (策略 mode) :这里是实在是整不出几句英文
  • 对象字典(Object Literal):
  • 提前返回(early return):
  • 枚举类型(enum):
  • 异常处理 (try/catch) :

优先介绍我个人觉得最为优雅的方案:策略模式,其他的都是一些平时常用的,仅供娱乐阅读。

策略模式

将每个条件和它对应的代码逻辑定义为一个对象,然后在一个字典中保存这些对象,用输入的条件作为字典中的索引键,通过调用键对应的函数,来避免众多分支嵌套,也更加易于扩展。

  1. 将所有错误信息保存为一个对象,可以更好地组织代码和添加更多的错误信息:
const errors = {
  name: "请输入商品名称",
  distance: "智能推荐距离不能为空",
  category: "规格不能为空",
  address: "请选择商品地址",
  detailedAddress: "请输入商品详细地址",
  content: "请输入商品描述",
  
};
  1. 将验证逻辑存储在数组中,并通过循环进行检查,这可以使验证更易于扩展:
const validations = [
  { prop: "name", required: true },
  { prop: "distance", required: true, condition: this.data.actived == 2 && this.data.isRawxx.a },
  { prop: "category", required: true },
  { prop: "address", required: true },
  { prop: "detailedAddress", required: true },
  { prop: "content", required: true }
];

let error = null;
validations.find(v => {
  const propName = v.prop;
  const required = v.required;
  const condition = v.condition;
  if (required && !this.data.info[propName]) {
    error = errors[propName];
    return true;
  }
  if (condition && !this.data.info[propName]) {
    error = errors[propName];
    return true;
  }
  return false;
});

if (error) {
  wx.showToast({ title: error, icon: "none" });
} else {
  console.log("1");
}

在上面的代码中,我们创建了一个名为 validations 的数组,该数组存储所有必填字段并检查它们是否已填写。对于延迟验证的条件,我们可以添加 condition 属性。

使用 validations.find() 来循环遍历所有验证规则。如果找到任何问题,将选择第一项并退出循环。

该优化方法可以让代码更容易阅读和维护。它还更易于扩展,如果需要添加更多的验证规则,只需简单地添加一个新对象即可。这个优化方法使用了一些现代的 JavaScript 技巧,例如模板字面量,解构和箭头函数。

完善字段更加语义化,看个人

完善字段,实现思路如下:

  1. 将所有错误信息保存为一个对象,可以更好地组织代码和添加更多的错误信息:这一步还是不变
const errors = {
  name: "请输入商品名称",
  distance: "智能推荐距离不能位空",
  category: "规格不能为空",
  address: "请选择商品地址",
  detailedAddress: "请输入商品详细地址",
  content: "请输入商品描述",
  solution: "请输入具体方案"
};
  1. 将验证条件分离出来,并且将其转化为数组,增强代码的可读性
const validations = [
  { prop: "name", error: "name", check: this.data.info.name, message: errors["name"] },
  { prop: "distance", error: "distance", check: this.data.isRawxx.a && this.data.actived == 2, message: errors["distance"], require: true },
  { prop: "category", error: "category", check: this.data.info.category, message: errors["category"], require: true },
  { prop: "address", error: "address", check: this.data.info.goodsArea, message: errors["address"], require: true },
  { prop: "detailedAddress", error: "detailedAddress", check: this.data.info.goodsDetailedAddress, message: errors["detailedAddress"], require: true },
  { prop: "content", error: "content", check: this.data.info.content, message: errors["content"], require: true },
  { prop: "solution", error: "solution", check: this.name === 2 && this.c.r == 2, message: errors["solution"], require: true }
];

let error = null;
const failValidation = validations.find(validation => {
  const { prop, error: e, check, message, require } = validation;
  if (require && !check) {
    error = message;
    return true;
  }
  if (!require && check) {
    error = message;
    return true;
  }
  return false;
});

if (failValidation) {
  wx.showToast({ title: error, icon: "none" });
} else {
  console.log("1");
}

在上面的代码中,我们使用了模板字面量以及解构的方式完成了验证操作,通过将所有的验证条件存到数组对象中,方便阅读、添加新的验证规则。

该优化方法可以让代码更容易阅读和维护。它还更易于扩展,如果需要添加更多的验证规则,只需简单地添加一个新对象即可开始扩展。

对象字典

可以使用对象字典(Object Literal),将对应的处理函数作为值存储在键中,来代替if/if-else语句,这种方式也被称为“策略模式”。这种方法简化了代码,使其更容易扩展,同时也使它更容易理解和维护

// 处理函数:输入商品名称
const inputProductName = () => {
    wx.showToast({ title: "请输入商品名称", icon: "none" });
}

// 处理函数:智能推荐距离不能为空
const recommendDistanceNotEmpty = () => {
    wx.showToast({ title: "智能推荐距离不能为空", icon: "none" });
}

// 处理函数:规格不能为空
const categoryNotEmpty = () => {
    wx.showToast({ title: "规格不能为空", icon: "none" });
}

// 处理函数:请选择商品地址
const chooseGoodsArea = () => {
    wx.showToast({ title: "请选择商品地址", icon: "none" });
}

// 处理函数:商品详细地址不能为空
const detailedAddressNotEmpty = () => {
    wx.showToast({ title: "请输入商品详细地址", icon: "none" });
}

// 处理函数:请输入商品描述
const inputGoodsContent = () => {
    wx.showToast({ title: "请输入商品描述", icon: "none" });
}

// 处理函数:测试 getName 函数
const testGetName = () => {
    wx.showToast({ title: "我测试函数呢", icon: "none" });
}

// 为每个条件设置处理函数
const conditions = {
    "productNameIsEmpty": inputProductName,
    "recommendDistanceIsEmpty": recommendDistanceNotEmpty,
    "categoryIsEmpty":  categoryNotEmpty,
    "goodsAreaIsEmpty": chooseGoodsArea,
    "goodsDetailedAddressIsEmpty": detailedAddressNotEmpty,
    "goodsContentIsEmpty": inputGoodsContent,
    "getNameFailed": testGetName
};

// 获取条件列表和判断结果,这个顺序可以自行修改
const validationConditions = [
    { condition: () => !this.data.info.name, key: "productNameIsEmpty" },
    { condition: () => this.data.actived == 2 && this.data.isRawxx.a, key: "recommendDistanceIsEmpty" },
    { condition: () => !this.data.info.category, key: "categoryIsEmpty" },
    { condition: () => !this.data.info.goodsArea, key: "goodsAreaIsEmpty" },
    { condition: () => !this.data.info.goodsDetailedAddress, key: "goodsDetailedAddressIsEmpty" },
    { condition: () => !this.data.info.content, key: "goodsContentIsEmpty" },
    { condition: () => !getName(), key: "getNameFailed" }
]

// 依次检查每个条件并发出相应处理的通话
validationConditions.some((item) => {
    if (item.condition()) {
        const process = conditions[item.key];
        process && process();
        return true;
    }
});

// 无条件通过
console.log("1");

提前返回

在函数中首先检查一些条件,如果条件不满足则返回。这样代码中就不需要深嵌套的if-else语句,可以使代码更加清晰易读。

function showToast(options = {}) {
  const defaultOptions = { icon: "none" };
  wx.showToast(Object.assign({}, defaultOptions, options));
}

function check(){
  if (!this.data.info.name) {
    showToast({ title: "请输入商品名称" });
    return;
  }
  if (this.data.actived == 2 && this.data.isRawxx.a) {
    showToast({ title: "智能推荐距离不能位空" });
    return;
  }
  if (!thisdata.info.category) {
    showToast({ title: "规格不能为空" });
    return;
  }
  if (!this.data.info.goodsArea) {
    showToast({ title: "请选择商品地址" });
    return;
  }
  if (!this.data.info.goodsDetailedAddress) {
    showToast({ title: "请输入商品详细地址" });
    return;
  }
  if (!this.data.info.content) {
    showToast({ title: "请输入商品描述" });
    return;
  }
  if (this.name === 2 && this.c.r == 2) {
    showToast({ title: "请输具体方案" });
    return;
  }
  console.log("结束了,头都已经大了");
}

枚举类型 + switch

将一系列条件定义为枚举类型,然后使用switch语句进行判断,这样代码会更加简洁,并且易于维护和扩展。

const ERRORS = {
  NAME: "请输入商品名称",
  RECOMMEND: "智能推荐距离不能位空",
  CATEGORY: "规格不能为空",
  ADDRESS: "请选择商品地址",
  DETAILED_ADDRESS: "请输入商品详细地址",
  DESCRIPTION: "请输入商品描述",
  PLAN: "请输具体方案"
};

function showToast(options = {}) {
  const defaultOptions = { icon: "none" };
  wx.showToast(Object.assign({}, defaultOptions, options));
}

function check(){
  switch (true) {
    case !this.data.info.name:
      showToast({ title: ERRORS.NAME });
      return;
    case (this.data.actived === 2 && this.data.isRawxx.a):
      showToast({ title: ERRORS.RECOMMEND });
      return;
    case !thisdata.info.category:
      showToast({ title: ERRORS.CATEGORY });
      return;
    case !this.data.info.goodsArea:
      showToast({ title: ERRORS.ADDRESS });
      return;
    case !this.data.info.goodsDetailedAddress:
      showToast({ title: ERRORS.DETAILED_ADDRESS });
      return;
    case !this.data.info.content:
      showToast({ title: ERRORS.DESCRIPTION });
      return;
    case (this.name === 2 && this.c.r === 2):
      showToast({ title: ERRORS.PLAN });
      return;
    default:
      console.log("结束了,头都已经大了");
  }
}

异常处理

如果处理的数据可能出现错误、或不符合期望的条件,将这些错误抛出异常并进行单独处理,使得代码结构更为简洁,同时也避免被深度嵌套。

为什么要使用这个方案呢? 其实有的时候 this.data.info.name这种类似的获取值,真的会让人麻,虽然现在有了 ?. 的规避方式

const ERRORS = {
  NAME: "请输入商品名称",
  RECOMMEND: "智能推荐距离不能位空",
  CATEGORY: "规格不能为空",
  ADDRESS: "请选择商品地址",
  DETAILED_ADDRESS: "请输入商品详细地址",
  DESCRIPTION: "请输入商品描述",
  PLAN: "请输具体方案"
};

function showToast(options = {}) {
  const defaultOptions = { icon: "none" };
  wx.showToast(Object.assign({}, defaultOptions, options));
}

function doSomething() {
  // 模拟异步处理方案
  return new Promise((resolve, reject) => {
    // 处理方案
    if (this.name === 2 && this.c.r === 2) {
      reject(ERRORS.PLAN);
    } else {
      resolve();
    }
  });
}

function check() {
  try {
    // 检查输入
    if (!this.data.info.name) {
      throw ERRORS.NAME;
    }
    if (this.data.actived === 2 && this.data.isRawxx.a) {
      throw ERRORS.RECOMMEND;
    }
    if (!thisdata.info.category) {
      throw ERRORS.CATEGORY;
    }
    if (!this.data.info.goodsArea) {
      throw ERRORS.ADDRESS;
    }
    if (!this.data.info.goodsDetailedAddress) {
      throw ERRORS.DETAILED_ADDRESS;
    }
    if (!this.data.info.content) {
      throw ERRORS.DESCRIPTION;
    }

    // 处理方案  我花里胡哨使用一个Promsie
    doSomething().then(() => {
      console.log("结束了,头都已经大了");
    }).catch((error) => {
      showToast({ title: error });
    });
  } catch (error) {
    showToast({ title: error });
  }
}

API层面的优化方案

网上的一些优化方案,就大致讲一下,不做细讲了~ 在if分支上不过过多体现

function test(fruit) {
  if (fruit == 'apple' || fruit == 'strawberry') {
    console.log('red');  
  }
  console.log(111);
}

Array.includes

function test(fruit) {
  const list = ['apple', 'strawberry'];
  if (list.includes(fruit)) {
    console.log('red');  
  }
  console.log(111);
}

上述使用 Array.includes 处理多重条件的方式已经是比较好的处理方式了,可以有效地简化代码,提高可维护性和可读性.

其实这个方案还是有弊端的,比如数字1字符串1 这里需要明确类型~

function test(fruit) {
  const list = [1,"2"];
  // 数字2 是不能被 includes 命中
  if(list.includes(fruit)){
  	 console.log('red');  
	}
  console.log(111);
}
test(2);

Set方案

另外,如果需要对数组中的条件进行动态添加或删除,可以使用 Set 类型来存储条件,例如:

function test(fruit) {
  // 把条件存储在 Set 中
  const redFruits = new Set(['apple', 'strawberry']);
  redFruits.add('cherry');
  redFruits.add('cranberries');

  if (redFruits.has(fruit)) {
    console.log('red');
  }
}

使用 Set 可以通过 add 和 delete 方法来动态添加和删除条件,同时也可以使用 Set.size 属性获取条件的个数。使用 Set 可以更加灵活地管理条件,但是需要注意 Set 的兼容性和性能等问题。

总结

实现的思路千奇百怪,在业务中选择自己最适合自己的方案,业务才是最重要的~

其次,感觉不封装才是最好的封装~