造轮子

程序员都喜欢造轮子,我身边的程序员,无论他们用什么语言,java、android、ios、python、go, 甚至是js,都热衷于造轮子。其实用go程序员造轮子我倒是能理解,毕竟很多java转go的朋友经常 吐槽:怎么这个没有,怎么那个没有。但是java程序员为什么要造轮子呢?但凡你能想到的功能,都有足够多 足够成熟的类库给你用,maven在手,天下我有呀。但偏偏java程序员似乎特别喜欢造轮子。比如你在github 搜netty ,能搜到无数多的repository,各种基于netty做的小工具,rpc,聊天室,ftp服务器 甚至servlet服务器,而且据我观察,相当多的repository已经做到了超越demo、可堪使用的水平。可是,它们是 那么的高度重复,它们在市面上的各种主流中间件面前都无疑是弟中弟。如果只是demo练手就算了,可偏偏 很多repository的时间线一直断断续续延续几个月甚至几年。我们这么多的人,把这么多的精力,消耗在 这些没有实际价值的事情上,是否是一种比不关水龙头、彻夜开空调更可怕的浪费?我的答案是:不是的。 对于一个有追求的程序员,造轮子不但有意义,而且有必要。

我的轮子们

其实我的第一个轮子,是window平台的文件查询工具,当时刚接触lucene,觉得倒排索引很强大,就产生一个大胆的想法:用swing写一个程序,递归扫描电脑硬盘的文件,将文件名和路径创建索引,以后找文件只需要打开这个程序就可以了,当时甚至考虑到了很多细节,比如程序每次打开加载上次索引文件的同时,会开启一个线程再次全盘扫描,更新索引,同时界面上有一个手动触发全盘扫描的按钮,当时做好了自我感觉非常良好,四处发送给别人使用,甚至考虑到有些人的电脑没有jdk,所以研究了只打包需要的jdk类依赖的方案,把整个jar减小到30MB(【狗头】),很快就有朋友发了一个绿色版的everything给我,打开后我傻眼了,我甚至能想象出网线那边的那个朋友的坏笑。
后来还做了一个common-util的工具包,集成了apache的好多工具包的核心功能,集成了日期转换工具,集成了深度优先的爬虫,甚至集成了nutch识别文本编码格式的功能,这个工具包一直到去年我才弃用,之前开发的个人项目,都会引入这个工具包。
这些轮子,我都给删掉了,或者标记为private,我觉得它们是我的纸篓里的草稿:)

轮子的用途

我对轮子的定义比较宽泛,只要那些已经存在的功能,重复开发就是轮子。实际上,很多程序员都会开发一些小工具,小插件来提高工作的效率,它们也许是嫌具备相应功能的软件太大了(不是有个梗,我就修个图,为什么要那么大的photoshop,还那么贵),也许只是想尝试下小工具的开发,验证下自己的能力。在我看来,这都是轮子。造轮子有很多用途,比如刚才提到的开发小工具能提升开发效率;比如一种高效率的看源码的方式就是对着源码造轮子;比如可以向别人证明自己的编程能力。我今天要谈的是,造轮子可以让自己保持思考。

保持思考

听说很多大学已经把java作为入门编程语言了,java的大行其道,一方面拉低了整个从业者的平均水平,另一方面,根据二八效应,主流的java框架人气越来越高,高到什么程度呢?很多java程序员,其实就是spring程序员。没有什么项目是spring-boot+mybatis解决不了的,如果有,就再加上spring-cloud。早些年,我们说程序就是数据结构加算法,现在,对很多java程序呀来说,程序就是spring。这到底是好事还是坏事呢?我看到过国外有几岁的小孩子用ipad编程,是说编程,其实就是很多个图案拖来拖去,对他们来说,编程就像拼积木拼乐高。有时候我想,现在的java程序员,是不是跟这些小孩子一样?照着spring的guides调一调接口,写一写注解,将从四处拷贝来的代码块,拼在一起,改改名字,就成了一个程序。
我理解的编程不应该是这样的,我并不是说语言有高低之分,越接近底层的程序员越牛逼,只是,一个拼积木的程序员,我怎么能相信他是一个解决能力的程序员呢?
上个月我到4S店给车子做保养。看着维修师傅工作,我在想一个问题,一个平庸的人,怎么样才能成为一个水平中上的赛车手呢?你会踩油门踩刹车调头拐弯,你把这车子开得人车合一,你也只是一个优秀驾驶员,你想成为一个优秀的赛车手,你要对赛道很熟悉,对积分的规则很熟悉,对比赛的时间卡点很熟悉,最重要的是,你要对你开的那个车子很熟悉,你要了解它的工作原理,为什么加速要降档?转速应该控制在多少转才不会拉缸?这个发动机的推重比是多少?离合器咬合的最佳时机是什么?轮胎热了多久适合漂移?刹车的脚感和刹车泵是怎么贴合的?对比编程,赛道、积分、卡点,这些就是业务场景和业务需求,后面的这些车子原理,就是你使用的这个框架的原理,了解汽车原理的最好方法是什么?把这个车子拆了,看个究竟,所以有追求的程序员喜欢看源码。
但是,对于一个普通人,把车子拆开,也无非是一堆碎铁和塑料件,你怎么能从发动机的结构大致看出发动机的燃烧原理,从发动机和变速箱的连接方式看出换挡的原理,从刹车泵的结构看出刹车的原理?你更无法从这一地碎片找出让这些零件更高效率工作的方法。对比编程,很多人把spring翻了个边,debug了一番,也只是大致了解了下ioc的流程,aop的逻辑,甚至无法解答一些简单的问题,比如spring-ioc怎么解决循环依赖?spring-aop为什么事务会失效?
我觉得,那个想成为优秀赛车手的普通人,你不妨对着这一地的零件,自己徒手造一台车(大雾)。
让一个普通人造一台车,很难,毕竟汽车行业是几万个细分领域的集合体,一个人怎么可能完成?可是,你至少可以完成其中很关键的地方。我知道有很多优秀的赛车手,比如尼基-劳拉,会改造发动机,会组装变速箱。他调试出的车辆在同车型里圈速永远是最快的,因为发动机和变速箱的每一个螺丝,他都拧过。

关于尼基-劳拉的故事,可以看一下电影RUSH(《极速风流》).


幸运的是,让一个普通人造一台车很难,甚至是不可能,但是让一个程序员写一个框架,比如一个轻量版的spring-boot,那么难度就小太多了。只需要一根网线,一台电脑,以及一块机械键盘。你可以用1000行代码基于java.nio轻松的实现一个轻量版的tomcat/jetty,甚至性能还不赖,然后你可以去实现spring。spring多简单啊,不就是ioc和aop吗?ioc扫描下代码不就好了?aop用asm甚至Javassist就可以实现吗?(大雾)
接下来就是零零碎碎的细节功能,每完成一个,你就对spring甚至对java理解得更深。>实现后端路由的时候,怎么将请求参数和方法参数一一对应啊?我反射出来的参数名字为什么都是arg0,arg1这些东西呀?写好的name、age哪里去了? 喔,原来要asm来读取呀;喔,原来jdk8之后加个-paramers 启动参数就可以读取到呀,真棒! >动态代理不是生成一个子类吗?为什么父类的构造方法没有执行两次呢?喔,objenesis原来这么厉害! >事务隔离级别应该怎么实现? >session怎么支持?
keep-alive怎么支持?

你甚至会对spring产生一些怀疑:

>这个数据结构是不是效率不高?
我是不是应该设置一个可探测的初始集合容量?省得无意义的扩容?

我曾经手写了一个nio服务器,现在看来简陋得不值一提,代码质量也很差,但是它的确让我理解了tomcat+spring的运行原理;使用solr的时候,我手写了一个相似度打分plugin,也许你现在问我搜索打分排序原理,我要思考下,但是我很快就能大致想到TF/IDF。这都是造轮子的过程中,思考产生的价值。
当然,最后,在你不停地完善功能的过程中,会不断的自我怀疑:我做这个东西有意义吗?spring-boot它不香吗?我为什么这么菜?学这些东西好像没什么用?。终于有一天,你会想到下面这幅图,你觉得做到1-4,很容易,并且是核心,5是最无聊的,但是没有5,这幅画没有任何价值。而spring和自己的轮子的最大差别就是它把5做得很好:)


造轮子的过程,是思考的过程。你在造过很多轮子后,会更理解一些框架的细节和体贴,甚至,你会得出一个结论:程序就是算法加数据结构,以及操作系统加计算机网络原理。我相信netty的作者,恐怕也是在开发中对现存的框架(也许就是他自己的mina【狗头】)有很多怀疑,在充分的思考后,动手写出了netty。

最后,这个轮子: https://github.com/rongjoker/quarantineJ ,真的可以对spring开发者有很大的帮助,虽然它跟spring没啥关系。


Is this article useful to you? How about buy me a coffee ?