前端代码优化之从系统区分处理的业务场景看如何优化代码中的if判断

news2024/11/26 2:34:50

最近有个三端统一的技术场景,主要是以前移动端的 hybrid 网页在不考虑 UI 适配的情况下、期望能够直接在 PC 客户端投放。在评估修改面的时候发现了一段可以深思的代码:

if (platform === 'iphone') {
  location.href = iphoneClientUrl;
} else {
  location.href = gphoneClientUrl;
}

其中platform是来自平台判断函数获得的当前系统标识、其值如'iphone'(iPhone)、'gphone'(安卓),iphoneClientUrl/gphoneClientUrl分别是 iPhone 和安卓应用的 URL Schemes 客户端协议跳转地址。

我们知道,根据不同系统/应用进行区分处理是常有的事、比如这里的调用不同协议,那么这段代码在当前面临适配 PC 运行的场景会有什么样的问题呢?

问题 1.不合理的兜底处理

首先如果直接在 PC 客户端投放的话,这段代码会直接走进else的执行分支、即会调用安卓的客户端协议跳转地址(gphoneClientUrl)。这种情况大概率是调不通的、会容易导致执行异常,比如跳到空白页之类。

所以这段代码的第一个问题就是不能让安卓逻辑的执行代码作为最后 else 的兜底,PC 端运行安卓 mobile 的代码容易出错。
为了修改这个问题、之前的代码可以改为:

if (platform === 'iphone') {
  location.href = iphoneClientUrl;
} else if (platform === 'gphone') {
  location.href = gphoneClientUrl;
} else {
  // 兜底处理
  console.log('当前系统未支持此协议调用');
}

这里增加了一个未识别平台的兜底处理,避免直接运行 mobile 端的兜底处理。

*这类兜底判断做法我们可以在很多大厂的代码中发现,如下是百度的一段:
p-baidu

问题 2.没有较好得遵循“开闭原则”

为了适配当前 PC 客户端的需求,这段代码现在还要对 PC 客户端的协议进行判断处理,如:

if (platform === 'iphone') {
  location.href = iphoneClientUrl;
} else if (platform === 'gphone') {
  location.href = gphoneClientUrl;
} else if (platform === 'windows') {
  location.href = windowsClientUrl;
} else {
  // 兜底处理
  console.log('当前系统未支持此协议调用');
}

那么问题可能又来了,如果要适配 Mac、iPad、Linux 甚至鸿蒙等系统这段代码又要进行调整,如:

if (platform === 'iphone') {
  location.href = iphoneClientUrl;
} else if (platform === 'gphone') {
  location.href = gphoneClientUrl;
} else if (platform === 'windows') {
  location.href = windowsClientUrl;
} else if (platform === 'mac') {
  location.href = macClientUrl;
} else if (platform === 'ipad') {
  location.href = ipadClientUrl;
} else if (platform === 'linux') {
  location.href = linuxClientUrl;
} else if (platform === 'harmony') {
  location.href = harmonyClientUrl;
} else {
  // 兜底处理
  console.log('当前系统未支持此协议调用');
}

也就是说,每当要适配一个新的系统就需要再增加一条 else 判断,那么这段代码就没有较好得遵循“开闭原则”、不易维护。因为这段代码的主体逻辑是根据不同平台进行协议跳转,而我们的改动只是增加一个新的平台处理、不应该对主体代码进行修改。
另外这样的代码也使得这段的代码重点迷失,从原本的关注根据 url 进行跳转变成了关注通过各分支进行跳转处理。

那么这段代码应该如何调整呢?先放调整后的参考代码:

const PLATFORM_CLIENT_URLS = {
  iphone: iphoneClientUrl,
  gphone: gphoneClientUrl,
  windows: windowsClientUrl,
  mac: macClientUrl,
  ipad: ipadClientUrl,
  linux: linuxClientUrl,
  harmony: harmonyClientUrl,
};

// 调用体
function jumpToClientUrl(platform) {
  const clientUrl = PLATFORM_CLIENT_URLS[platform];

  if (clientUrl) {
    location.href = clientUrl;
  } else {
    // 兜底处理
    console.log('当前系统未支持此协议调用');
  }
}

这里我们用对象字面量PLATFORM_CLIENT_URLS来收口各系统及其对应协议地址,抽象了根据不同平台进行协议跳转的主体逻辑至jumpToClientUrl方法中,这样做的好处是每当要适配或调整一个新的系统时,我们只需要修改PLATFORM_CLIENT_URLS即可,这个对象还可以放在配置文件中与运行时代码解耦从而使调整时甚至不用改动运行时代码。

当然,这种情况下比较简单,那么遇到稍复杂些的场景应该怎么样呢?

场景 1.各判断分支的判断条件或对应执行处理都不一样时。

继续延续前面代码的场景,首先看判断条件不一样的情况,比如假设

  • iPhone 需要大于 iOS10(osVersion >= 10
  • 安卓需要在安卓 6 ~ 10 区间(osVersion >= 6 && osVersion <= 8
  • windows 必须是 Windows 8.1 版本(osVersion === 8.1

这种情况下刚才的对象字面量方式就不能进行直接处理了,那么应该如何适配呢?

抽离判断分支,对刚才的对象字面量进行调整:

const PLATFORM_CLIENT_SCHEMA = {
  iphone: {
    rule: osVersion => osVersion >= 10,
    url: iphoneClientUrl,
  },
  gphone: {
    rule: osVersion => osVersion >= 6 && osVersion <= 8,
    url: gphoneClientUrl,
  },
  windows: {
    rule: osVersion => osVersion === 8.1,
    url: windowsClientUrl,
  },
  mac: {
    rule: osVersion => osVersion >= 0,
    url: macClientUrl,
  },
};

// 调用体
function jumpToClientUrl(platform, osVersion) {
  const clientSchema = PLATFORM_CLIENT_SCHEMA[platform];
  let jumpClientUrl = '';

  // 如果有规则且判断通过
  if (clientSchema?.rule?.(osVersion)) {
    jumpClientUrl = clientSchema.url;
  }

  if (jumpClientUrl) {
    location.href = jumpClientUrl;
  } else {
    // 兜底处理
    console.log('当前系统未支持此协议调用');
  }
}

我们对需要单独进行判断的系统场景进行了结构调整,将特殊判断用rule字段抽离,同样保持了适配一个新系统只需要调整对象(PLATFORM_CLIENT_RULES_AND_URLS)而不用修改jumpToClientUrl函数。

再看执行不一致的场景,假如

  • iPhone 是打开一个弹窗(Alert.show()
  • 安卓是调用 js 方法而不是跳转(callAndroidNative(gphoneClientUrl)
  • windows 是window.open()打开协议地址(window.open(windowsClientUrl)

这种情况下可以延续刚才判断条件的抽离、进行:

抽离执行语句,对刚才的对象字面量进行调整:

const PLATFORM_CLIENT_SCHEMA = {
  iphone: {
    rule: osVersion => osVersion >= 10,
    url: iphoneClientUrl,
    run: () => Alert.show(),
  },
  gphone: {
    rule: osVersion => osVersion >= 6 && osVersion <= 8,
    url: gphoneClientUrl,
    run: () => callAndroidNative(gphoneClientUrl),
  },
  windows: {
    rule: osVersion => osVersion === 8.1,
    url: windowsClientUrl,
    run: () => window.open(windowsClientUrl),
  },
  mac: {
    rule: osVersion => osVersion >= 0,
    url: macClientUrl,
  },
};

// 调用体
function jumpToClientUrl(platform, osVersion) {
  const clientSchema = PLATFORM_CLIENT_SCHEMA[platform];
  let jumpClientUrl = '';

  // 如果有规则且判断通过
  if (clientSchema?.rule?.(osVersion)) {
    // 如果有单独执行条件
    if (clientSchema.run) {
      return clientSchema.run();
    }
    jumpClientUrl = clientSchema.url;
  }

  if (jumpClientUrl) {
    location.href = jumpClientUrl;
  } else {
    // 兜底处理
    console.log('当前系统未支持此协议调用');
  }
}

进一步对需要单独执行处理的系统场景进行了结构调整,将特殊处理用run字段抽离,同样保持了适配一个新系统只需要调整对象(PLATFORM_CLIENT_SCHEMA)而不用修改jumpToClientUrl函数。

场景 2.考虑拓展应用场景。刚才我们所做的一系列优化实质还只是在一个小应用场景,如何将类似的系统判断处理通用化呢?

我们可以定义抽象类接口、将各系统的属性信息、各类判断和执行方法作为此抽象类的实现类中,将各场景的消费处理放到消费类中。然后通过类似策略模式、模版模式甚至适配器模式供消费类使用。可以通过如策略模式来实现判断条件和执行逻辑的统一抽象,提高整体代码的可扩展性、复用性和可读性。

那么接下来就以策略模式为例实现一个简单的跨端 api 封装(因为 js 中还没有抽象类/接口语法,下面就用 ts 来实现代码效果):

策略模式

策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。比如每个人都要“交个人所得税”,但是“在美国交个人所得税”和“在中华民国交个人所得税”就有不同的算税方法。——WikiPedia-策略模式

先来回顾下策略模式的概念:在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。这种类型的设计模式属于行为型模式。
在策略模式中,我们创建表示各种策略的对象和一个行为随着策略对象改变而改变的 context 对象。策略对象改变 context 对象的执行算法。策略模式就是能够把一系列“可互换的”算法封装起来,并根据用户需求来选择其中一种。
策略模式实现的核心就是:将算法的使用和算法的实现分离。算法的实现交给策略类。算法的使用交给环境类,环境类会根据不同的情况选择合适的算法。

UML 如:
p-uml

  • 优点:
    1. 算法可以自由切换。
    2. 避免使用多重条件判断。
    3. 扩展性良好。
  • 缺点:
    1. 策略类会增多。
    2. 所有策略类都需要对外暴露。

策略模式非常适合我们之前系统环境判断的处理,以下是一个实现 demo:

策略模式实现系统判断及处理

接口:

interface PlatformStrategy {
  // 跳转场景
  jumpClient(): void;

  // 其他场景、如设置标题
  setTitle(): void;
}

实现类(策略类):

class IphoneStrategy implements PlatformStrategy {
  jumpClient(osVersion: number) {
    if (osVersion >= 10) {
      Alert.show();
    } else {
      console.log('当前系统未支持此协议调用(iOS版本小于10)');
    }
  }

  setTitle(title: string) {
    document.title = `${title}(iPhone)`;
  }
}

class GphoneStrategy implements PlatformStrategy {
  jumpClient(osVersion: number) {
    if (osVersion >= 6 && osVersion <= 8) {
      callAndroidNative(gphoneClientUrl);
    } else {
      console.log('当前系统未支持此协议调用(安卓版本小于6或大于8)');
    }
  }

  setTitle(title: string) {
    setAndroidTitle(title);
  }
}

class OtherStrategy implements PlatformStrategy {
  jumpClient(osVersion: number) {
    console.log('当前系统未支持此协议调用');
  }

  setTitle(title: string) {
    console.log('当前系统未支持此协议调用');
  }
}

消费类(环境类):

class PlatformCustom {
  platformStrategy: PlatformStrategy;
  constructor(platformStrategy: PlatformStrategy) {
    this.platformStrategy = platformStrategy;
  }

  setHomePageTitle() {
    this.platformStrategy.setTitle('主页');
  }
  setHomeRuleTitle() {
    this.platformStrategy.setTitle('规则页');
  }

  jumpClient() {
    this.platformStrategy.jumpClient(osVersion);
  }
}

使用:

const NowPlatformStrategy = STRATEGY_MAP[platform] || OtherStrategy;
const platformCustomer = new PlatformCustom(new NowPlatformStrategy());

// ...
platformCustomer.setHomePageTitle();

// ...
platformCustomer.jumpClient();

可以发现,我们在消费类的定义和使用时,无须关系各系统环境的处理、进行在面临适配新系统时也不用对使用或消费类进行修改,很好得遵循了“开闭原则”。

另外,在大前端领域下,这类模式也适合跨端 Api 的封装,大家可以看各类跨端框架(如 Taro)的封装、都或多或少遵循了策略模式/适配器模式。

总结

本文的优化建议

从本次前端系统区分判断处理的业务场景以及一段代码的优化处理下,本次提出的前端优化建议有以下几点:

  1. 我们需要合理设计兜底处理,避免在适配新场景下直接调用不兼容的代码;
  2. 涉及较多判断的场景下,我们可以使用抽象方式进行处理、遵循开闭原则;
  3. 策略模式/适配器模式/模版模式可以应用于一些统一处理的场景、比如跨端统一判断逻辑;
  4. 我们需要持续学习设计模式、思考在前端的实践应用
*可能伴随的问题

上述的各类对 if 处理做了各种抽象,这种情况有没有什么问题隐患呢?

如果硬要说隐患的话,有以下两点几乎可以不值一提的隐患:

  1. 多创建了枚举/对象/类,占用了空间。;
  2. 代码的理解成本或许有所增高、没有直接 if else 看得顺畅。

思考

我们还有哪些场景可以提前做 if 语句的抽象优化?比如你需要处理各家银行、各个城市、各类水果、各只基金代码等等…
在处理这些场景时我们是否需要提前引入设计模式?如果需要、判断条件会是什么?


推荐阅读
  • 《重构-改善既有代码的设计》
  • 《代码整洁之道》
  • 《编程珠玑》
  • 《程序员的思维修炼:开发认知潜能的九堂课》

以上这些经典书籍都包含了 if 语句优化方面的内容。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1097454.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

白皮书 |得帆云低代码aPaaS X OA全新解决方案,解锁数字化协作新境界

进入正题之前&#xff0c;我们先看两个大厂案例&#xff1a; 10年IBM Lotes OA迁移 -来自国内500强发动机全链路制造公司 主要有如下几个痛点&#xff1a; 系统老旧&#xff0c;扩展性一般&#xff0c;无法集成现有的其他业务系统 随着人员的增加&#xff0c;经常性的出现卡…

linux U盘无法使用,提示“Partition table entries are not in disk order“

问题&#xff1a; U盘在Windows上使用正常&#xff0c;在linux下无法使用fdisk -l 命令提示&#xff1a;Partition table entries are not in disk order $ fdisk -l Disk /dev/sdb: 525 MB, 525336576 bytes 17 heads, 59 sectors/track, 1022 cylinders Units cyl…

ArkTS及openHarmony

补充 padding&#xff1a;内边距&#xff0c;也就是盒子边和盒子内部的距离 margin&#xff1a;外边距&#xff0c;也就是盒子和盒子的距离 openHarmony应用开发及UI界面 常用布局 Row 水平线性布局核心代码 子控件会共享同一行&#xff0c;也就是都在同一行内 Preview C…

扩展市场版图,美格智能5G智能模组SRM955集齐全球主流认证

AIoT时代来临&#xff0c;掀起新一轮智能化终端设备的变革&#xff0c;激发应用领域的新需求。AI等新兴技术应用&#xff0c;成为拉动智能终端产品变革和市场变迁的主要力量。智能模组是AIoT时代中的核心元器件&#xff0c;是实现万物智联的关键。 美格智能作为智能模组的创领…

SpringBoot学习日记

Spring程序与SpringBoot程序对比 SpringBoot程序优点 起步依赖&#xff08;简化依赖配置&#xff09;自动装配&#xff08;简化常用工程相关配置&#xff09;辅助功能&#xff08;内置服务器&#xff0c;......&#xff09; 内嵌Tomcat REST风格 REST简介 REST&#xff0c;表…

2023年主题教育专题组织生活会对照检查材料六个方面发言材料

组织生活会发言材料&#xff0c;很多人还没写完&#xff0c;可能写着写着就不知道怎么继续了&#xff0c;其实写这类材料需要有一个好的写作框架&#xff0c;结合我们的实际情况来写。 只有那些勇敢面对困难的人&#xff0c;才能找到成功的道路。生活并非总是一帆风顺&#xff…

ant design pro v6如何引入第三方js?如腾讯地图等!

由于ant pro隐藏.html&#xff0c;需要通过他们约定的方式引入即可。 1.配置config文件 /config/config.tsheadScripts: [// 解决首次加载时白屏的问题{ src: /scripts/loading.js, async: true },{ src: "https://map.qq.com/api/gljs?v1.exp&keyOB4BZ-D4W3U-B7VV…

CountDownLatch的原理

使用CountDownLatch可以实现等待多个线程执行完毕的功能&#xff0c;实现线程之间的协调&#xff0c;让它们按照我们期望的顺序执行&#xff0c;从而避免了可能出现的并发问题。 CountDownLatch是如何实现主线程等待子线程全部结束的呢&#xff1f; 代码用例 这里我们使用一段…

【TES720D】青翼科技基于复旦微的FMQL20S400全国产化ARM核心模块

板卡概述 TES720D是一款基于上海复旦微电子FMQL20S400的全国产化核心模块。该核心模块将复旦微的FMQL20S400&#xff08;兼容FMQL10S400&#xff09;的最小系统集成在了一个50*70mm的核心板上&#xff0c;可以作为一个核心模块&#xff0c;进行功能性扩展&#xff0c;特别是用…

AN动画基础——元件,组件,散件

【AN动画基础——元件&#xff0c;组件&#xff0c;散件】 元件不同元件的作用影片剪辑按钮图形元件特性 组件组件的作用组件特性 散件散件作用散件特性 本篇内容&#xff1a;认识元件&#xff0c;组件&#xff0c;散件属性 重点内容&#xff1a;元件&#xff0c;组件&#xff…

专业翻译哪家强?插件AI来帮忙!

大多数人一提到翻译软件&#xff0c;想到的应该是某度翻译或者是某歌翻译&#xff0c;日常使用也是用这两个居多&#xff0c;但是这两个甚至市面上常见的翻译软件的效果都不是很好&#xff0c;不能精准翻译到一些专有名词的意思。 那么究竟有没有好用的AI翻译呢&#xff1f;答…

DAY06_瑞吉外卖——用户地址簿功能菜品展示购物车下单

这里写目录标题 1. 用户地址簿功能1.1 需求分析1.2 数据模型1.3 导入功能代码1.4 功能测试 2. 菜品展示2.1 需求分析2.2 前端页面分析2.3 代码开发2.3.1 查询菜品方法修改2.3.2 根据分类ID查询套餐 2.4 功能测试 3. 购物车3.1 需求分析3.2 数据模型3.3 前端页面分析3.4 准备工作…

大坑-MATLAB图片转存时需注意的点

MATLAB中图片的保存和转存有一个巨大的陷阱&#xff0c;我也是在吃了大亏后发现的&#xff0c;正常情况下&#xff0c;MATLAB跑完实验&#xff0c;生成的图片如下 放大后这样 可以方便修改坐标轴标题&#xff0c;最初我就是因为想修改坐标轴标题才给它放大的&#xff0c;因为…

Linux系统之安装ServerBee服务器监控工具

Linux系统之安装ServerBee服务器监控工具 一、ServerBee介绍1.1 ServerBee简介1.2 ServerBee特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、检查本地环境3.1 检查本地操作系统版本3.2 检查系统内核版本 四、安装ServerBee4.1 下载部署脚本4.2 解压下载文件4.3 部…

全流量安全分析发现内部系统外联异常

内部系统外连监控的重要性在于保护企业的信息安全和预防数据泄露&#xff0c;以下是几个重要的理由&#xff1a; 1、检测异常活动&#xff1a;通过监控内部系统的外连连接&#xff0c;可以及时发现是否有未经授权或异常的链接尝试。这可能表示存在恶意软件、黑客攻击或内部员工…

LED显示屏高刷新率和低刷新率有什么区别

LED显示屏的刷新率是指图像在LED显示屏上更新的速度&#xff0c;也即屏幕上的图像每秒钟出现的次数&#xff0c;它的单位是赫兹&#xff08;Hz&#xff09;。LED显示屏的刷新率越高&#xff0c;图像闪烁感就越小&#xff0c;稳定性也就越高&#xff0c;换言之对视力的保护也越好…

头部品牌集体扑街!2023年9月京东平板电视TOP10品牌排行榜出炉

鲸参谋监测的京东平台9月份平板电视市场最新销售数据已出炉&#xff01; 根据鲸参谋平台的数据显示&#xff0c;9月份&#xff0c;京东平台大家电品类——平板电视的整体销售呈现下滑。具体地&#xff0c;9月平板电视的销量为62万&#xff0c;环比降低约18%&#xff0c;同比降低…

DDD之领域(Domain)和子域(Subdomain)

领域驱动设计系列文章&#xff0c;点击上方合集↑ 1. 领域 领域&#xff08;Domain&#xff09;是一个组织所做的事情以及其中所包含的一切&#xff0c;领域可以表示整个业务系统。 领域&#xff0c;简单来说&#xff0c;是指一个业务或行业领域&#xff0c;例如电商、社交媒…

【C++】C++11—— 包装器

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C学习 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C】C11…

虹科 | 解决方案 | 经销商(OEM)方案

针对汽车厂的方案 Pico 科技是PC版示波器的市场先驱&#xff1a;我们屡次获奖的PicoScope示波器被超过20家世界领先的汽车厂选择&#xff0c;用于提高质量和降低成本。PicoScope既是示波器&#xff0c;也是频谱分析仪、NVH分析仪、发动机压缩和蓄电池/起动充电系统检测仪。我们…