
1.2 商用程序员对开发的理解
商用程序员出于系统分析和设计的需要,对于产品、项目、需求有一些相对独特的理解,这些理解是基于长期从事商用程序开发,对市场化、产品化以及抽象化概念的理解。本书大量使用了这种概念,这里提出来,帮助大家对本书的理解。
1.2.1 资源和成本
资源是一个很广泛的概念,在商用程序员眼中,一切都是资源。这不仅仅和技术有关,而且与人有关。
从一个公司讲,市场份额是资源,客户关系是资源,公司的流动资金、固定资产也是资源。
从一个项目讲,投入一个项目的人力是资源,投入的钱也是资源;已有的开发软硬件条件是资源,提供的测试环境也是资源。
从一个项目团队讲,团队中最顶尖的高手、能实现的极限算法是资源,团队的整体平均实力是资源,团队成员的工作时间也是资源。
从一个程序员讲,自己已经拥有的成熟代码库是资源,自己的时间是资源,自己的精力也是资源。
从一个程序来讲,本模块能使用的CPU占用率是资源,本模块能使用的磁盘空间是资源,系统设计的目标运行平台也是资源。
商用程序员一定是对资源非常敏感的人,因为他们深深知道,资源决定算法,资源决定数据结构,资源决定开发的难易程度,资源决定最终能为公司赚取的利润,以及决定自己能够赚取的薪水。
举几个例子:
一个项目团队,如果只有一个程序员,那么不考虑大家水平上的差异,这个程序员可以比较随意地决定算法和数据结构,因为全部代码都是他一个人写,无需和他人配合。这时候,他可以优先选择自己的成熟代码来完成开发,降低资源消耗。
一个软件系统设计的目标平台很高,使用高端服务器平台,这说明程序的运行资源很多,程序员就可以在开发时采用一些简单的、便于实现的算法,如使用静态数组,而不使用动态内存分配等,以一定的资源消耗,降低开发难度、提升开发效率。
一个商用程序员,做了几年,总有一些相关行业的成熟代码积累,在做分析时,程序员潜意识就会尽量考虑设计向代码靠拢,以便后期实施时降低开发难度和成本。
这些实例说明,不管我们是不是承认,其实,每个人在做事时都潜意识地会先做资源分析,因为资源决定了做事的成本,也决定了赚钱的速度。
其实,商用数据传输工程,甚至任何一个商用工程的开发,道理是一样的,系统分析首先就是资源分析,以此来评估项目的研发成本,进而推算一个项目可能的利润,然后决策是否可行。资源分析,其实就是经营中的成本分析。
1.2.2 盈利导向
很多时候,资源决定做事的成本,但成本并不是经营的全部。商业经营中的成本控制仅仅是手段,最终是为盈利目标服务的。
这要求商用程序员不仅仅要做一个技术的高手,还应该深深理解自己的工作最终极的目标是为公司盈利。
一个产品,使用再好的技术,若市场表现不好,也不会盈利。一个项目,尽管做得尽善尽美,但超出了工期,客户拒绝买单,也不会盈利。商用工程开发本质上是一个经营的过程,其最终目标不是钻研技术,而是盈利和赚钱。
举个例子,一个项目,需要处理海量用户的在线数据。作为传统的程序员,这么大的量,肯定首先会考虑使用动态的数据管理结构,比如链表之类的算法,由此带来程序复杂度大大增加、bug率增加,开发的时间成本随之增加,进而,程序员单位时间的产出减少,公司的盈利也减少。
但商用程序员会首先分析资源,在线数据的信息量不大,每个用户64字节估计就够了。一般的系统,用户再多,算100万好了,总数据量也就64MB,那么开个64MB的数组行不行,就在内存里面计算,优化算法也多。最重要的,程序容易,开发快捷,不出错,公司能赚钱。
商用程序员做事具有明确的盈利目的性,单刀直入,选择算法以最简化为原则,满足需求为原则,并且随时关注资源的消耗,成本的控制。
1.2.3 客观
其实不管是不是做程序,在企业中工作,职业素质的基本要求就是客观。客观,就是一分为二地辩证看待问题,遇事,既不轻易肯定也不轻易否定,先分析再决策。这种态度,不仅仅是技术分析需要,也是基本的做人和做事的态度。
1.做事态度的客观
商用项目开发行为实际上是一次经营行为,有明确的盈利目的,这就要求参与的每一个人具有较高的职业素质,遇事能冷静、客观地分析问题,善于一分为二地考虑问题。这是平衡思维。
一个程序员,从学校出来,刚刚走入社会进入一家公司,一定有很多冲动,希望在软件研发这个领域快速地证明自己的能力和水平,迅速获得公司和同事的认可,从而获得自己合适的社会地位和相应回报。这没有任何错误。
但是,在商用工程中,由于客户需求千变万化,使用技术无论是深度和广度都远远大于学校中的教学内容,一个新程序员的技能很难真正满足商用开发的需求,需要大量的钻研和学习。
这就带来两种可能,一种是新人急于证明自己的能力,遇到难题,一般不会求助于团队,坚持自己钻研,导致开发时间无法控制。另一种,是新人为了表现自己,喜欢在开发中使用一些比较精巧、比较深度的算法来解决问题,但由于算法的复杂度增加,带来了开发时间延长的项目风险。
这两种态度其实都不可取。真正的商用程序员,其实是一个善于利用资源的人,他把身边的同事、自己的领导都视为自己的资源,遇到问题,既不会不思进取、轻言放弃,也不会死钻牛角尖、拒绝帮助。
同时,商用程序员在讨论工作时,应没有什么面子观念,对于问题冷静分析、客观表述,既不回避责任也不讳言能力不够,尽量把问题尽早暴露给所有团队成员,帮助团队及时采取策略,规避项目风险,最终实现盈利目标。
举个例子,笔者以前带的团队,有个年轻的伙伴,很喜欢用C语言里面的指针,尤其喜欢动态内存分配,经常是指针来、指针去。笔者劝过他,他也不太接受。
后来,在项目开发中,有个数据库的目录服务,交给他写。两个月,没有写出来,总是有bug,无法完成,笔者审查他的代码,写了10000多行,看起来还是很用心的。
但后来项目交工期到了,全体项目成员都完工,就等他一个,笔者没有办法,只有替他写了出来,1200行,什么技巧都没有用,两天时间完工,而且,程序一次过。
最后他被公司请去“喝茶”。现在,在另外一家公司做程序员的他跟我说,这辈子都不敢再乱玩指针技巧了。
这是典型的乱玩技巧和拒绝帮助带来的恶果,希望各位读者引以为戒。
2.技术的客观
在笔者的程序生涯中,曾经无数次遇到同一个问题,就是哪种语言更好。C、C++、Java、PHP等,现在又加进了Python等动态语言,甚至还有函数式开发语言。其实,经过这么多年的分析,笔者认为这个问题根本不称其为问题,讨论这个问题,根本就是无意义的。
在商用工程中,客户需求千变万化。每一门语言,都有自己擅长的一面,很难讲哪种语言能应对所有需求。但计算机程序设计语言,由于网络、由于跨平台的发展,其界限已经慢慢模糊,接口也越来越容易。
现代工程开发,很多时候是多种语言的混合体,哪个模块适合用哪种语言就用哪种语言开发。很多时候我们的方案拍板时,C和C++都输了,Java或PHP胜出,因为开发上层应用时,脚本语言简单、成功率高、开发成本低。
更重要的原因就是学习成本。很多主管喜欢按照自己喜欢的语言来做系统设计,其实是很要不得的。团队是由多个程序员组成的,每个人都有自己习惯的语言,在熟悉的领域,大家成功率都比较高,开发成本也低。如果不顾实际情况,硬性规定一定要用哪种语言,程序员为了完成任务,重新学习,这个学习成本,其实也是项目的成本消耗,很多时候得不偿失。
同样的问题也体现在算法和数据结构上的选型上。项目团队的主管,系统架构师,很多是技术出身,因此在开发过程中,角色定位不准,经常越俎代庖,替程序员选择算法。布置一个任务,不但要结果,还要求程序员必须用哪种方法做。每个程序员都有自己熟悉的算法,有自己成熟的代码库,而用其成熟的技术开发,本来就是大大节约成本的一件事。
因此,除非超出程序员能力、完全不能实现需求,一般建议架构师或项目主管不要太关注算法细节,避免效果适得其反。
举个例子:
以前一个项目,服务器的,当时的方案有点问题,项目组最后完成时,测试到并发登录率很低,每秒只能3~4人并发登录,而要求的设计指标是70人左右。项目组全体人员“吐血”。
分析原因,当时的CTO选择的qdbm数据库,根本没有做性能测试,仅仅参考了一份网上的报告,说每秒能支持10万次以上的读写,就决定使用。由于他是CTO,有方案最终裁决权,项目方案就此定稿。
但最后经过我们实测,这份报告是qdbm的枪手写的,所谓10万次以上的读写性能,是cache机制全效率运行的效率,换而言之,是所有数据都在内存时候的效率。
问题是,我们的业务数据差不多20亿条,放在内存中根本就不现实。还有,qdbm的CPU占用率太高,那个枪手也很不负责任,根本没有说明,只要达到1万次/秒的读写频率,CPU占用已经到了90%以上,其他业务也就不必做了。
而上面提到的这个CTO根本没有仔细分析,就决策使用qdbm,导致项目最终失败。
最后,笔者所在的项目组在12天内,推翻他的方案,使用MySQL数据库,更换数据组织形式,重构了几万行代码,才算按期交活。那12天,笔者基本上没怎么睡觉。
仅仅这一个失误,公司十几个人做了将近一年的无用功,浪费差不多200万人民币。希望各位读者以后引以为戒。
1.2.4 平衡
当我们在学校的时候,出于教学和科研的目的,同时也是因为我们时间丰富,做事情对成本的考虑较少,因此可以把一个算法、一个数据结构,仔细钻研到极致,最终实现最佳的配比性能。
但很遗憾,在商用工程中,这种态度其实是最要不得的。
比如一个客户需求,仅要求服务器能承接100个客户的并发请求,我们在那里研究算法,做出了10000路并发请求的服务器,好不好?肯定好。但是,有必要吗?
客户只要100并发,就只会给100并发的钱。我们做得更好,多花了很多资源,但没有赚到更多的钱,其实是做无用功。
商用程序员是平衡的高手,他们能在满足客户需求的前提下,尽可能节约成本。另一方面,他们也随时在计算新需求的开发成本,如果感觉某个功能,开发起来成本过高,则会主动提出建议,建议公司的市场人员和客户沟通,取消这个功能,或者换成别的效果差点但成本很低的功能实现,再或者,请客户多花一点钱,换个更好的运行平台。
在公司里面做事,其实程序员会面临一些压力,很多时候,市场人员无法理解程序开发的成本计算,可能由于客户难以沟通,简单把这类问题归纳为程序员水平不够、态度不好,并向公司汇报,以此向研发人员施加压力。
笔者在商用工程开发中这种问题遇到很多。确实没有太多太好的方法,一般说来,笔者会给出详细的分析说明文档,论述自己请求降低需求的理由,另外,笔者长期的工作态度和业绩,也说明了笔者的技术水平,对方的施压没有太大说服力,最终,一般都能得到一个较为平衡的结果。
因此,商用程序员要关注到自己也是公司的一分子,遇事要多沟通,保持高度的职业化精神,不断关注自己的输出和贡献,创造自己的个人品牌价值。这些,看起来是一些做人的道理,但也是研发工作能顺利进行的保证。
说到平衡,还有一种系统设计平衡,也请各位读者关注,就是在商用工程中,一般很忌讳把某个单项性能做到登峰造极。
原因很简单,一台计算机系统,天生应该是平衡的,CPU就那么快,内存就那么大,如果其中某个功能,性能做得太突出、太优秀,笔者一般不会认为这是好现象,反而担心它占用了太多的资源,导致其他业务无法正常运行。这不好。
即使计算机系统资源足够,不存在上述矛盾,但是优秀的设计往往占用程序员更多的研发时间,这也是成本,项目管理者应该加以重视。一般说来,客户要求100%,我们一般做到整体性能101%就够了,千万不要试图去把某个模块的性能做到150%,这可能会导致其他模块的性能不到20%。因此,当我们极力研究某个高效算法时,也要综合考虑系统的综合性能,不要太走极端。
这个例子是最近发生的。公司的一个同事,女孩子,过来问我,如果要做一个文件传输模块,是不是需要开线程池等一系列设计,使用多任务方式,并发,获得最高的性能。
笔者仔细询问了她的设计需求,发现这是一个VoIP语音系统附带的功能,就是说,这是一个附属功能,系统的主营业务是语音,重要的是要确保语音的流畅。根据经验,一旦使用多任务并发,可以很轻松地抢夺网卡的带宽达到高传输率,但是,也很可能导致语音的品质无法保证。
由于这个系统是以语音为主的,笔者经过思考,建议她简单做个串行队列,慢慢传文件,这个模块不但不追求效率,而且每传一个模块都要可以睡眠10毫秒,为关键业务主动让出带宽。
这个设计,获得了项目组的一致同意。
笔者有个经验,一个商用系统设计完成,随便拿一个性能参量,比如吞吐率,以时间轴画一条测试曲线,这条曲线,一般应该很平滑,不要有尖锐的波峰和波谷,这是一种比较好的状态。
如果有任何一条曲线出现了一个波峰,系统总能力又是恒定,那么,可能另外的一条或几条性能曲线会出现波谷,这意味着,另外的一些服务目前无法工作了。
1.2.5 服务
在笔者的职业生涯中,很多次听到一些企业宣称自己是解决方案提供商,这其实已经是在卖服务了。
我们知道,现在生产力过剩,很多情况下,产品同质化严重。比如简单的一个手机,市场上看到的就有几百家厂商的几万种产品。消费者如果仅仅是需要打电话的话,可能会挑花眼。
所以,现代企业营销,有句名言:“差异化求生存”。即,我们做一个产品,不要求一定比竞争对手做得好,但一定要有不一样的地方,这个差异性,是经过细分市场分析,优选了目标客户群的需求后产生的差异性。比如,一部为中老年人做的手机和一部为中学生做的手机,显然需求有很大差异。
而在差异化生存中,最大的差异化,莫过于服务品质差异化。有车的朋友可能有这样的经验,在普通的修理铺和4S店,虽然都是修车,但获得的服务品质、享受都大相径庭。这就是服务差异化。
因此,笔者认为,商用程序员要有服务的意识,以差异化方式逐渐提升个人品牌,最终,从技术上、从职业道路上,走出一条成功之路。
1.做人要有服务的态度
现代商用软件开发,一般都是大工程运作,那种单兵作战的程序员英雄时代已经过去了。这个社会是一个合作、协作的社会。
在公司里面,我们往往是一个团队进行研发,完成项目。在项目过程中,所有的团队成员都在为同一个目标而奋斗。在笔者看来,商用程序员作为团队的一分子,是必须有很强的合作精神和服务意识的。
一个人开发程序的时候,很多读者都有体会,程序的设计规划会显得随心所欲,因为没有什么禁忌和阻碍,怎么选算法都可以。但在项目团队中,一般是没有这么好的条件的。
很多时候,我们的模块,要从上级模块接收数据,经过自己的业务处理,再交给下级,依次传递,完成工作。这说明,我们写出的程序,不仅仅对最终用户负责,更要对团队成员负责。
商用工程项目一般都是以成败论英雄,一个软件,最终是以整体的用户体验获得客户的认同,从而销售获得利润,即使是做到了99%,但一天没有全部完成,一天就不能说项目成功。
因此,开发时不仅仅要考虑自己的逻辑正确、功能实现,还必须考虑团队伙伴的工作是否能顺利完成。团队所有成员的工作成果和团队所有成员的收益是绑定的关系,有一个人完不成任务,团队全体受损。
因此,团队合作,笔者首先建议大家建立“服务”意识。这些意识包括:
(1)设计和实施工程项目时,不要眼睛老是看着自己的模块,多想想别人的模块是如何实现的,有没有好的方法让大家工作完成得更好。
(2)随时考虑“上家”的数据是否有疏漏,帮助上家查漏补缺。
(3)谨慎地调用“下家”的模块,确保给出正确的数据参数,避免下家崩溃。
(4)主动热情地和同伴讨论问题,把问题尽量暴露在明处,自己有问题,主动请大家帮助,别人的问题,也主动思考。
(5)即使是模块级程序员,也试图用系统分析的思维考虑系统整体设计,帮助架构师或主管完善设计,从自己的模块角度提出优化建议,并积极讨论,努力把工作做得更好。
(6)以较为职业的态度和所有伙伴沟通,讨论问题对事不对人,尽量持客观的态度分析和解决问题,获得团队成员的认同。
(7)遇到事情勇于承担责任,对工作尽心尽力,一切以完成项目为出发点,尽力使自己和团队完成项目任务。
(8)高度负责任地完成自己的工作,努力做到0 bug,以较高的产品质量,实现对项目团队整体目标的支撑。
(9)主动关注其他一些对项目实施有帮助的情况和风险,及时提出建议,帮助项目团队查漏补缺。
(10)总之,当我们每个人为团队成员服务好了,我们的产品、项目对客户的服务承诺才有保障。
2.技术上的服务观
在笔者多年的职业生涯中,学到了很多的服务理念,在系统分析时,常常不知不觉也将这个概念带入进来。
做过程序设计的读者应该都清楚,程序设计,现在基本上都是模块化的,程序的运行过程往往是多个模块互相之间的调用结果。C++的类对象尤其说明这个问题。
但笔者不这么看,笔者认为,程序,其实是由一个个小的服务构成,每个模块都向外提供一些功能服务,程序的运行实际上是上层应用不断调用下层服务的过程。系统分析其实也是一个服务细分的过程。
笔者认为这个思路,其实是系统分析做久了,不断使用细分思维的结果。根据笔者的经验,在商用工程中,这种服务思维对解决问题非常有帮助。
我们可以看到,软件系统的每个模块,既是服务者又是被服务者,每个模块被多个其他模块服务,也同时为多个其他模块服务。服务,就是某种资源的拥有者对外提供的一种功能调用,其他模块在需要时通过对该服务提出申请完成功能,避免自行处理。
这样,模块处理的数据和逻辑趋于简单化,以简洁的应用接口NPI或API来实现互相之间的沟通,彼此既不关心对方的实施细节,也拒绝别人关心,高内聚、低耦合,最终达到系统高效、快速开发的目的。
如图1.2所示,所有的模块没有必然联系,全部靠业务逻辑规定的服务依赖关系,共同完成服务。

图1.2 演示拓扑
同时,从细分服务的观点看,程序上下级逻辑层次逐渐清晰,下层相对上层称为功能层,具体实现一个计算服务,上层相对下层称为业务层,通过组织服务实现自身逻辑。各层之间逻辑清晰、分层明确,形成服务栈的模型,最终实现对客户的一体化服务。
如图1.3所示,各个模块形成明确的服务支撑栈,每一级都是上级的功能层和服务支撑层,也是下级的业务层,各级共同实现最终的客户服务。
简单说来,在技术上,服务思维其实也是细分思维,后文有一个具体的实例来讲解如何通过细分服务来实现业务的优化。
3.Move Loading问题
商用工程一般是团队开发,因此,从服务问题带出了一个相对很常见但又很敏感的话题,就是Move Loading。
前面讲过,Loading就是服务器的带宽压力,后来又被引申为计算压力,是对资源的消耗。

图1.3 整理后的演示拓扑
在团队开发中往往有这样一些情况,一些复杂的算法,我们俗称“脏活”,由于设计和实现起来非常麻烦,很多时候,考虑到具体设计人员的工作能力、工作态度等,团队的架构师或者项目经理可能会刻意把这部分工作转移到服务支撑链上另外一个程序员处,由别的程序员来开发。
有时候,架构师自己参与开发实施时,也可能通过这种方法转移自己的压力。当然反过来也有,有些架构师可能考虑到某个项目成员的能力局限,主动帮助该成员完成部分工作,就直接写到自己负责的模块中。
这类行为,笔者一般统称Move Loading,就是转移压力。请大家注意,这种做法非常不好。
首先,从工作任务安排的公平性上说,接手的程序员多做了很多工作,但从系统的要求看,这部分并不是他的工作,相当于他做了工作但没有获得应有的承认,也就没有适当的回报,这会造成团队内部很多不满,严重的甚至会使团队分崩离析。
其次,从技术上说,Move Loading不但不减轻负担反而增加Loading。我们知道,当一个“脏活”在A模块被刻意分配到B模块完成的时候,程序必须把该工作相关的数据一起发送到B模块,逻辑上多了数据传送这一块,设计上多了一些信令处理,运行时多占用了带宽和计算资源,但这个脏活到了B这个模块,该做的事情一点没少。从系统整体Loading上计算,相当于团队、B模块的实施者、公司、客户都在买单,仅仅是为了让A少做事。这显然是错误的。
因此,在笔者主持的团队开发中,这类行为一般是严厉禁止的。如果一个程序员确实没有能力完成分配给他的任务算法,笔者可能会帮他做,但一定是放在他的模块中,同时,他本人工作量的计算也相应减少,维持一种必要的公平性。
界定Move Loading的一个简单的原则就是“资源最近原则”,即,任何工作都应该在其需求的资源或基础数据的最近处处理,因为此处的整体传输成本最低。
比如图1.3的例子,文件缓存存取效率的优化算法,一定是放在文件缓存服务模块中完成,不可能由本地数据库Cache代为完成,虽然看起来它们挨得很近。当然,数据库Cache可能会考虑到文件存储的便利性,以特定的优化格式存储数据,方便文件缓存提升效率,但这个不属于Move Loading,而是系统整体的性能优化。
笔者经历的Move Loading最明显的例子是一个服务器集群的开发,开发中,PHP的程序员需要通过socket访问我们的后台数据库服务,那个程序员一直是做脚本语言,对socket开发不熟悉,在项目方案讨论会中,就提出数据库服务要像MySQL那样也提供出apache的so接口供PHP调用,PHP只管把信令送进去,传输由C来完成。
当时大家也没在意,就同意了,项目也顺利完成,但后期测试时,感觉到前端的Apache服务向后台数据库集群提交数据时显得有点慢。并发量还是很大,但就是每笔交易偏慢,大约300毫秒,有点长了。
笔者百思不得其解,直到项目结束之后,大家聚餐,笔者才想明白这个问题:C写的so,里面建立的socket连接,由于是被PHP单次调用,函数退出,socket必须关闭。
因此,每笔交易,都需要经过“建立链路—传送数据—拆除链路”这一过程,socket没有重用性,大量的时间都浪费在连接和拆除socket上了,极大地浪费了系统的计算资源。显然这是个失败的设计。
这根本就是个Move Loading的行为,因为PHP本来就有socket的函数支持,如果PHP自行完成,由于其内部有共享变量区,可以实现socket重用,大多数访问,前述的300毫秒的交易时长,可以缩减为几个毫秒。
不过,木已成舟,笔者也只有留个遗憾了。