简单了解JavaScript中常见的反模式


Posted in Javascript onJune 21, 2019

前言

反模式 是指对反复出现的设计问题的常见的无力而低效的设计模式,俗话说就是重蹈覆辙。 这篇文章描述了 JavaScript 中常见的一些反模式,以及避免它们的办法。

硬编码

硬编码(Hard-Coding)的字符串、数字、日期…… 所有能写死的东西都会被人写死。 这是一个妇孺皆知的反模式,同时也是最广泛使用的反模式。 硬编码中最为典型的大概是 平台相关代码(Platform-Related), 这是指特定的机器或环境下才可以正常运行的代码, 可能是只在你的机器上可以运行,也可能是只在 Windows 下可以运行。

例如在 npm script 中写死脚本路径 /Users/harttle/bin/fis3, 原因可能是安装一次非常困难,可能是为了避免重复安装,也可能仅仅是因为这样好使。 不管怎样,这会让所有同事来找你问“为什么我这里会报错”。 解决办法就是把它放到依赖管理,如果有特定的版本要求可以使用 package-lock,如果实在搞不定可以视为外部依赖可以放到本地配置文件并从版本控制(比如 Git) 移除。

例如在 cli 工具中写死特殊文件夹 /tmp, ~/.cache,或者路径分隔符 \\ 或 /。 这类字符串一般可以通过 Node.js 内置模块(或其他运行时 API)来得到, 比如使用 os.homedir, os.tmpdir, path.sep 等。

重复代码

重复代码(Duplication)在业务代码中尤为常见,初衷几乎都是维护业务的稳定性。 举个例子:在页面 A 中需要一个漂亮的搜索框,而页面 B 中恰好有一个。 这时程序员小哥面临一个艰难的选择(如果直接拷贝还会有些许感到不安的话):

  • 把 B 拷贝一份,改成 A 想要的样子。
  • 把 B 中的搜索框重构到 C,B 和 A 引用这份代码。

由于时间紧迫希望早点下班,或者由于改坏 B 需要承担责任 (PM:让你做 A 为啥 B 坏了?回答这个问题比较复杂,这里先跳过), 经过一番思考后决定采取方案 2。

至此整个故事进行地很自然也很顺利,这大概就是重复代码被广泛使用的原因。 这个故事中有几点需要质疑:

  • B 这么容易被改坏,说明 B 的作者 并未考虑复用。这时不应复用 B 的代码,除非决定接手维护它。
  • B 改坏的责任不止程序员小哥:B 的作者是否有 编写测试,测试人员是否 回归测试 B 页面?
  • 时间紧迫不必然导致反模式的出现,不可作为说服自己的原因。短期方案也存在优雅实现。

解决办法就是:抽取 B 的代码重新开发形成搜索框组件 C,在 A 页面使用它。 同时提供给日后的小伙伴使用,包括敦促 B 的作者也迁移到 C 统一维护。

假 AMD

模块化本意是指把软件的各功能分离到独立的模块中,每个模块包含完整的一个细分功能。 在 JavaScript 中则是特指把脚本切分为独立上下文的,可复用的代码单元。

由于 JavaScript 最初作为页面脚本,存在很多引用全局作用域的语法,以及不少基于全局变量的实践方式。 比如 jQuery 的 $, BOM 提供的 window,省略 var 来定义变量等。 AMD 是 JavaScript 社区较早的模块化规范。这是一个君子协定,问题就出在这里。 有无数种方式写出假的 AMD 模块:

  • 没有返回值。对,要的就是副作用。
  • define 后直接 require。对,要的就是立即执行。
  • 产生副作用。修改 window 或其他共享变量,比如其他模块的静态属性。
  • 并发问题。依赖关系不明容易引发并发问题。

全局副作用的影响完全等同于全局变量,几乎有全局变量的所有缺点: 执行逻辑不容易理解;隐式的耦合关系;编写测试困难。下面来一个具体的例子:

// file: login.js
define('login', function () {
fetch('/account/login').then(x => {
window.login = true
})
})
require(['login'])

这个 AMD 模块与直接写在一个 <script> 并无区别,准确地说是更不可控(requirejs 实现是异步的)。 也无法被其他模块使用(比如要实现注销后再次登录),因为它没返回任何接口。 此外这个模块存在并发问题(Race Condition):使用 window.login 判断是否登录不靠谱。

解决办法就是把它抽象为模块,由外部来控制它的执行并获得登录结果。 在一个模块化良好的项目中,所有状态最终由 APP 入口产生, 模块间共享的状态都被抽取到最近的公共上级。

define(function () {
return fetch('/account/login')
.then(() => true)
.catch(e => {
console.error(e)
return false
}
})

注释膨胀

注释的初衷是让读者更好的理解代码意图,但实践中可能恰好相反。直接举一个生活中的例子:

// 判断手机百度版本大于 15
if (navigator.userAgent.match(/Chrome:(\d+))[1] < 15) {
// ...
}

哈哈当你读到这一段时,相信上述注释已经成功地消耗了你的时间。 如果你第一次看到这样的注释可能会不可思议,但真实的项目中多数注释都是这个状态。 因为维护代码不一定总是记得维护注释,况且维护代码的通常不止一人。 C 语言课程的后遗症不止变量命名,“常写注释”也是一个很坏的教导。

解决办法就是用清晰的逻辑来代替注释,上述例子重新编写后的代码如下:

if (isHttpsSupported()) {
// 通过函数抽取 + 命名,避免了添加注释
}
function isHttpsSupported() {
return navigator.userAgent.match(/Chrome:(\d+))[1] < 15
}

函数体膨胀

“通常”认为函数体膨胀和全局变量都是算法课的后遗症。 但复杂的业务和算法的场景确实不同,前者有更多的概念和操作需要解释和整理。 整理业务逻辑最有效的手段莫过于变量命名和方法抽取(当然,还要有相应的闭包或对象)。

但在真实的业务维护中,保持理性并不容易。 当你几十次进入同一个文件添加业务逻辑后,你的函数一定会像懒婆娘的裹脚布一样又臭又长:

function submitForm() {
var username = $('form input#username').val()
if (username === 'harttle') {
username = 'God'
} else {
username = 'Mortal'
if ($('form input#words').val().indexOf('harttle')) {
username = 'prophet'
}
}
$('form input#username').val(username)
$('form').submit()
}

这只是用来示例,十几行还远远没有达到“又臭又长”的地步。 但已经可以看到各种目的的修改让 submitForm() 的职责远不止提交一个表单。 一个可能的重构方案是这样的:

function submitForm() {
normalize()
$('form').submit()
}
function normalize() {
var username = parseUsername(
$('form input#username').val(),
$('form input#words').val()
)
$('form input#username').val(username)
}
function parseUsername(username, words)
if (username === 'harttle') {
return 'God'
}
return words.indexOf('harttle') ? 'prophet' : 'Mortal'
}

在重构后的版本中,我们把原始输入解析、数据归一化等操作分离到了不同的函数, 这些抽离不仅让 submitForm() 更容易理解,也让进一步扩展业务更为方便。 比如在 normalize() 方法中对 input#password 字段也进行检查, 比如新增一个 parseWords() 方法对 input#words 字段进行解析等等。

总结

常见的反模式还有许多,比如 == 和 != 的使用;扩展原生对象;还有 Promise 相关的 等等。

== 要提一下。这是语言的设计缺陷通常使用 Lint 工具来避免使用。与其他 Lint 错误不同的是一旦开始大面积使用后续改正十分困难(因为与 === 确实不等价)。因此强烈建议项目初始化时就添加 Lint。

这些反模式的流行背后都存在很有说服力的原因, 但反模式对可维护性和软件的长期发展有着更为严重的影响。 按照 技术债务 的说法, 每次选择捷径都会产生隐含的代价,而这些代价在将来的某一时刻总要偿还。 那些推迟的重构不仅会影响下一次变更,而且会像经济债务一样持续地叠加利息。

虽然不存在一种具体的考核方法来计算债务大小, 但可以肯定的是如果你能熟练使用 Harttle 博客中总结的各种反模式, 一定能达到每次代码提交债务大于收益的境界。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
xmlHTTP实例
Oct 24 Javascript
关于javascript document.createDocumentFragment()
Apr 04 Javascript
ASP.NET jQuery 实例10 动态修改hyperlink的URL值
Feb 03 Javascript
javascript使用定时函数实现跳转到某个页面
Dec 25 Javascript
微信JS接口大全
Aug 25 Javascript
Vue.js组件tabs实现选项卡切换效果
Dec 01 Javascript
基于jQuery实现文字打印动态效果
Apr 21 jQuery
详解windows下vue-cli及webpack 构建网站(二)导入bootstrap样式
Jun 17 Javascript
使用socket.io制做简易WEB聊天室
Jan 02 Javascript
微信小程序实现的绘制table表格功能示例
Apr 26 Javascript
Vue通过provide inject实现组件通信
Sep 03 Javascript
Vue单页面应用中实现Markdown渲染
Feb 14 Vue.js
通过图带你深入了解vue的响应式原理
Jun 21 #Javascript
10种JavaScript最常见的错误(小结)
Jun 21 #Javascript
微信小程序开发注意指南和优化实践(小结)
Jun 21 #Javascript
使用Vue开发自己的Chrome扩展程序过程详解
Jun 21 #Javascript
如何测量vue应用运行时的性能
Jun 21 #Javascript
Vue组件之高德地图地址选择功能的实例代码
Jun 21 #Javascript
如何提升vue.js中大型数据的性能
Jun 21 #Javascript
You might like
数据库中排序的对比及使用条件详解
2012/02/23 PHP
PHP下载大文件失败并限制下载速度的实例代码
2019/05/10 PHP
JavaScript高级程序设计(第3版)学习笔记2 js基础语法
2012/10/11 Javascript
js中浮点型运算BUG的解决方法说明
2014/01/06 Javascript
jquery实现可关闭的倒计时广告特效代码
2015/09/02 Javascript
JavaScript编程中布尔对象的基本使用
2015/10/25 Javascript
浅谈js函数中的实例对象、类对象、局部变量(局部函数)
2016/11/20 Javascript
jQuery实现手机号正则验证输入及自动填充空格功能
2018/01/02 jQuery
vue二级路由设置方法
2018/02/09 Javascript
Layui数据表格之获取表格中所有的数据方法
2018/08/20 Javascript
JavaScript多种滤镜算法实现代码实例
2019/12/10 Javascript
对pandas中to_dict的用法详解
2018/06/05 Python
Python闭包函数定义与用法分析
2018/07/20 Python
python dataframe向下向上填充,fillna和ffill的方法
2018/11/28 Python
Python检查和同步本地时间(北京时间)的实现方法
2018/12/03 Python
利用Python实现微信找房机器人实例教程
2019/03/10 Python
django 连接数据库 sqlite的例子
2019/08/14 Python
flask/django 动态查询表结构相同表名不同数据的Model实现方法
2019/08/29 Python
python错误调试及单元文档测试过程解析
2019/12/19 Python
Python高阶函数、常用内置函数用法实例分析
2019/12/26 Python
django haystack实现全文检索的示例代码
2020/06/24 Python
详解h5页面在不同ios设备上的问题总结
2019/03/01 HTML / CSS
英国最出名高街品牌:Forever Unique
2018/02/24 全球购物
微软瑞士官方网站:Microsoft瑞士
2018/04/20 全球购物
户外用品商店创业计划书
2014/01/29 职场文书
工程质量承诺书范文
2014/03/27 职场文书
班长竞选演讲稿
2014/04/24 职场文书
理发店策划方案
2014/06/05 职场文书
英语教研活动总结
2014/07/02 职场文书
个人授权委托书模板
2014/09/14 职场文书
教师个人培训总结
2015/02/11 职场文书
学年个人总结范文
2015/03/05 职场文书
反邪教学习心得体会
2016/01/15 职场文书
党员观看《筑梦中国》心得体会
2016/01/18 职场文书
微信小程序用户授权最佳实践指南
2021/05/08 Javascript
浅谈MySql update会锁定哪些范围的数据
2022/06/25 MySQL