我开发了一个基于 Beancount 的账本托管服务 HostedBeans,欢迎大家来了解纯文本复式记账并试用我的服务。
标签 #年度小结

2014 年度小结(技术方面)

2014 年的第一个项目是一个有关比特币交易的系统,规模并不大。

这是我除了 Hello World 之外的第一个 Node.js 项目,也是我第一次写 CoffeeScript. 简单地看了一遍「CoffeeScript 小书」,又随便搜了搜对 CoffeeScript 的评价,大家说得最多的是「CoffeeScript 嘛,哪有什么语法,想怎么写就怎么写就行了」,说起来倒还真是如此,差不多只花了两个小时就学会了 CoffeeScript, 而且之后几乎没在这上面遇到什么坑。

说起 CoffeeScript, 似乎在 2012 年中旬,whtsky 牛就开始学习了,并且在博客上发了两篇文章,虽然 CoffeeScript 很简单,但这也可见 whtsky 总是站在潮流浪尖。

虽然之前一直对比特币很是关注,但对比特币的交易规则其实还是一知半解,其实比特币交易平台的工作模型和股票是相似的——说起来在此之前我也不知道股票是如何工作的。因为对这个项目业务逻辑的知识了解不够充分,所以这个项目我差不多是只写了个开头,就由别人完全接手了。


第二个项目是也是有关比特币的,一个 Web 系统,也是 Node.js.

因为这时搬到了苏州,有很多的时间和小明交流,所以总算是对股票的工作方式,以及相似的比特币交易平台的工作方式,有了一个比较深入的了解。

当一个 Web 系统的规模稍大一些,比如有几十个 API, 以及几个比较大块的业务逻辑;再对这个系统进行重构就非常困难了,但碰巧我又总是在重构——这有主观原因也有客观原因,客观原因就是毕竟我初学 Node.js, 还在探索所谓的「最佳实践」,免不了要走一些弯路。于是在这个系统主体已经完工的时候,我开始着手引入自动测试。

说起自动测试,在此之前,我无数次地听到这个词。我知道这是一种先进的开发技术,但不知道究竟如何应用,也不知道用好了之后会有怎样的效果。在 LightPHP 上我做过一些探索,但感觉大部分模块因为有复杂的依赖关系,不容易编写单元测试;而为简单的模块编写单元测试又没有带来实际的正面影响。理想的情况下,应当将程序划分为模块,然后对每个模块或者说单元进行测试。但实际上大多数情况下,我还是没能彻底地剥离掉模块之间的依赖关系,所以更多的时候我在使用「自动测试」这个词,而不是「单元测试」。

所以,还是老问题,剥离模块之间的依赖关系令我非常头痛。虽然在这个项目上,后来还是做到了为绝大部分功能编写测试,但是用了很多不够优雅、不够健壮的实现方案,导致测试非常容易出问题,而且出了问题之后往往要进行大幅的修改才能解决,且测试代码之间也有错综复杂的依赖关系。


然后依然是一个有关比特币的交易系统,依然 Node.js.

这是一个自动交易系统,整个系统每时每刻都在进行大量的计算,基本上占用了服务器的几乎所有资源。这个项目的代码量并不大,但是逻辑密集,计算量大,对性能有一定要求 —— 事实上这差不多是我做过的唯一对性能敏感的项目,因为大多数项目的用户量实在太少了,根本谈不上优化性能。

为了能让算法逻辑更清晰,也为了找出改善性能的关键点,这个项目前前后后重构了很多次。在最后一次重构后,似乎效果还不如之前,不过因为比特币的价格一路在跌,这个项目被暂时搁置了,最后的一个版本开始在无人看管的状态下继续吃 CPU. 但是这不影响比特币继续一路下跌,终于在最近,这个项目被彻底关掉了——说起来比特币差不多是 2014 年度最差的投资品了。

这个项目使用了 MongoDB 作为数据库,程序差不多是在虐待数据库 —— 我没有花太多时间来对数据库进行优化,连索引也是随便想当然建的,然后程序每秒钟都在写入和读取数据,以及一些没有被很好地优化的聚合查询。不过 MongoDB 在这半年间完全没有出过问题,让我对 MongoDB 好感倍增;说起来 RP 主机上的 MySQL 曾经出过无端丢数据的情况,再加上毕竟 MySQL 的作者已经不建议我们使用 MySQL 了,顿时觉得 MySQL 的前途一片灰暗。


之后开始我们(指 番茄土豆团队, 下同)开始着手重写之前 PHP 版本的 番茄土豆. 因为之前在设计上考虑不充分,在用户量增加了之后,出现了一些性能问题,为了系统性地解决性能和其他的一些问题,索性不如直接用 Node.js 和 MongoDB 进行重写,而不是在原来的 PHP 版本上再修修补补。做出这个决定也是因为这时候我们大概用了半年 Node.js, 相比于 PHP, 我们觉得基本上只有优势,而没有发现有什么不如 PHP 的地方,于是希望将原有的 PHP 项目都改为 Node.js.

这次重写主要是其他人完成的,我只是稍微参与了一下,写了其中几个小的功能点。这是我第一次使用 Mongoose, 之前我认为既然 MongoDB 的特点是无模式,那么何必非要用一个 ORM 重新定义模式呢;后来我又稍稍改变了一点想法,确实 MongoDB 的 API 提供的是一种较为底层的数据库操作,还是需要一些辅助的功能来更好地完成业务逻辑,例如字段的检查器、在文档上定义实例方法等。

Mongoose 差不多是 Node.js 社区最主流的 MongoDB ORM, 选择它是一个没有悬念的事情。但用了一阵 Mongoose 之后我发现,虽然 Mongoose 设计了一个美丽的图景,但在细节上坑实在太多了。比如它虽然提供了在文档间定义引用关系和嵌入关系的方法,但这个功能非常弱;另一方面因为它用了一些比较 hack 的方式来实现对字段的验证,这导致又没有办法自由地修改从数据库中取出的文档,例如自己来实现引用关系,这给向视图传递数据造成了一些困难。

总之,在我使用 Mongoose 的过程中,总是有一种自己重新造一个轮子的冲动,但我又没有把握设计得更好,需要很努力地克制这种冲动 —— 好吧,其实我连给这个轮子的名字都想好了。

在 Node.js 版基本完成后,如何将新版本部署上线成了一个很大的问题,这个问题困扰了我们几个月的时间。番茄土豆在 2014 年初也上线了一个重写的版本,但那次的情况要简单得多,一个晚上就基本搞定了。而这次因为除了 Web 版之外还有几个平台的客户端,需要保证这些客户端所使用的 API 依然可用,这就要求新版本的上线过程必须是持续的、平滑的,新旧版本需要共存一段时间,目前我们还在逐步完成有关新版本上线的工作。


我们给番茄土豆设计了一个「周报」的功能,会在每个周末向用户的邮箱发送一周的工作报告,这个工作由我负责,但这差不多是我 2014 年度完成得最不好的一个项目,前前后后花费了很多时间,但还是错误百出。

说起来也简单,无非是每周运行一次:从数据库查到数据、生成统计数据、渲染邮件、发送邮件,但每个步骤都出了很多问题。首先要保证这个任务每周运行一次就花了一些功夫,因为番茄土豆的用户来自不同的时区,所以需要在当地时间每周日早上来发送这封邮件,这就将「每周一次」变成了「每周 24 次,每次完成一部分」。而邮件一旦发出又不能撤回,试错有很大的代价 —— 在这个项目上我出现了太多次严重的失误。

因为这是我们第一个与邮件相关的工作,因此之后大部分与邮件相关的工作也归我了。我围绕着邮件写了一些一般化的库,比如 pomo-mailerpomo-sender. 前者用于渲染涉及多语言的邮件,后者是一个考虑了时区和定时任务的邮件队列。

在实现邮件队列上,我遇到了一些有关 Node.js 异步流程控制的坑。在 PHP 和 Node.js 中,都不需要我们人工地创建和管理线程,因此之前从 C++ 上学习到的有关多线程编程的知识也快忘光了 — —或者说那些知识其实根本没实践过。直到年末看了 JavaScript 异步编程 这本书之后才基本掌握了如何在 Node.js 中优雅地控制异步流程。


RootPanel 是贯穿我 2014 整年的一个项目,也贯穿我学习 Node.js 的整个过程。

在之前写 PHP 的时候,当需要写前端 JavaScript 时总是非常苦恼,因为 JavaScript 语言的设计并不全然合理,浏览器间又有不兼容的拓展,再加上市面上全是些不靠谱的 21 天学通 JavaScript 教程。所以在一开始使用 Node.js 的时候我也对 JavaScript 比较抗拒,加上 CoffeeScript 屏蔽了 JavaScript 语言的一些细节,所以在最开始的一段时间,我对 JavaScript 的了解其实很少。例如原型系统、真假值表什么的都是后来才系统地了解。

RootPanel 3 被我定义为「一个插件化的 PaaS 开发框架」,实现彻底地插件化是最重要的一点。2013 年末的时候我已经开始尝试用 PHP 实现 RP3 了,但非常困难,主要是因为 PHP 毕竟还是传统的面向对象架构,正统的方式是通过类的继承、定义接口来实现插件化,这就导致大量的代码是在维护这种「模式」而不是专注于业务逻辑。JavaScript 就好像无模式的 MongoDB 一样,PHP 中的类、对象、数组、函数,在 JavaScript 中都是 object, 可以自由地添加和读取属性,以实现在运行时拓展功能。当然 JavaScript 的灵活性也导致了我作为一个 Node.js 新手,一开始花了很多时间去探索什么才是好的设计模式,浪费了很多时间,尤其是浪费了很多时间在重构 RootPanel 上。

在 2014 年 8 月,因为 us1 被反复 DDoS 直到下线。我不得不加快了 RP3 的开发,在之后的三个月里,快速地发布了几个版本,还节外生枝地发布了一个 GreenShadow. 在之后,尤其最近两个月,RP3 的进度明显慢了下来,主要原因是我对插件系统的设计依然不理想,还在继续探索更好的实现方式;目前 RootPanel 的版本号是 0.8, 希望在新的一年里我能将插件化的实现方式确定下来,发布 1.0 版。


在 2014 下半年,我断断续续地看完了 SQL 反模式, 这本书中列举了一些好的和不好的数据库设计模式。说起来我的 SQL 基础非常不扎实,基本上也就会个增删查改,从未系统性地学习过 SQL. 我突然开始反思,是否是因为我只用到了 MySQL 众多功能中很小的一部分,所以才觉得关系性较弱、无模式的 MongoDB 更好用呢。于是我开始尝试系统地学习 SQL, 但似乎 SQL 本来就不是一个系统的语言,有大量「取决于实现」的细节。所以我其实也只是先补习了一下之前了解很少的子查询、GROUP BY, 以及 JOIN.

这时,我们希望将番茄土豆中的订单和支付系统独立出来,用 Node.js 重写,我决定在这个项目中使用 MySQL.

这个订单系统是我第一次在一开始就引入单元测试的项目。因为我们一直都是前后端独立开发,所以在此之前,我都是通过 Postman 在测试我的程序。因此经常发生这样的情况:在开发完成一个功能后,测试没有问题,但之后因为改动其他部分而产生了问题,这种情况往往只有到前端开发用到这个接口的时候才会发现,因为我们团队都是远程工作,经常发生这样的事情会对工作效率有一些负面的影响。而在重构之后这个问题更加严重,往往要重新测试所有接口来确认重构没有对其造成影响。

而如果从一开始就引入单元测试,开发就变得容易得多,开发的大部分时间就是在看单元测试的结果而已,一旦单元测试显示通过,那么你就知道程序至少在按照你单元测试中写清的规则在运行。有人认为编写单元测试需要花费额外的时间,进而觉得很不值得,其实不然,开发一个程序终究是需要测试的,单元测试会将原本需要人工测试的步骤自动化,以便可以随时完整地重新运行所有测试。只要探索出了正确的方法,编写单元测试并不会比人工测试花费更多的时间,而且单元测试会有一项额外的好处:测试的步骤被存档了下来,而且会进入源代码的版本控制中。

当然,为了能够编写单元测试,是需要在设计项目结构上花一些功夫的。前辈们在这一点上的经验总结起来就是三个字母 —— MVC. 之前我一直错误地在 Controller 中包含了太多的逻辑,比如在 Controller 中实现大部分的错误处理。其实这通常也不会有太大问题,但一旦引入了单元测试,这种架构就暴露出了问题。在 Web API 中往往一个接口包含了一组逻辑,如果在 Controller 中包含这些逻辑就会出现一些重复,这些重复的逻辑没办法被抽象成一个函数。更严重的是因为在单元测试中需要构造一些特定的环境,如果通过调用 Web API 的方式来实现会非常繁琐,因为 Web API 往往是被保护在用户认证、权限认证之后的。所以更正确的方式是尽可能在 Model 中实现大部分的逻辑,以便被划分成更细粒度的单元,被单元测试直接使用。

Node.js 上主流的 MySQL ORM 应该是 Sequelize, 不过我在这个项目中本着「步子不要迈太大」的原则,并没有使用 ORM, 也没有使用 MySQL 的外键和事务,这些功能估计要在新的一年里去探索了。


我们团队的前端项目一直在使用 AngularJS, 如果我还在写 PHP 的话,那这应该和我关系不大。不过既然已经掌握了 JavaScript, 就不如尝试一下。于是买了一本 JavaScript Web Applications 学习如何在前端实现 MVC. 这本书简单地介绍了 Backbone 这个框架,我发现相比于 AngularJS 我更喜欢 Backbone 这种侵入式弱,定制型强的轻量级框架。于是我读了一遍 Backbone 仅有 2000 余行的实现,并决定在新的一年里用 Backbone 来重构 RP3 的前端。


2014 年的最后一个项目也是一个 Node.js 的 Web 系统。

这个项目也是从一开始就引入了单元测试。我发现我之前为 Web 系统编写的单元测试存在一个问题,即究竟应该以什么为「单元」进行测试。之前通常是以每个 API 接口为一个单元,测试这个 API 接口在各种情况下的工作情况。但当程序的逻辑复杂起来以后,为了测试一个 API 接口在某种环境下的工作情况,需要花费大量的代码来准备这个环境。于是一个更好的方式就是以「行为」为单位,一种行为包含了一组 API 请求,它们往往需要通用的环境,因此更适合被放在同一个单元中。

2014 年度小结

转眼又一年过去了,去年的年度小结我写得很满意,今年也来写一下。

我的 2014 依然很精彩,美中不足的一点是这一年里日志写得很少,连去年的三分之一都不到。一方面是知道得多了,想得多了,越想越不敢落笔,怕写出来不理想,有疏漏,很多日志都写了一半堆在草稿箱里;另一方面是今年的大部分时间离开了家和学校,和几个小伙伴们在一起生活和工作,有了新的点子会先和他们交流,导致日志变成了一个「分发点子」的次要渠道。

首先第一部分是年初正式决定不上大学,随后又决定高中的最后一个学期也不留在学校了,离开的时候基本没有理学校的意见,至于在程序上到底算不算退学我也不是很清楚,不过我很喜欢「高中退学」这个经历。期间我写了「这个世界缺少童话」和「时无英雄,使竖子成名」这两篇日志。做出这个决定有两方面的原因,首先是在学校的所见所闻,让我更加坚定地认为学校不是一个好的学习和成长环境,无论是高中还是大学,至少对我来说是这样;然后就是我也有了离开学校,打破传统的能力,又得到了一个很好的机会,这部分会在后文提及。

然后今年六月份以观光的心态参加了高考——好吧,我决定在这里第一次透露一下我参加高考的真实目的:主要是想最后找一点机会给班主任再添添乱,可惜高考那几天她装得比较和善,没找到机会。后来离开学校之后,还经常会梦到一些场景,醒来感叹在学校的时候没多给她找点麻烦,希望以后还有机会。有关她的更多内容可见我去年写的「电影:那些年,我们一起追的女孩」和「宽容和公平」,总之我认为她不是一个合格的老师,为人非常失败,价值观扭曲。

第二部部分是我从沈阳来到苏州,今年二月我受 @unstop 和 @ming 之邀去苏州见面顺便挑选公司的办公地点。短暂的四天时间让我了解到了一种新的,理想中的生活方式是完全可行的;加上之前几个月的交流,我们的价值观也是非常接近的。于是考虑到我和小璐基本没什么发展了,以及很喜欢苏州的气候,我在一个月后搬到了苏州。前两个月这边只有我和 @ming, 之后就热闹多了,在这边成功地举办过几次粉丝聚会,@faceair, @mason, @dacer, @yudong 以及雨露姐姐都参加了活动,还有 @orzFly, @huai, @bolasblack, @xhacker, 虽然他们可能不承认是我的粉丝。

第三部分就是比较平淡的下半年,这半年里我过的是一种「没有计划」的生活。没有固定的起床和睡觉时间,困了就睡,睡不着了就起来,差不多是「滚动式」的睡眠周期;工作安排也都比较松散,有时间就做,不想做就往后拖。这样的半年之后我发现我还是挺适应的,以至于后来对「有计划」的事情开始不适应,如果第二天有什么安排的话,会很焦虑,然后睡不着觉。后半年基本天天宅在家里,仅有两次比较远的行程,一次是和公司的小伙伴一起去南京,一次是去武汉办粉丝见面会。

武汉的粉丝见面会算是计划了很久的,也是我第一次自己来准备远距离的行程,说起来参加的人数不是很理想,算上我也只有五个人。不过见到了呆萌的 cry 姐姐也算值了,在一起聊了两个半天竟然完全没有冷场,大概是之前有太多想说的话,而 cry 姐姐在网上又完全无法沟通——其实面对面沟通依然十分困难。我问 cry 姐姐在学校里还有没有其他同学和她说话,她说「偶尔有」——分明就有没有嘛。去武汉是因为之前在家呆了太久,极少出门,感觉待在家里太无趣了。然后,毫不意外地,在武汉逛了几天之后觉得还是在家消消停停地呆着舒服。每年如此循环个两三次也是挺好的。

今年里,与不少朋友疏远或是断了联系,并不是有意为之,感觉虽然曾经相互很熟悉,但是总有那么几点关键的价值观不匹配。当我们还是小孩子的时候,总是能玩到一起去,但越是长大,越体现出价值观的差异,越是越走越远,变成两个世界的人。

最后简短地谈一下技术方面,说起来这一年中,为公司写的代码中,大概 85% 的代码最后都没有被用到。原因可能是自己重构代码、更换实现方式、修改了项目需求或者方向、项目被中止等等。这大概也是为什么程序员天天在网上吐槽产品经理的原因。但我觉得这是很正常的事情,首先所有人都会犯错,都没办法准确地做出预判,就好像代码总是有 Bug 一样不可避免;另一方面,参与的人越多,就会在沟通和流程上浪费越多的资源,这同样无法避免。相信其他各行各业,同样不可避免地只有少部分的工作时间是在真正地创造价值。

2013 年度小结

年度小结这东西,前年写过,去年没写过,但是春节写过一篇类似的回忆日志。

今年大概可以分三个部分吧,「勾搭妹纸」,「赚钱」和「丰富简历」。

按顺序来,我有稳定的收入是从今年年初开始的,也就是 RP 主机正式开卖的时候。

众所周知 RP 主机是 2012 年 11 月上线的,上线之后我立刻就开始构思 2.0 版本,从 3 月份开始,一直在跳票,直到 6 月 6 日 2.0 版本才上线,我在 V2EX 进行了一下宣传,吸引到了不少用户,那是我发的最成功的一次广告。
然后我又准备进行一次小更新,即 2.2 版本,顺便上线香港节点。2.2 版本本来已经写好了,9 月份开始在香港节点试运行,但是香港节点经常无故地遭到 DDoS, 半个月后被迫下线,然后我也懒得给其他两个节点更新 2.2 了。
之后我又开始构思 3.0 版本。

RP 主机赚得并不多,一开始几个月是两三百元钱,暑假那阵是卖得最火的,也不过一千元。不过这个项目做得仍然非常划算,首先这是我第一项稳定的收入,其次是借此认识了非常非常多的朋友,各种大牛。同时 RootPanel 也是目前为止我写得规模最大的 Web 项目。
在今后我打算将所有的盈利以各种形式送出去,把 RP 主机做成一个公益的项目。

然后 8 月份,我在 V2EX 看到一个招远程兼职的帖子,他们正在做的,和曾经做过的项目,都是我熟悉的,用过的,顿时感觉世界真小。
我没立刻动心,发给了我的一个朋友,过了半天他告诉我他被选上了,于是我也想去试试,于是就这样开始了几个月的兼职。

一开始谈的是只是假期的一个月,后来 9 月份学校开学的时候,和学校有一次比较大的矛盾,总之从那之后我白天在学校不必上课了,也有时间写代码了。
当时和班主任连学校领导,闹了几天的样子,说起这个,还不得不提我和我们班主任那深仇大恨,我读书十多年,没见过这么脑残的老师。

因为我白天在学校也可以写代码了,于是兼职的老大又找到了我,继续和他们一起做项目。

老大做的是 Bitcoin 相关的项目,Bitcoin 我很早就关注了,这一年里,Bitcoin 从 3 月份的 50 CNY, 到 4 月份的 500 CNY, 9 月份 1000 CNY, 10 月份的 5000, 到现在央行约谈第三方支付,可以说十分之疯狂,我也从中小赚了一笔——大概和我的心态有关,我并未用 CNY 购买过 Bitcoin, 只是用 Bitcoin 收款然后一直懒得提出来而已。

5 月份的时候,据小璐后来说的,她脑袋一短路就突然叫我去看电影,然后就开始了我勾搭妹纸的部分。
一开始两个月是藏在心里,然后挣扎了两个月,再到现在这样子。

以前看别人的故事,总以为发生在我身上的话,我能华丽地拿起或放下,现在看来不是那么回事。

8 月份的时候我试图将三年前写过的「类似自传的故事」重写一遍,那时小璐说一定不要把我写进去啊。
我说那可不行,你是里面重要的部分,还有时间呢,也许我写到那之前还会有变数也说不定。

在「面试」那个兼职之前,我曾心血来潮地为自己写过一份简历,不谦虚地说,含金量也是有一点的。通过那次面试我也认识到了一份「漂亮」的简历的重要性。对我来说,就是 Github 和我的博客,整个下半年我都在为此而努力。

当然,不只是表面功夫。我曾立志花一辈子读完 78 块大理石,就是机械工业出版社的那套大理石封面的「计算机科学丛书」。
今年我买了其中 7 本,读完了其中 3 本,剩下 4 本正在读。说起来这一年因为资金宽裕了,买了不少书。

今年我还学了不少新技术,比如 NodeJS, Golang, 用 Jade 和 Less 来写前端等等。
同时 LightPHP 还在没完没了地写,但仍旧没达到「可以正式发布」的质量标准;零毫秒调研过一些,但目测这个项目是要永远地坑掉了。

一年前我在想,要是用 Arch 显得我多高端啊,于是今年后半年我主要使用 Arch 作为 Linux 发行版,顺便调研 Debian, 彻底丢掉不够高端的 Ubuntu.

精子生于 1995 年,英文 ID jysperm.

订阅推送

通过 Telegram Channel 订阅我的博客日志、产品和项目的动态:

王子亭的博客 @ Telegram


通过邮件订阅订阅我的博客日志、产品和项目的动态(历史邮件):

该博客使用基于  Hexo  的  simpleblock  主题。博客内容使用  CC BY-NC-ND  授权发布。最后生成于 2024-04-08.