我所理解的好代码
最近对于代码的组织和优化思考了很多,也看完了知名的“蝴蝶书”,本着思考了还是总结一下的原则,还是记录一下自己的认识方便后面嘲讽自己有多么菜
引子
作为一名菜鸟,实际开发中由于缺乏积累,总是要踩了不少的坑才能发现自己早先到底有多么蠢。于是,许久之前写的店铺系统,维护起来渐渐地发现了很多大坑其实是可以避免的。作为教训,我觉得有必要做一下笔记,也算是写个检讨,因此这里但关注点不是我代码写得到底有多烂,而是如果我下次重新写这么几万行代码的项目,我应该注意到哪些点。
几个标准
我觉得对于大部分的前端开发工程师(这里主要面向浏览器编程)而言,JS 很多特性可能在工作中永远都不会用到,我们创建一个对象时,99% 的时候不会使用构造对象法,也不会用到位操作符。因此在对这门语言的认知还不足以成为专家的时候,我觉得评价一个人的代码水平的关键点,就是 “可读性好”
可读性,是一个众说纷纭的东西,就好比 JS 到底要不要加分号,代码缩进到底是两个空格还是四个空格,很多时候是谁也不服谁,哪个都有自己有道理但方面,自然我也不想在这里过多地纠缠。
我评价代码的可读性,主要基于以下5个点:
- 直白易懂,没有过多奇淫技巧
- 遵守一般的最佳实践
- 团队统一的代码风格
- 适度使用代码预处理和模块
- 方便使用版本管理工具
简单易懂往往很难做到
首先阐述第一个点,直白易懂,没有过多奇淫技巧
我很少看各种类库的源码,因为我觉得太晦涩了,虽然有时也觉得写得真是精妙,但这是建立在花费几倍的时间去理解和钻研的前提下,有时还得借助搜索引擎的帮助(其实还是因为太菜了么?读不懂啊)。因此我是强烈反对把各种奇淫技巧带到实际的工作项目中的,除非你能确定这个项目从出生到死亡都只有你一个人在干。
这里举个简单的例子:
其实是很简单的逻辑,就是未成年和女性可以享受半价,但由于使用了两个三元运算符,简单的逻辑反而要认真地思考以下才知道(还有运算符的优先级问题呢)
实际工作中,我也遇到了同事非常喜欢用三元运算符的,他说三元运算符看起来更简洁一些,相比 if...else 能节省很多代码
我刚开始的时候也是觉得这样真是太赞了,看起来很牛逼的写法呢。但是现在,我在思考三元运算符到底是把事情搞复杂了还是变优雅了,对比一下:
嗯,确实,代码从一行变成了5行,足足翻了5倍!但我敢说,这次一下子就知道函数的逻辑。
实际上我还发现一个地方,那就是现在基本都会使用 UglifyJS 对代码进行压缩
UglifyJS 对 if...else 语句常用对压缩算法就是使用三元运算符!也就是说你辛苦为性能也好,为节省字节也好,只不过是消费读者的脑力。
当然,我这里并不是说性能不重要,但过早但优化是万恶之源,牺牲可读性来做到所谓的简洁和高性能,是不是有点本末倒置呢?毕竟,随着硬件网络的发展,我们通过这种方式节约的带宽和性能可以说是微乎其微。在敏捷开发的时代,效率和维护迭代才是最重要的。
经典一般都经历了时间的检验
这里说的经典,其实也算是简单易懂的延续,有些技术在刚出现时可能相当复杂,但随着时代但发展,成为每个人都知道但东西但时候,这就不能算是奇淫技巧了,而是应该叫做最佳实践
我记得我第一次写切换列表时的时候,头都大了,因为有接近二十个项目,那时候因为什么也不懂,所以写出来的是这样子的:
你能想象每一个按钮都绑定了一个事件处理程序,只是为了切换不同的 Ajax 请求么?当然,现在我会用事件委托上去:
正如这里使用事件委托抽象 Ajax 请求才是最合适的做法一样,很多最佳实践是需要经验的。一个设计模式即使再好,但读者不懂得这样做但深层次原理,也就无法理解这样写的原因,于是在该读者眼里,这反而成了一种 “奇淫技巧”。因此写出好代码的一个条件,那就是自身要有足够的积累,不至于连业内通用的一些技巧或者说是最佳实践都难以理解。
统一就好办
想必都知道 JS 社区最热门的两个问题是,到底要不要加分号,缩进到底是两个空格还是四个空格好
很多这些争吵不会有一个统一的结果,因为很多时候是一个人的喜好问题而已。但当把个人但喜好带到团队中的时候,问题就开始麻烦起来了
在早先的日子里,让我去维护前人写的那些项目的时候,我很痛苦的一件事情就是,到底要不要手动帮它们的格式改成我喜欢的格式啊?最可恶的时候可能是明明是同一个文件,居然有三四种杂交的代码风格!
其实这里问题很好解决,那就是使用统一的团队代码风格,创业公司刚开始可能没有注意到这一点,但要知道,人员是会流动的,如果没有统一的团队风格指导,往往接手的人写出来的代码风格是完全不同的。这里建议的一点是,最好是使用社区的已经整理好的代码风格(实际上就是参照大公司),配合 Linter 工具有神奇的效果喔
最好一开始考虑成长的代价
这里真的是血的教训了,当你的代码长度有上万行的时候,你就会发现,很多时候时间都浪费在寻找特定的代码行。
在工作中有一个项目是实现一个店铺系统,JavaScript 代码两万行,CSS 代码近一万行的规模。可怜的我们为了能够让客户们方便地自定义代码(这根本就是一个伪需求嘛!),直接使用的全局单例模式,然后把所有模块的逻辑和样式分别塞到一个 JS 文件和 CSS 文件中……这意味着单个 JS 文件就约一万行了(桌面浏览器和手机浏览器分开实现)
写的时候当然是没什么太大问题,但是项目上线了,总会有新的需求和新的优化,这时就成为了我们的噩梦了……
首先当然是因为模块们全都塞在一起了,公有的方法控件什么的也共处一室,修改的时候我就得打开编辑器不断地跳来跳去,只因为逻辑太多
其次是对于 CSS 而言,没有用预处理的情况下,公有私有的方法混在一起非常难办,有时微调一个模块的功能,为了不影响其他模块的样式,难免引入脏代码覆盖,久而久之,我就不知道这几千条样式中是有多少条是重复的了。
因此,我在这里吸取到的教训是,如果你的 JS 代码或 CSS 有可能超过三千行,那么请严肃考虑到底要不要使用预处理器和模块化开发。如果答案是否,请再考虑该项目后续迭代的速度和功能变更可能性。
也要考虑复制粘贴
这里说一个有趣的例子,我们在写店铺系统的时候,因为是要把 DOM 结构全部写到 JS 中方便统一热更新,这里我们发现,拼接字符串真是一个好痛苦的事情,看下面几种写法:
我们最后选择了第四种写法,其实只考虑了两点:既有HTML风格的缩进,又不至于每一行风格差异太大导致经常复制粘贴错误。
是的,复制粘贴也是一个需要考虑的地方,很多人兴许没有注意到
我们来看一段代码:
没错,就是很多有经验的人喜欢使用的一种模式,社区中又叫做单 var 模式,出现这种模式的原因么,就是因为 JavaScript 中的变量会提升,那么有人就建议在函数的开头定义好全部的变量,由于只使用来一个 var 可以很突出地告诉读者这里声明了所有的变量
乍一看好像没什么问题,不过我要指出其严重的两点不足之处,首先是对于 Git 等版本管理工具而言,如果修改了其中一个变量的初始值,那么 Git 中记录的是这一整行有变动,却又无法一眼看到到底是哪个变动了(需要整行进行对比),不过这个问题可以通过一种改进的写法来优化:
这样以来就解决了 Git 中变更的问题,但其实不管是优化前还是优化后,我们都要注意到一个点,就是复制粘贴代码的时候极其容易忘记把 var 声明写完整。变量全部提升到头部一起声明的一个缺点就是,后续复制用到的代码块到其他地方的时候,需要重新考虑是否把声明落下
其实我是推荐一声明一变量,用到时再定义的做法的:
有趣的是,
UglifyJS同样会帮您优化这里,多个 var 声明的方式最后也被改成了单 var 的形式以节省字符。鉴于此,我建议是还是以可读性可维护性优先
支撑我这种思路的还有一个点是,在 ES6 中,let 和 const 是不存在变量提升的,同时也推荐使用时再声明。我这种做法只是学习了未来的一个最佳实践罢了
最后
每个人眼中的优雅的代码的模样可能都不同,也许有很多标准,我所能做的就是多踩些坑,能给后面会接手自己代码的人少些麻烦,也是程序员的一种美德