简单了解JavaScript中常见的反模式
前言
反模式是指对反复出现的设计问题的常见的无力而低效的设计模式,俗话说就是重蹈覆辙。这篇文章描述了JavaScript中常见的一些反模式,以及避免它们的办法。
硬编码
硬编码(Hard-Coding)的字符串、数字、日期……所有能写死的东西都会被人写死。这是一个妇孺皆知的反模式,同时也是最广泛使用的反模式。硬编码中最为典型的大概是平台相关代码(Platform-Related),这是指特定的机器或环境下才可以正常运行的代码,可能是只在你的机器上可以运行,也可能是只在Windows下可以运行。
例如在npmscript中写死脚本路径/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模块与直接写在一个