我是一名自豪的码农,唯一会做的事情就是写代码。我只在心情好的时候 写文档,而且用PerlYuYan写。同时,我也不关心所谓的软件工程和 生命周期Blahblah……但是我有一点点在意代码的质量;在一个软件卑微的 生命里总有那么几次它的质量会跌到谷底,然后我们这些伟大的码农就要 开始和这个软件的各种阴暗面还有悲惨的童年带来的心理缺陷搏斗,把它 变成一个完整的成熟产品。我希望能一边写一边想清楚,一个软件的一生 到底能有多乱七八糟,软件这东西到底有没有节操和下限……

一开始……

你拿到第一份需求,你有完整的用例,于是你很全面地考虑过了, 你设计了一个异常完美的程序结构,它采用B/S架构,业务逻辑、持久化 层、通讯协议的边界清晰可见……你不害怕将来需求会变更,因为你在设计 的时候为那些特别蛋疼的需求已经纠结得够久的了。

于是你很快就写出了程序框架,里面甚至还预留了未来需求的生存空间。 你回头看一眼代码库里的东西,不禁觉得生活很美好、自己很牛逼。这时 的软件可能不完整但是通常是最纯粹的需求实现(当然是指第一版需 求),而且即使是Daily Build也能通过每一个单元测试(如果你有写测试 的话)。

一切看起来都很好,除了公司发的午餐。

新需求

你期待已久的时刻终于到来了:你们的开发团队接到了新的需求。你终于 可以检验一下程序的扩展性有多好了。

这时候的需求可以分为两类:刚好能直接扔到程序框架里的,和扔进去略 显不协调的(当然还有扔进去会非常不协调的需求,不过它们被你们 自动过滤掉了)。这两类需求各占的比例视程序类型而定,而你的程序不 巧总是收到第二类需求。

于是本来简单的事件循环越来越长,因为你没时间把新事件抽象成回调、 把业务和管理循环的代码分开。于是同一个数据库表里的字段越来越多, 它们互相依赖,因为添加新模块总是没有往旧模块里加代码写得顺手。于 是程序里开始出现时序问题,因为你最初没有预留足够的、恰当的空间让 进程们互相通信,而且团队里有人开始投机取巧……

更糟的是,你们的开发团队很忙,所以自己不做测试。

“不过,没有什么能让程序变得更糟了吧?毕竟我们的总体结构很优秀, 底子好。”你有时候会这样自言自语。

第一个性能瓶颈

你们一个接一个地实现文档里的需求,渐渐地开始有些图穷匕见 柳暗花明的感觉,你预感到,模块的数量变化终于要引起质变了。

但是凭着你牛逼的直觉,你知道质变是朝着好的方向,于是你们直接 把整个系统放在老板要求的业务压力下开始测试。你们用自己写的机器人 (当然是没测试过的)去做压力测试,然后发现CPU占用曲线很稳定,很 好预测。你们因此得出结论,服务器可以同时稳定支撑3000+的长连接没 有任何问题。

于是你们选了个好日子让程序上线了,你们的推广平台带来50000个新用 户,同时在线人数达到1200……然后线上延迟达到2000毫秒

WTF?两秒延迟?!1200/3000 = 0.4,才是测试得到的额定负载的4成, 就TMD出事了?当你还盯着数据疑惑不解的时候,老板找上门来了:“线上 出什么问题了?!”

你也答不上,于是胡乱编了个理由把老板打发走之后,硬着头皮在线上的 系统里开始跑profile;你还必须小心翼翼,因为profiling对性能影响很 大,对原本就有性能问题的线上系统来说一不小心就吃饱撑着了。

Profile的结果很明显,用户花钱支付的过程中有个函数特别慢,你翻了 一下没多长的代码,发现里面有个锁,用户花钱的时候都要锁一下,其余 的代码都是简单的算术操作。你一拍脑袋:“对了,机器人不会花钱!我 TMD写这个的时候怎么没测过锁的速度?!”你开始用脏话骂自己,骂这段 程序,骂各种存在的不存在的东西。然后你把锁从程序里去掉了,没有 去改其他的任何逻辑,因为原来的设计就是只依靠锁来保护数据完整性。 也就是说,现在用户可以把他们的电子货币用到变成负数。“没关系, 这种情况很少出现,如果出现了就当作我们先借给用户的吧。”你很乐观 地想。

后来这种情况的确很少出现,于是这段恶心的代码就这样留在系统里,和 货币一起幸福地生活下去了。

这时候你们的软件质量已经开始恶化了,但其实它才刚被交付 而已。

第二个性能瓶颈

……待续……