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

最近有个三端统一的技术场景,主要是以前移动端的 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.mzph.cn/news/106936.shtml

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

相关文章

Webpack和JShaman相比有什么不同?

Webpack和JShaman相比有什么不同&#xff1f; Webpack的功能是打包&#xff0c;可以将多个JS文件打包成一个JS文件。 JShaman专门用于对JS代码混淆加密&#xff0c;目的是让JavaScript代码变的不可读、混淆功能逻辑、加密代码中的隐秘数据或字符&#xff0c;是用于代码保护的…

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

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

图片批处理工具 PhotoMill X直装 for mac

PhotoMill X是一款强大的图像处理软件&#xff0c;它可以帮助用户快速地对照片进行编辑、调整和转换。它支持在单个或批量模式下处理大量的图像文件&#xff0c;并具有直观的用户界面和易于使用的工具。 PhotoMill X具有的功能有&#xff1a; 裁剪、缩放、旋转、调整明暗度、…

python+django学生选课管理系统_wxjjv

1&#xff09;前台&#xff1a;首页、课程信息、校园论坛、校园公告、个人中心、后台管理。 &#xff08;2&#xff09;管理员&#xff1a;首页、个人中心、学生管理、教师管理课、程信息管理、课程分类管理、选课信息管理、作业信息管理、提交作业管理、学生成绩管理、校园论…

国际伦敦银点差费值得吗?

伦敦银是国际轨技术属市场上广受追捧的白银保证金交易品种&#xff0c;具有交易时长、交易制度灵活、资金利用率高等诸多的优点。 国际伦敦银的优势主要来自它所实行的是保证金交易制度。目前香港平台一般执行的保证金比例标准是2%&#xff0c;以目前22美元/盎司左右的白银价格…

epiiAdmin框架注意事项

1&#xff0c;epiiAdmin文档地址&#xff1a; 简介/安装 EpiiAdmin中文文档 看云 2&#xff0c;项目性想新建模块 composer.json文件——autoload选项——psr-4下增加模块名称&#xff0c;然后执行composer update命令。 "autoload": {"psr-4": {"…

代理现货白银有什么手续

成为现货白银代理商的好处有很多&#xff0c;一方面打理依然可以像普通投资者那样&#xff0c;采用平台的交易服务&#xff0c;直接在市场上通过交易&#xff0c;赚取高杠杆所带来的高回报&#xff0c;另一方面还可以根据自己客户的交易量&#xff0c;从平台获得一定的返佣&…

【剑指Offer】28.对称的二叉树

题目 给定一棵二叉树&#xff0c;判断其是否是自身的镜像&#xff08;即&#xff1a;是否对称&#xff09; 例如&#xff1a;下面这棵二叉树是对称的 下面这棵二叉树不对称。 数据范围&#xff1a;节点数满足 0≤n≤1000&#xff0c;节点上的值满足 0∣val∣≤1000 要求&am…

sql注入(5), sqlmap工具

sql注入, sqlmap工具 请注意&#xff0c;在实际操作中使用sqlmap测试和利用SQL注入等安全漏洞应始终符合法律法规和道德准则&#xff0c;并且需要在拥有明确授权的情况下进行。在没有获得适当授权的情况下对任何系统或网络进行渗透测试都是非法的。 sqlmap是由python开发的测…

C++引用(起别名)

0.引用的概念 引用不是新定义一个变量&#xff0c;而是给已存在变量取了一个别名&#xff0c;从语法的角度来说编译器不会为引用变量开辟内存空间&#xff0c;它和它引用的变量共用同一块内存空间。比如说你的名字和外号指的都是你本人。 void Test() {int a 10;int& ra …

【MAC】升级 Mac os 后报错

背景 17 年买的 mac&#xff0c;发现很多软件都无法安装&#xff0c;于是升级 mac os 到 10.13&#xff0c;从官网下载 10.13 版本&#xff0c;之后升级&#xff0c;升级还算顺利。但使用 git 的时候发现出现问题了。 问题 使用 git 出现如下错误 xcrun: error: invalid ac…

第二证券:市净率高好还是低好?

市净率是一个衡量公司股票投资价值的指标&#xff0c;通过比较公司股票价格和公司每股净资产的比值来评估公司股票的估值水平。市净率高好还是低好这个问题并没有一个简单的答案&#xff0c;取决于具体的市场环境和投资者的需求。本文将从多个角度分析市净率高好还是低好。 首…

头文件 <cstdarg> 的使用

进行函数可变参数的实现。 va_list&#xff1a; 适用于 va_start()、va_arg() 和 va_end() 这三个宏存储信息的类型。void va_start(va_list ap, last_arg)&#xff1a; 初始化 ap&#xff0c;其中 last_arg 是最后一个传递给函数的已知固定参数。type va_arg(va_list ap, typ…

How to install mysql 8.0 based on podman

创建配置目录 mkdir -p ~/data/podman/mysql-8.0/etc创建数据存储目录 mkdir -p ~/data/podman/mysql-8.0/var临时启动一个实例 docker run --detach \ --restart always \ --publish 23306:3306 \ --name mysql-8.0 \ --volume /usr/share/zoneinfo/Asia/Shanghai:/etc/lo…

安装运行vue-element-admin的报错问题-解决办法

文章目录 1.第一处2.第二处3.安装运行 在使用vue-element-admin时&#xff0c;使用命令安装&#xff1a; npm install -registryhttps://registry.npm.taobao.org会报错&#xff0c;不通过。需要修改两处。 1.第一处 在目录vue-element-admin-master\src\components\Markdown…

【监督学习】基于合取子句进化算法(CCEA)和析取范式进化算法(DNFEA)解决分类问题(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Docker SpringBoot项目连接本地数据库

要让本地的容器运行的应用程序连接到本机上的PostgreSQL数据库&#xff0c;可以使用以下步骤&#xff1a; 确保本机上已经安装并运行了PostgreSQL数据库。可以使用psql命令行工具或其他可视化工具来管理和连接到本机上的数据库。 在应用程序的配置文件中&#xff0c;将数据库连…

【工具】转码silk格式为mp3

【工具】转码slk格式为mp3 前提 安装 ffmpeg 【安装】Linux安装ffmpeg_linux安装ffmpeg4.4_我是Superman丶的博客-CSDN博客 GitHub - kn007/silk-v3-decoder: [Skype Silk Codec SDK]Decode silk v3 audio files (like wechat amr, aud files, qq slk files) and convert to o…

DataStructure--Tree

1.Tree–Basic 参考链接 2.Binary Tree 参考链接 二叉树是有序树。 简单地理解&#xff0c;满足以下两个条件的树就是二叉树&#xff1a; 1. 本身是有序树&#xff1b; 2. 树中包含的各个节点的度不能超过 2&#xff0c;即只能是 0、1 或者 2&#xff1b;2.1 满二叉树 如果…

单片机综合小项目

一、单片机做项目常识 1.行业常识 2.方案选型 3.此项目定位和思路 二、单片机的小项目介绍 1.项目名称&#xff1a;基于51单片机的温度报警器 &#xff08;1&#xff09;主控&#xff1a;stc51&#xff1b; &#xff08;2&#xff09;编程语言&#xff1a;C语言 &#xff08;…