我们是否需要使用框架?

2015-11-11 1410

转载:https://blog.robotshell.org/2014/do-we-need-framework/

几乎所有领域的编程中,都有“框架”(framework)的出现,在编程语言之上提供了又一层的抽象,实现了一些基础设施,来辅助某些开发过程。就算不是程序员,只要经常安装软件的有心人都会注意到一个叫做 .Net Framework 的重量级框架。现在估计有一半的常用 Windows 软件使用了 .Net 框架,的确大大方便了桌面软件的开发。可能是因为它的确太有用,简化了很多原来很麻烦、不优雅的工作,并且本身体量较大,不容易实现,鲜见有人重复发明轮子。但 Web 领域就不一样了,不管是前端还是后端,都有多如牛毛的各式框架供选,并且还不断有人不满意已有的框架而自己从头来过,甚至最后剥离出一个新框架。也不断有人质疑, Web (或其中某一个方面)这样“简单”的事情,需要用上框架么?或者,需要用上现成的框架么?

让我们从 HackersNews 的一篇“日经”贴说起: You might not need jQuery 。 jQuery 的确愈发臃肿(2版本开始去除老旧浏览器支持,有所精简),很多人又只是为了使用 jQuery 方便的选择器、 AJAX 和动画,而如果不需要支持上古浏览器的话,如今 JS 里已经可以原生使用大量 CSS 选择器, AJAX 也不再如当年那么麻烦,动画则更是可以交给 CSS3 搞定。正如那个网页所说的,大部分 jQuery 功能即使自己实现也多不了几行。所以何不抛弃 jQuery ?看看这条 HN 的评论,一片口水战中不少人即使支持抛弃 jQuery ,却又推荐了一些轻量级的 JS 库,只有少数 hardcore 的 JS 开发者声称全原生也是好的。

但是我认为,具体的技术原因只是表面。 jQuery 太臃肿、有缺点,你可以提交 wish/issue ,开发者们也在不断改进(看看 jQuery 2.x 吧)。推荐其他框架也许几年后又会出来“you might not need XXX”。更本质的问题是,为了某些需求而使用一个框架是否必要?重新发明轮子是不是好的?与其说是一个技术问题,倒不如说是一个工程问题。如何做选择,需要根据具体情况来分析,没有简单的“真理”。“Software engineering is all about making decisions.”这条评论就说得很好:https://news.ycombinator.com/item?id=7153090

那么就来说说我的观点。我认为,框架的最重要功能是在于健壮,而不是方便。一个成熟的、优秀的框架,不论是重量级还是轻量级,一定是 well-documented and well-tested 。这能够解决以下几个方面的问题:

1. 可移植性

即使是 Web 应用,也存在“可移植性”的问题,前端后端都有。 Web 从来都不是一个统一的平台。

前端自不必多说,搞过 Web 开发的应该都被浏览器兼容性问题折腾过。的确,存在一些“best practice”,照着做的话即使是原生的 JS 也可以在多数时候应付不同浏览器之间的区别,正确操作 DOM 和 AJAX 。但浏览器实在是一个大工程,现代浏览器几乎抵得上一个小型的操作系统了,有 DOM 等资源需要管理,有网络和磁盘 IO ,有事件,有安全隔离,有极其复杂的排版系统和 JS 引擎(其中还有大量的分析、优化、垃圾回收问题),还有各种扩展或是插件……不同浏览器甚至同一浏览器不同版本之间的细节区别比很多人想象得要多得多,如果运气不好碰到罕见的或是新问题,往往是连资料都很难插到(有可能是某个浏览器某个版本的奇怪 bug )。举个很不恰当的例子,几年前我曾今在网页上放了一个简单的 Flash 播放器,里头有几个 ActionScript 函数来调用外部的 JS 函数。在 IE 和 Chrome 下都没有问题, Firefox 却死活用不了。最后才在 Adobe 的 bug tracker 里头找到了一个很不起眼的 Flash 插件的 bug , workaround 是在调用后加一个很短的等待, IE 的 Flash ActiveX 和 Chrome 内置的 Flash 不受影响……

前端框架有很重要的一部分功能就是处理这些问题。 jQuery 的核心开发者们都是深谙浏览器原理,甚至接触过 Firefox 或者 Chrome 代码,还经常给浏览器报告 bug 。在大量的测试和经验积累下,最终提供的高层 API 把浏览器之间的区别和 bug workaround 都封装了起来,使你总是可以使用统一的接口。这就是为什么一些原生 JS 做起来也只要一句话的事情 jQuery 还是提供了自己的封装(当然,还有部分原因是提供统一的 jQuery-style 的操作方法)。不妨看看这个极不完整的列表,你就会知道 jQuery 做了多大的努力。

后端又有什么问题呢?最大的问题大概是 false assumption 。后端开发者往往没有前端开发者对于“兼容性”的敏锐意识,认为后端应该没什么“浏览器兼容”问题,觉得自己的机器和服务器没有区别,所以放手去写,能 work 就万事大吉。如此一来就经常出现由于开发环境不够“标准”而导致的一系列问题:开发使用的语言版本过高或过低,生产环境中没有某个函数或行为不一致;开发使用 Windows ,而生产环境是 *nix ,导致的一些编码、路径问题;最常见的则是不小心在自己的全局配置文件里图方便加了某个配置,自己跑着一切正常,拿到生产环境就不行了(例如设置了 Apache 和 MySQL 的默认编码,设置了 PHP 的时区等等),导致调试或者部署难度加大。

解决这个问题,首先肯定是要提高程序员的素质,意识到问题的存在是解决问题的前提。至于具体方法,除了一些“best practice”之外(例如连接数据库时总是先设置好编码,使用 date 的话需要在应用中正确设置好时区而不是依赖系统时区),更有效的大概有两个办法:一是统一开发、测试、生产环境,大团队里应该都是这么做的,至少测试环境和生产环境会较为统一,而不是各个程序员在自己千差万别的开发环境里做测试(其实这样做某种意义上也相当于引入了一个“开发/测试框架”);二就是使用框架,把常见的、容易出问题的地方“包裹”起来处理好,例如几乎所有的 full-stack web framework 都会把数据库连接部分封装起来,不用管具体使用的是哪个数据库、哪个数据库驱动,甚至还能把不同数据库之间 SQL 的细微区别都封装起来。看过一些 PHP 框架的代码的话,你就会发现里头也有不少判断 PHP 版本执行不同逻辑的地方。

2. 稳定性

对于少数原生实现很麻烦又很常用的功能,总是需要制作一个 helper 来避免大量的代码重复,即所谓的“轮子”。好的框架中的轮子比起自己写的轮子有什么优势呢?要说的话大概就是稳定二字了。虽然大部分的轮子都不难造,但总有个别精巧者,需要一些技巧才能实现,或是涉及到1所述的兼容性问题,需要照顾不同环境,何况还有“积少成多”的麻烦——一个大项目写下来,自己造的轮子往往都能够得上一个轻量级框架了。

举一些例子:不愿意用 real_escape 这种需要连上数据库以后才能用的“笨重”的东西,自己写字符串处理在所有引号前面加上反斜杠,然后被多字节编码问题搞哭;“发明”了先 bin2hex 再在 SQL 中 hex2bin 这样“巧妙”的防注入方法;想提供 HTML 富文本编辑器却不愿意用成熟的 HTML Purifier ,自己写 XSS 过滤器,结果被层出不穷的奇技淫巧打成筛子,感叹自己其实不懂 HTML ;某著名 Windows 下载软件某个版本的 base64 padding 不对,生成出来的网址有问题,大概是自己重新造了一个 base64_encoder ……

这么多的代码,维护起来是有不小成本的。偶尔手抖、脑抽带来的 bug 在所难免,更可怕的是,日久天长,有时好不容易绕过的 bug 在某此 commit 中又被引入了。这些问题,只能靠充分的测试才能避免。而且必须是可度量的、程序化的测试(continuous integration)。就算是鼎鼎大名的 TeX , Knuth 也给它写了一个著名的 torture test —— trip.tex 。如何做好测试又成了一个大问题,需要聪明的头脑和丰富的经验。优秀的框架都积累了一整套丰富的测试用例,有的是经过卓绝的努力,才在排除某个 bug 之后积累得到的宝贵经验。

jQuery:http://swarm.jquery.org/project/jquery
CakePHP:http://ci.cakephp.org/

优秀框架的宣传词中常见的那些:stable, robust, battle-tested, real world proved 之类的形容词,真的不是随便说说的。

3. 协作与二次开发

如果这个项目不是你的个人项目,并且需要在将来继续维护,那么又将遇到一个新的问题:你要告诉你的同伴/后辈,这个轮子怎么用。程序员总是喜欢造轮子,可叫他们写说明书简直是要了他们的命。他们会说:轮子谁不会用,装车上滚就行了?可真实情况是,两年后,没有人知道这个轮子该用什么螺丝螺母,该配什么轮胎,以及某个该死的“智能刹车转向辅助系统”是怎么回事。

文档是必须的。对于需要大量重用的通用“轮子”,注释是远远不够的,需要有详尽的文档(配合适当的例子)才能保证它能在现在和以后被你以及你的同事们正确使用。什么样算是详尽呢?看看 jQuery 的文档吧。你或许说,反正又不难,这么简单的功能看一下代码实现自然就会用了。这样做是节省了自己的时间,把成本转移给了别人。甚至到头来别人会一怒之下干脆自己再写一个(反正也不难,对吧?)。请再次考虑积少成多这个问题,如果项目中有大量的轮子,那么有一个统一的风格和完善的文档无疑会有很大帮助,这正是框架所提供的。

框架对于协作开发的帮助还不仅仅在于好用的轮子。框架往往不只是提供了一些 helper ,还对整个任务做了一定的约束和规范,搭建好了一定的基础结构和流程,提供了解决问题的一个大体思路(例如 Web 中该如何处理请求、服务页面的一整个 flow ),更可贵的是,这套东西同样是 well-documented ,意味着只要阅读了框架的文档,就能比较快地投入到项目的开发之中了,省去了在一些基础问题上还要研读代码的功夫。

总结一下,如果是一个大公司,有充足的人手和水平高超的 core developer ,那么选择自然是比较多的(不过这样的大佬大概也不会来读这篇文章吧),不满意现有的框架,大可以自己再写一个新的(不过“框架”大概是少不了的,因为规模太大了),什么文档、测试他们自然可以搞定。而对于处在这样的公司的人来说,则没什么好说的:只能接受现有的框架。反之,如果是一个个人项目,以后也不需要怎么维护,那么也是随你的便,想要“短码之美”,不引入额外依赖,尽可能原生实现也没人拦你。而对于需要协作的项目(例如小团队,或是希望别人贡献的开源项目),那么最好还是悠着点——不使用框架,自己造一个的话,掂量掂量自己能不能处理得好(或者有时间处理)上面的这些问题吧。

对于一个“软件”来说,文档的文字量往往不少于代码,而在代码的那部分之中,还有不少是用来测试代码。