如何提高程序员的生产率 (2)

版权声明:本文由韩伟原创文章,转载请注明出处: 
文章原文链接:https://www.qcloud.com/community/article/252

来源:腾云阁 https://www.qcloud.com/community

 

接上篇 如何提高程序员的生产率 (1)

三. 开发过程

沟通

软件通常都需要经过很多人和很多次的沟通才能生产出来,但是沟通本身又往往会影响软件的开发速度。这是一段很矛盾的关系。好的沟通方法能降低开发中因为信息不透明导致的开发资源浪费,而又尽量减少沟通所占用的精力。

1. 需求沟通
在任何一个软件产品中,如何应对需求的变更,都是至关重要的。需求一直是软件工作得以成功或者失败的最重要因素。软件开发中很多技术和方法都是围绕着需求来设计的。

需求的沟通是需求工作的第一个环节。首先沟通的对象必须是经过挑选的,以免添加不必要的需求混乱。最佳的需求沟通是和用户或者用户代表。但是他们往往他们缺乏必要的计算机知识。而程序员却很少有丰富的需求领域的知识。这个鸿沟需要双方共同去弥补,最重要的做法是,不要光靠口说。

程序员应该认真研究需求领域的知识,仔细查看涉及的单据、原型产品、现有工作流程等,而且必须用笔记录下来,之后再去整理问题,逐条咨询用户。在仔细了解情况之前,不宜开始设计整体程序结构。

当你有一定了解之后,程序员就可以动手开发一个快速的原型,如果没有足够资源,一组界面图也是很好的。以程序的外观来帮助用户来协助设计,是最有力的需求沟通方式之一。不断的在用户的帮助下完善这些界面图,标识上那些可能需要的数据和流程,然后完善,然后再继续找用户确认。这个过程在前期可能会相当枯燥,因为那些沟通用的原型也许一点都不会给后续的开发带来重用。但是能明确需求,却是对开发最大帮组。

2. 需求评审
在需求基本有一个思路之后,需要开始准备需求评审。首先说说评审的材料,一般会是《用例图》,《领域建模的模型》,用例规约,一些《非功能需求点列表》,甚至软件的一些经过用户确认的使用流程和使用界面。

评审的过程其实远没有沟通的过程重要。这个环节主要是给大家一个明确的开端:现在我们开始做系统设计了。作为在一些公司内部,需求评审是否得到通过还是产品是否能正式开始立项的一个流程。

对于评审,很多时候会认为是,提交评审者来写报告,然后向一堆专家或者上级来介绍,最后他们提出问题,幸运的话就盖章认可。但我认为评审不应该是这样的。因为如果上级和专家没有参与项目需求的沟通,他们对项目的了解是片面的,靠一些文档和口头介绍,一定会以“挑毛病”的姿态出现。而项目本身是否真的可行,却不负担真正的责任。

因此我认为需求评审应该是项目发起者,也就是有权拍板是不是做这个项目的人,来主持这个评审会议,所有的材料由他宣讲,疑问由他回答。而其他的与会者包括专家团和可能的开发团队,纷纷向他提出问题,以确认真正的可行性。最后主持者在搜集了众多意见后,筛选出有意义的意见,改善需求评审的材料,最后拍板是否完成需求评审。这种流程,无疑会要求公司的权力下放,而事实上,如果不是下放,又或者不是老板亲自主持,很多项目最后就输在了起跑线上。

3. 合作沟通
程序员的沟通讲求的是效率,准确是最重要的要素,如果无法表达“准确”,那就只是在创作,而非沟通。对于需要高度注意力的工作——开发软件来说,群体创作的效率实在不够高。因此提高沟通的“准确”程度是至关重要的效率因数。

开发中的沟通有很多种,其中最重要的一种是需求沟通。特别是作为技术人员,和策划、美术的沟通最多。这里需要重视的就是,和非技术人员沟通,快速原型:如假的界面图或者手画稿,填写了假数据的表格等等,是很重要的沟通手段。很多时候我都会要求把这些沟通过程中的手画稿扫描保留下来作为设计文档的一部分。

另外一种沟通是程序员之间的。我一直认为程序员之间最好的沟通就是“不用沟通”。客户端和服务端之间调整协议,最好的方法是直接两个IDE联调。关于工作任务的接口,最好是定义出接口代码,并且有单元测试针对接口代码做处理。当然拉,对于这些接口的设计,还是需要当面沟通的。

最后要说说会议。我认为沟通不应该打断开发。不管是5分钟还是1个小时,会议都是一种打断。而很多会议需要开前准备很多东西,大家试图用这种方法减少会议的时间,但实际上却占用了同样多的时间。我们应该关注如何减少会议,以及减少会议对开发的打断。在午餐的时候开会,我认为是一个比较好的方式,这种打断是天然的,不会产生额外的打断。而且与会者通常不会睡着。午餐时的讨论气氛也会比较好,比起一本正经的会议要活跃的多。

4. 测试沟通
程序员和测试人员之间的沟通一定是密切与频繁的,虽然我们有JIRA、BugZilla之类的BUG跟踪系统,但是往往很多问题还是要通过口头的沟通来解决,我认为和测试人员的沟通关键是要分出优先级,对于“可重现”的BUG应该优先沟通,因为可以很快解决。而那些偶现的问题,可能沟通中一大半都会是猜测,其实可以先放后,这不表示那些问题不重要,而是应该在确定能解决的问题都先解决掉,这些时间内测试人员也可以努力把偶现变成必现。

测试沟通的内容中,对于“名词”一定要明确,因为我们会有很多个环境,必须要让每个概念有个清晰的表达方法。我们经常会在“测试”这个词上犯糊涂,比如“测试版”到底是哪个版本,“测试网”又是哪个网?甚至“测试机”是个PC还是个手机?这些都必须明确下来。比如用alpha网,beta网就清晰的多。

设计

1. 设计意见搜集
负责设计方案的人必须负责搜集设计意见。我不赞成方案由下属设计,意见由下属向上级搜集。我认为方案应该由上级负责,而意见向下属争取。如果有一个上级并不负责设计方案,那么他也不应该有权力去“评审”这个方案。这样做看起来风险很大,因为我们很多的上级并不负责具体的“设计”工作,但是对比起上级提供一些不知道该怎么办的“指示”,而非要加到方案中去的情况,可能危害要少的多。

一般来说我们认为上级会比下属的经验更丰富,如果“无权”评审方案,就必须换一种工作方式:要不自己来做设计,要不采用建议的方式说服下属——这样做的好处是能让上级对自己的概念讲解的更清晰、更细致。否则不足以说服下属。

2. 设计评审
设计评审的形式往往是一个结论是yes or no的会议。但是实际上要用好这个沟通工具,却不能怎么简单。首先,方案的评审提交者应该是最后决定者,当然应该在一个机制的监督下决定。这个决定就是,对于与会者提出的问题,包括提交者提出的问题,都要有明确的答案。

与会者可能会与提交者就一些问题有分歧,但提交者会有最终决定权。这个机制听起来很荒谬,好像自己指定法律自己又可以修改。但是,实际的开发上,如果方案和执行者并不统一,最后执行者还是会有办法“规避”一些决策。这里也涉及一个重要的问题,就是要把权力给有能力承担他的人。不能想着只是让某些自己还不放心的人去做方案,然后试图在评审的时候“把关”了事。

最后把关固然比不把关要好,但是把关要避免成为扯皮或者阳奉阴违,就必须要权事统一。真正的把技术的知识灌输到底,这样会耗费指导者较多的精力,因为需要在提交评审前更多的沟通。当然了,虽然方案又提交者决策,但是,作为业绩考核和技能评定,上级还是可以针对这些设计方案来给出专业评判意见。对于非常顽固者,实际上也有一个“秋后算账”的“威慑”。

设计方案,也是开发者的一个重要输出,代表了开发者的技术水平和工作绩效。

对于设计方案的组成部分,下面架构设计章节会有更多的讨论。

测试

1. 单元测试
单元测试的好处是让你在设计代码之前,就要考虑别人如何去使用你的代码。代码的可读性是建立在读者的前提上的,如果强迫第一个读者和使用者就是作者本人,那么在设计API和接口部分,就会考虑更少的耦合。因为强耦合会让单元测试非常难写。IDE和单元测试结合是一个很好的实践,虽然并不能节省多少输入的代码量,但起码会给与一个正式的环境。

每个细节组成部分的正确性测试,会增强你重构代码的信心。对于单元测试,我认为最少应该覆盖所有正确的路径,以及重点防御的错误路径。对于覆盖了这些重点关注的地方之后,放开手重构代码就很方便了。虽然很多语言能通过编译就已经消灭了很多的BUG,但是起码还是要运行一下。而单元测试可以分区域的运行完成的代码,避免的整个系统一起运行,突然爆发一长队的错误,让DEBUG工作无从入手。

单元测试应该是代码的一部分,和源代码一起存放。自动构建时也应该进行检查输出结果。通常提交代码的时候都会自动运行单元测试,但是当版本树有分支合并的时候,单元测试尤为重要,而最重要的是在分支上建立的单元测试,这些测试会大大加强系统的稳定性,因为检验了“合并”功能产生的代码——这些代码是最容易出错的。

2. 系统测试
系统测试的自动化程度决定了其重要性。系统测试一定要先办法找到测试的“切入点”,如对WEB系统测试,使用HTTP请求列表作为测试发起事件。对于WINDOWS测试,则使用窗口消息。对于服务器,使用协议包。一定要想办法把手工操作“记录”成可编程控制的操作。第二个重点就是要有办法用程序鉴别测试目标的输出,这里可能会用到正则表达式,词法分析,图形辨认等,但是如果一个好的系统设计,在考虑到测试时候,应该设计出尽量简单的测试输出点数据。如一些结构化网络数据,或者客户端数据中的模型数据等。

系统测试的环境自动化部署是关键,其中测试数据还原也很重要。一般系统测试需要反复执行——每次发版本都要。而测试结果很多和数据结果有关,因此一定要有办法把系统的数据(数据库内容)还原成每次测试的初始状态。这个在建立系统测试的环境的时候就应该考虑,并且做成每次测试执行前都自动还原数据,否则最后的“断言”测试很可能会失败。

3. 压力测试
我们必须要做压力测试。因为我们要提供有限服务。服务器或者客户端都应该对系统的最大能力值有一个设定,然后通过压力测试获得最可靠的数据,用以设定这个值。这样能保证程序在合理环境下正常运行。
压力测试可以通过搜集、复制产生数据。比如服务器可以记录网络请求包,然后按会话记录成一个个队列,最后通过并发一起回访,就可以模拟多人同时的情况。如果还不够压力,可以复制队列,然后修改一些互斥数据(如用户ID),产生更大的并发压力。这种测试往往能很真实的反应情况,缺点是必须要搜集一定的用户操作。但是我觉得公司一般的QA的操作就是很好的复制源。

压力测试如果用自己构建的压力数据,需要注意的事项:密度、顺序、覆盖率。有时候需要自己构造压力数据,我认为最少应该有几个可输入的调控相:每秒并发发起多少请求,每个请求的间隔时间;每个请求的顺序是否按预定顺序排列好(因为有的操作是需要先后次序的),请求包是否覆盖了系统的所有处理逻辑,而覆盖的密度比例是否和真实的环境大体类似。如各种协议格式的包,是否按外网的比例来产生。

压力测试必须要注意专用环境:带宽、磁盘、硬件、测试数据还原。高速的局域网,最好和办公网分开,否则会影响工作。磁盘要清空足够大,否则可能会让日志把磁盘填满。要用专用的测试硬件,以免造成别的工作数据的破坏——同时也提供了性能指标的硬件基准,这个尤其重要。压测的数据包数量巨大,一定会产生巨大的持久性数据,为了可以重复操作,这些数据一定要能自动还原。同时也是保证自动化重复压力测试的需要。

编写

1. 需求分析
KISS:保持简单。需求之所以复杂,是因为人的思想难以界定,充满了是和否的中间地带。所以第一原则是:只承认确定是的需求。未来的需求暂时不考虑,在第一批需求完成后,真实的第二批需求就会确定下来。人类不可能未卜先知,所以不要妄想一开始就能准确的预测需求,只需要把确定的需求理解清楚,就打下了基础。以学习的心态去了解需求,不要被固定的合同束缚,才真正能跨过需求的第一关。软件领域最大的、永恒的挑战,正是需求变更!

面向对象分析:寻求领域概念。计算机把世界抽象为0和1的序列,这种方式也影响了程序员。但是世界是复杂而多变的。固定的数据建模已经被证明了无法很好的应对需求变更,所以真正的方法应该是去先认识世界,对于业务领域中的概念做学习和界定,然后用面向对象的方法去框起来。这样“对象”和现实世界与0、1之间,就搭起了一个人类可以理解的桥梁。因此我们的需求应该从领域模型出发,使用业务领域的名词作为系统中对象的名词,尽量用对象去“描述”这个领域中的概念,而不是急着去完成一个个概念供使用者交互的“流程”。

需求分析有很多工具,如领域建模。UML中的用例图和用例规约。针对非功能需求的“场景-决策”表格。还有著名的鲁棒图,可以转变成UML的模块图。这些都是值得学习的需求分析工具。而且不会太花费时间。

2. 代码风格
这个方面《编写可读性代码的艺术(ARC)》里面说的已经很详细。只需要认真的按照里面所说的去做就可以了。

另外有一些惯例,我觉得明确一点的好:

  • 关于英文缩写命名,为了简单,一般只取前三个非元音字母为好,而且单词长度超过5个字母的才缩写。
  • 对于C++这类兼容过程式的语言,如果是过程式的变量或函数,应该采用下划线连接小写单词,如arg_num, get_file_name(),如果是和类与对象相关的,包括属性、方法,就采用JAVA风格的命名,如argNum, getFileName()

3. 代码重用
理论上代码重用这是个大话题,不应该放在这么小的位置来说。但是实际上软件开发很多地方都涉及重用。所以这里只提一些我认为最重要的:能下载到的代码绝不开发。——事实上很多程序员都以重新发明轮子为荣,并且都认为别人的轮子不好用,比如鄙视STL的C程序员比比皆是。我认为老板给你的工资很多时候并不包括重新发明轮子,不管你觉得怎样,学会使用已有的轮子都是一个程序的义务。否则等于浪费老板的投资,对于自己来说虽然好像技术长进了,但是整个项目都被你脱了后腿。与其你自己去写STL,不如去认真读一读STL源码,掌握它的内容,然后教更多的程序员去用他。否则我怀疑最后你只能去写汇编了,那就随便你了。

代码重用往往和机器性能有一定冲突,但是在我看来,很少项目因为性能差那么一点点而挂掉。但是因为代码开发效率太低,跟不上项目需求变化而挂掉的项目多的多。所以那些只以性能作为技术水平标杆的程序员,某些时候是项目的毒瘤!

4. 设计范式
过程式:C语言
结构化编程当中,最重要的设计方法是:自顶而下,逐步细化。无论你是否在写过程式范式的代码,这个方法都应该遵守,否则一定会出现划分粒度不一,难以重用的代码块。大部分的巨大函数体都是忽略这个原则造成的。

一般来说C语言会要求对一个内存进行操作,并且反应执行结果在其之上。所以通常都会有一个类似g_Env之类的全局变量,用来存放多个过程需要的数据。为了避免多个函数需要反复的通过不同的输入参数和返回值来“串接”调用,通常都会设计成一起去修改 g_Env变量。

同时也会有一个g_Cfg的全局变量充当配置文件。面向过程的代码则很少使用全局变量作为“操作台”来处理数据,因为都被封装到对象里面了。

C语言的回调模式一般通过函数指针来实现,这个几乎是做框架代码的最好用的方法。而面向对象则采用多态来取代函数指针,会更安全一些。

对于函数的返回值,一般来说不会用来真正的“返回”某些数据,而是返回的是处理的结果是否正常\异常。而处理结果的数据往往通过输入参数(指针)来得到,这个是和面向对象代码很大的区别。面向对象的语言一般支持异常,取代了返回值的“错误报告”功能,而返回值则真正是一个处理的结果。这样减少了输入参数,会让API更容易使用,符合理解习惯,也更不容易出错。

总体来说,注意抽象好函数的层次,使用统一的,尽量少一点的全局变量,使用函数指针实现回调,使用返回值作为函数的错误报告,使用输入参数,就是过程式C语言的主要特点。

OO:设计模式
对于封装、多态、继承的玩法,最后演变成GOF23种设计模式。说到底都是为了实现几个主要的原则,其中最关键的就是“开闭原则”。这方面的书籍也是汗牛充栋,特别需要关注的是《重构:改善既有代码的设计》。

面向对象编程方式是现在最利用适应需求变化的一种技术。包括泛型这种静态化的对象技术。所以无论什么原因,都应该掌握一定的这方面知识。

面向对象设计范式的核心点是去描述现实世界的概念,而非用算法和数据去模拟这个世界的运行。因为关注点在概念这种名词特征上,所以可以先初略后精致的逐步细化设计,正好符合需求变化的特征。

动态模式:JS/PHP/PYTHON……
动态类型语言以其极致快速的开发效率,容易掌握的语法而闻名。其中最能提高效率的是动态数据类型。减少了大量的声明、定义和局限的代码。甚至减少了大量的数据结构的使用。如PHP里面所有的数组都是哈希表,而JS/PYTHON里面的Object就是哈希表。实际上AS3.0也是有类似的特性。更好的利用这些语法糖和数据类型是提高开发效率的关键。所以我也反对在这类语言里面自己重新包装一套类似C语言的数据结构或者容器类库,实在是没必要。

动态类型语言另外一个特点是脚本化解释运行,不需要维护堆栈,也不需要考虑编译的定义。甚至还有交互式终端一行行的去命令式运行代码。把尽量多的业务逻辑的处理细节“过程”用动态类型语言(脚本)来执行,是提高开发效率的重要手段。如果你比较一下用C语言编写CGI和PHP比较一下,你就知道效率相差有多少了。

特定领域:SQL/SHELL
SQL这类语言和一般的语言有比较大的设计差别,他更多的设计为“表示”而非“命令,所以需要另外一种思考方式。而这类语言因为和领域结合很紧,所以是强大的领域工具,包括SHELL和AWK这种脚本语言,具备别的语言非常费劲才能实现的功能。在这些领域,尽量使用这些语言会有事半功倍的功效。

SQL中的函数和存储过程,是强大的工具,有时候整个统计系统都可以直接在数据库里面运行。而很多运维的系统工具,用shell来实现则会简单高效,因为需要操作的功能是操作系统本身。

函数式:强大的数学表达力
Lisp,schema,erlang这些函数式语言,采用链表来描述一切,只需要栈而无需堆,用递归代替循环,这些都很符合多核并行运行的要求,而且还能很方便的中断和恢复程序运行。在业务需求变化较少的,而计算任务比较多的场景,是一种非常强悍的编程工具。对此我了解不是很多,但是很希望有机会能用函数式语言做一个东西看看。

构建-集成-部署-测试

版本管理-构建脚本-自动构建-构建包管理:
所有的源代码都必须进入版本管理;所有的构建、部署、测试脚本也需要进入版本管理,所有的一切都应该进入版本管理。一个构建系统的运行,应该是可以完全从版本管理服务器上重建一切的。

Hudson是一个很好的自动构建服务器,但是构建脚本你还是需要自己去写。确保你的脚本可以从零开始构建,并且在所有你可能碰到的平台上正常构建,如32位和64位系统。

构建出来的软件包,应该让hudson这类系统自动发布给所有相关的人,而且可以很方便的通过网络获取到,如HTTP或者SVN。并且需要保留多个历史版本以备回滚。

部署脚本-部署环境-自动部署
部署脚本往往是用动态语言写成,还会带有数据库操作等复杂的内容,因此必须也纳入版本管理。部署环境应该是足够安全而且和生产环境有足够快速的网络连接的。有了这些保障,才最后有自动部署的可能性。最好就是有一个部署服务器是和生产环境和办公网络,都属于内网(局域网)连接的。简单的自动部署工具必须要运维人员能掌握,并且取消开发人员的使用权限,避免开发人员直接修改线上服务产生的故障。

各类测试-自动测试-结果报告
代码提交前应该运行完成单元测试,代码在自动构建以及自动部署后,应该自动运行一定的系统测试脚本,结果放置到构建报告中。

一般来说,测试环境有几种:开发环境、内测环境、外测环境、运营环境。

在开发环境应该是单元测试的运行环境。内测环境则应该是在自动部署后的其中一个环境,用以和产品人员沟通和验证基本功能。外测环境应该和运营环境尽量一致,自动部署后,所有的测试失败和人员手工测试缺陷,都应该视之为BUG进入跟踪系统。而且外测环境应该是运维人员的工作范围,不应该让开发人员上去捣鼓。经过了这三层的测试环境,不管是代码功能还是部署设置,应该都能自动化的在运营环境上可靠的运行了。

重构

两顶帽子:加功能、改结构
关于重构有很多经典的书籍可以参考,比如《重构:改善既有代码的设计》《重构与模式》。但其中很重要的一点,就是重构的时候禁止添加新的特性,因为会让代码在修改的时候陷入复杂的“连锁”修改,从而让重构困难增加。同时如果你修改了特性,也需要新的单元测试来保障重构的正确性,这些都是自己搅合自己的做法。而在增加特性的时候最好也不要重构,因为你在努力通过测试的时候,也是在整理和验证需求。只有需求确定了,重构的目标才会清晰。

单元测试的重要性
重构的一个最大的困难,就是怕改的代码太多,从而出现BUG。但是如果你有一个覆盖率很高的单元测试集合,你就可以比较放心的去修改代码,因为一旦出错你立刻就能知道。当然有很多时候单元测试并非万能,有些情况单元测试确实无法覆盖,或者架设测试本身比较复杂。所以说重构还是一个比较高风险的动作。测试的工作本身是重构成功的最重要保障。

明确重构目标
很多时候重构起源于某些需求难以加入现有系统,但是如果只以这个作为重构目标,往往结果并非完全让人满意。实际上重构的目标应该是一个对于需求变更的总结以及前瞻。某些时候是以实现某种设计模式作为重构的目标。而选取的模式就是对于需求分析的结果。重构中目标的选取是至关重要的,这个需要对业务和技术有深入的理解,从另外一个角度看,重构目标和一开始的设计工作是同等的工作。所以说软件开发的“架构设计”实际上是持续在进行的。

加班

加班的原因:学习、体力活
程序员的加班司空见惯,究其原因,有两种,一种是不知道如何实现,另外一种是工作量大。

对于第一种情况,有些人会写一堆代码然后调试,发现不对又改。而我建议是应该把“不确定”的部分,设计一些代码片段功能(单元),然后使用单元测试作为验证,通过逐步掌握这些功能,最后形成可重用的代码。这会是最节省时间的方法。

对于第二种情况,应该多考虑设计一些工具。比如使用一些脚本语言来代替一些数据的体力活,尝试抽取可重用代码来简化逻辑。做这些可能一开始会比较费时间,但是长期来看,却是可以节省下大量的加班时间。

实际每个程序员都知道生命宝贵,并不会有意的拖延工期,除非觉得这个工作实在是不值得去做。所以因为进度原因导致的强制加班,最后都会被“减班”还回来。损失比不加班还要大。《人件》里对于这个有详细的论述,我也不去重复了。

如果是自愿性加班,本节会有用。如果是被迫性加班,实际上是无论如何不会增加整体的开发效率。所以如果真的要用加班这招,最重要的是变“被迫”为“自愿”。

如果作为团队领导者,你看到整个团队已经能在每天6小时内高效的工作,再强迫加班最后只能是降低开发速度。原因不再细速,可以参考《人件》。因此,如何让团队加班的方法,应该改成如何让团队不用加班的思考。改进开发工具,提高开发技术,加强团队建设,这些都是有效的方法,唯一需要避免的就是如何“强迫”团队加班。

避免加班的方法:思考设计、学习提高工作效率的方法、在上班时间内加班
根据我的经验,如果一个程序员一天有6小时都高度注意力的做开发,实际上开发效率已经可以达到很高,而很多一天工作12小时的程序员,其高度注意力时间往往只有4个小时。所以不加班完全是可能的,除去别的因数,对于个人来说,避免自己玩死自己是最用的知识。在动手之前先仔细考虑清楚细节,可以避免后期逻辑BUG的折磨,这种BUG是最难调试的。而学习各种工具以及关注代码在提高开发效率上的知识,则是另外一个重要的手段。因此再次吐槽那些不重视开发效率的程序员,你们轻视面向对象,轻视软件工程知识,轻视那些对程序性能无关的知识,最后的苦果就是——一辈子加班!

程序员的状态起伏

【设计-编码-调试-总结】的循环
程序员在开发程序的过程中,往往会经历以上几个阶段,在编码阶段是状态的高峰,调试阶段开始陷入痛苦,很多时候会跳过总结。整个循环完成后,则是疲惫不堪,注意力无法集中。因此我们可以注意几个重要的环节,减轻这些压力。

首先设计阶段如果能尽快热身,直接更专注的深入细节,则会避免无休止的修改方案,对于开发的指导也会更明确。而编码环节采用更多的工具和技巧减少体力和脑力的消耗。如果设计阶段和编码阶段的工作能关注代码可读性和结构,最后的调试往往也不会非常困难,然后为了奖励一下自己,可以写一点总结。

打破循环造成的心理影响
如果在每个阶段中能尽量多的融入下一个阶段的工作,同样也可以起到降低压力的作用。比如在设计阶段就把很多重要的代码命名工作做了,在编码的时候就顺手解决所有的语法错误,调试阶段的测试用例就可以用来作为工作汇报的材料。

避免疲劳战术的方案:缩小循环体
在开发的循环中,每个阶段越长,最后的疲劳就会越大,而每个过程越短,则越容易被接受。所以尽量在设计阶段划分的细致一些,可以明显的降低后续的工作压力。缩小开发流程也是敏捷开发模式的重要观点。这对需求分析、代码结构、测试技巧都有很高的要求。重视这些知识,就会明显的降低工作压力。

四. 架构设计

逻辑架构

逻辑架构主要是为了明确需求而做的设计。针对需求以及需求变化作为架构目标。因此逻辑架构在架构设计中,是对于提高程序员生存率最至关重要的一个设计。采用合理的逻辑架构,将会大大降低需求变更对开发的延迟作用。逻辑架构最直接指导代码中互相耦合的情况,仔细设计好耦合的规则,会让后续开发事半功倍。

逻辑架构的经典模式有《POSA》中指出的几种,其中以分层和微核模式最为常用。MVC和管道过滤器模式则在特定系统中是最优模式。后续基于分布系统的SoA等架构模式,则多是建立在分层的基础上。分层模式可以按需求关注点来划分层次,因此可以安排不同经验水平的程序员分别承担。微核模式则直接按具体功能划分代码,适合水平相差不大的团队进行功能的并行开发,因为可以单独测试。

不管何种逻辑架构,都是把功能-代码划分成多个不同的部分。这些部分通过一定的规则互相耦合。这提供了最好的工作量划分依据,以及互相之间的接口定义。由于存在接口定义,将来人员流动的时候,也可以根据接口来理解代码模块的逻辑。良好的工作划分,能极大的降低程序员之间的低效沟通,使得工作能被多个人同时推进,而工作能被同时推进,则是软件项目能利用好人力资源最直接原因。

需要指出的是,很多使用非面向对象语言的项目,特别是C语言项目,非常蔑视进行逻辑架构设计,因为所谓结构化编程其实约束很少,而大家往往直接忽略掉。这是非常大的问题,导致一些复杂的“过程”最后完全无法维护。有很多项目到最后不管如何增加人手,都无法提高开发速度,就是因为实际那些代码无法利用更多的人力,反而增加了更多的沟通成本,架构直接降低了开发效率——往进度落后的项目增加人手,只会让进度更见落后。

运行架构

运行架构主要是为了解决运行时的质量需求,主要分为性能需求和可用性需求。系统的性能需求除了在代码内部通过算法来提高,往往还要采用缓存和并行的方式来扩展。这就涉及到程序的运行时设计,如数据处理流,多进程间通讯,数据的多种格式转化。对于可用性,实际上也是通过并行热备的方法来实现,因此运行时的各种控制命令、地址、消息数据流也是运行架构需要考虑的。

运行架构一旦确定,等于大部分的“实现”代码确定了下来,设计一个有足够扩展性和可用性的运行架构,可以减少后期为了扩展性能或提供可用性做更多的代码。而且也降低了系统在运行期对开发工作的干扰——正在运行系统出了问题,往往是在节假日或休息时间,都会迫使程序员回公司或者上线维护,极大的增加了开发人员的疲劳,同样会影响项目的进度。因此一开始就考虑可用性和性能扩展问题,并且实际的用代码去实现好,绝对是一个未雨绸缪的好方法。明智的项目经理会愿意在项目前期花多一些资源在这种“非功能性”方面的开发,从而得到一支士气水平稳定的开发团队。

通常运行架构需要至少设计一个可以水平扩展的接入层或逻辑层,以便通过增加服务器来提高性能扩展。因此也需要预先设计负载均衡的系统,如果是WEB系统,用DNS轮训是最简单方便的,如果是游戏类用户间交互比较强的,设计一个“目录服务器”用来提供客户接入地址是最少需要做的。

可用性架构中,最少要为逻辑服务器和数据库(持久层)服务器设计至少一个热备服务器,在机器断电或者机房断网的情况下,可以经过程序自己的检测机制,自动跳转到备份服务器上处理。一般数据库服务器mysql采用master-slave用来备用,而逻辑服务器则可以使用目录服务器来指定备份服务器地址。当然了,如果目录服务器也需要热备的话,则需要另外一些代码支持,如客户端本身就会去多次尝试不同的目录服务器。

一般有“架构设计”的系统都会有运行架构的设计,所以我并不认为需要如何强调其必要性,反而需要强调的是,系统架构设计,远远不止是运行架构的设计。

开发架构

开发架构一般用于满足开发时质量需求,和逻辑架构有密切的关系。开发架构定义了源代码、二进制和发布包的文件、路径、负责人的划分和依赖关系。一个按照固定开发架构设计的系统,能方便的开发出持续集成的各种工具。

开发架构一般的主要呈现形式为SVN目录构造方式,或者在makefile、IDE的项目设置文件中。一个软件通常分成多个不同的“系统”或者“项目”。很多如Eclipse需要每个项目只存在一个执行入口main(),所以开发架构在这种情况下同时也收到运行时架构的进程设计限制。

一般来说功能相对独立的代码会成为一个“模块”或者“系统”(项目),通过提供程序链接接口,如DLL,.a和.h文件,JAR文件,或者SWF文件来提供。这个粒度的划分有多种标准,如按功能的,按开发者的。但是有一个通用的准则,就是如果已经设计的不同“模块”中都有调用同一个或者类似的代码块。这块代码就有资格自己成为一个平行的“模块”,以便日后修BUG可以单独升级。

个人比较喜欢按业务功能的层次来划分模块,这样有利于划分任务,减少代码互相影响,最重要的是比较容易让人理解。但是就必须经常把工具类模块协商抽取成为“公共模块”。

部署架构

部署架构对于持续集成,降低程序员的运营期压力,有至关重要的作用。一个好的部署架构可以提高性能和可用性。让程序员可以按部就班的去解决问题。

运行时架构在软件层面提供性能和可用性。而部署架构考虑的更多是硬件层面。比如网络机房的分布,服务器硬件的搭配,监控和维护工具软件的安装。开发测试网络和运营网络的设置。关于安全性的配置。

几个比较明确的经验是:

  1. 尽量靠近用户部署机房
  2. 所有机房间应该有一条专用的局域网链路,让服务器间数据可以有专用线路可用。
  3. 运营网络的防火墙对于公网接口设置为只开启服务端口,运营和维护端口全部放在局域网线路上。
  4. 开发办公网络不能直接和服务器的局域网直接连接,降低个人PC被入侵造成的影响。所有运营网络的服务器都必须限定有限IP的机器去连接远程登录,并且这些机器需要是LINUX的。
  5. 对于服务器硬件按照磁盘IO密集型、网络IO密集型、计算密集型、存储空间大需求型、经济节省型的分为5种型号,由业务技术人员仔细选择。
  6. 在测试、开发、管理等资源要求很小的服务器上,尽量采用虚拟机,最大化利用硬件,同时减少维护成本。
  7. 对于日常服务器的维护和监控,建立报警和预警机制,如磁盘、带宽、CPU、特定进程、监听端口。
  8. 对于机器和带宽的部署,采用最少可用性保障的策略,就是最少有2份服务器以及2倍预计带宽需求(或其他需求)。
  9. 对于部署过程需要的脚本、配置文件、安装包,必须要有自己独立的SVN或者文件服务器管理。同时也需要建立“自动化”部署的工具。在这些工具完成之前,不应该正式对外运营。而部署工具本身应该在“外测”环境下经过详细测试,才更新到正式运营网络上。

数据架构

数据架构对于任何业务都有很重要的影响。对于用户产生的数据,架构本身需要容易备份和恢复,数据就是财富,而数据架构是存放财富的方案。对于开发团队产生的数据,则需要完善和方便的管理工具,便于修改和增加这些数据。

数据架构本身还涉及性能、易用性等多个方面。一般来说分为使用关系型数据库和不使用关系型数据库两种。但是不管那种,对于数据的备份(热备)和恢复都是最首要应该考虑的问题。数据的结构过于复杂其实不利于备份和恢复。而很多时候大数据量又产生“分区”需求(分库分表),这是分布数据存储的课题。

对于使用关系型数据的,一般需要依赖数据库服务器自己的热备功能和分区功能,或者自己去实现。然后使用ORM软件来操作这些数据。所以关系型数据不宜做的过于复杂,太多的“外联”表会让整件事变得很复杂。
如果不使用关系型数据库,现在的NoSQL风潮提供了很多可选的方案,这些方案最大的特点就是对于结构复杂的数据有良好的扩展性,对于大数据量也有优秀的扩展功能——重要的是不用你自己去代码实现。缺点则是可能无法保证“统一性”。比较实际的观点是,尽量少使用关系型数据。虽然它的概念已经深入人心。

每个模块都可以做架构设计

上面的几个架构,基本包含了整个软件架构应该涉及的方面。而我们往往比较重视一些“大项目”的架构设计,而比较忽视一些中小项目的架构设计。实际上大部分项目并非“大项目”,而大型项目本身也很可能划分成更小一点的项目来外包,所以我们应该对于每个项目,都以架构设计的观点去分析和设计,只是不需要陷入文档的案牍之中就好了。

一些好的架构设计,也可以在多个模块和项目中共用。这种共用的好处非常多,一个是可以节省开发时间,另外也可以少走以前的弯路。最重要的是如果积累下这种设计的文档和思想,可以让整个团队的开发效率都得到提高。

五. 管理激励

明确目标

目标管理是现代管理学的一个重要成果。明确团队的目标,然后让所有人都理解并认同这个目标,是管理中最重要的工作。只有每个人明确了目标,才可能发挥出他的主观能动性。而且要有明确的目标,团队成员之间才可能合作。否则成员之间可能会互相拆台或者推卸责任。

目标应该是整个团队的目标,而不应该是只划分到个人,因为这样不利于团队的整合,而会形成严重的个人主义。当然我们最常见的是团队中对于目标不明确。

目标应该包括以下内容,才称得上明确。首先是项目要满足的市场需求,这是项目的愿景。其次是项目怎样才叫做的好的标准,一般是和竞争对手比较的结果。最后是项目目标和个人发展之间的关系。三层目标必须都让每个人明确和认同,才能真正激发出团队的动力来。

市场需求如果大家不认可,就会觉得项目不值得做,这个时候就需要领导者来用数据和事实证明给大家看。

项目优秀的标准,不可无限拔高或者虚无缥缈,明确的竞争对手数据是最好的标准,虽然这个信息通常很难获得,但是这个是团队中非常有凝聚力的信息。应该尽量的获取,然后公布给团队。

每个人和项目成败的关键,则是核心的激励机制,虽然很多时候开始很难明确到金钱上的奖励,但是应该起码提供一个“最好情况下”的许诺。很多项目以收益分红,或者过程评价来加工资作为明确的激励。

主要手段

人在集体中能发挥的作用必然比单个人要大,这是人类作为群居动物的天性所决定的。如果你有一定的工作经验,必定会体会过一个团结的团队所发挥的良好作用。如何能构造起一个真正的高效的团队,我认为最重要的方法是“沟通”。这种沟通必须是全方位的,包括团队成员之间的私交,他们互相之间在工作能力、人品性格、兴趣爱好方面的了解。在管理者和团队之间,对于项目前景、公司状况等一切和团队切身利益相关的方面的信息,都必须有良好的沟通。如果程序员觉得你在隐瞒些东西,你一定就是在某些方面有所保留。这种猜忌会破坏团队的形成。所以对于大量看起来是“保密”的信息上面,对团队尽量多的公开,是能让团队得到主人翁感觉的重要手段。传统管理方法中很多时候强调公司的信息保密,个人对于团队的效忠。而这些在高科技开发企业中是行不通的,因为他们就掌握着公司的最大机密——产品开发。

管理者除了对“人”做事,直接做事也必不可少,开发工作有很多方面是在做设计,所以开发工作本身并不是所谓的体力活,而是一些精巧的设计工作。除了自己参与开发工作,对于团队其他部分的设计工作,特别是其中的重要决策,管理者也需要尽量多的参与。一方面这种参与可以培训团队成员,另外一方面能加强对于项目控制。在决策的过程中,通过广泛的讨论,也是团结团队的一个手段。当然刚愎自用的处理方法就会产生反效果。

因为程序开发本身是一种高度自律型的工作,所以管理者的“不做”比“做”更重要,而且应该提供不受干扰的环境。在程序员注意高度集中的情况下,工作效率是最高的。试图不断的“参与”开发而打断程序员的工作,其实是在降低工作效率。好的管理者应该能让程序员沉浸在开发工作当中,完全不去考虑什么进度报告,工作汇报之类的事情。

因为程序员对于电脑的熟悉,所以经常会把一些别“专业”的行当混合起来交给程序去做。比如写代码的同事去处理数据库,由或者让程序员同事负责运维服务器和网络……术业有专精,如果一个程序员专心的做数据库的工作,他会比同时做其他几个事情做的更好。更不用说让程序员去夹网线搬箱子这些了。总体来说,提供资源支持,降低团队的“非专业”精力消耗。

人力分工

按照专业领域分工;专业再细分分工;软件开发团队应该学习外科医生的团队,采用严格的分工合作,来降低混乱的沟通。每个人只负责自己的事情,会做的非常好。因此如果程序能很好的分成5个模块,就使用5个程序员,然后为他们配上专业的秘书,数据库管理员,测试人员,项目经理,运维人员,IT人员,以及其他的资源处理人员,比如美术资源处理专员。

关注工作分切点的协议,因为人力分工是提高效率的重要手段,所以人员之间的切分点——协议或者流程,就是很关键的东西。客户端和服务端程序员之间,通常就是直接的通讯协议,应该把通讯协议变成一种代码,然后双方都使用这种代码。客户端程序员和美术之间通常也应该使用某种基于软件工具的格式文件,比如是UI编辑器的结果。处理WEB的团队往往由美术提供图片,然后由专人切图变成HTML,而这个HTML文件就是很好的协议文件。有些数据录入人员使用JSON格式的文件,或者是某种严格定义的EXCEL文件作为协议文件。

很多团队喜欢“广播”方式的沟通,任何一个事情都让所有人在一个QQ群里面看到,或者任何一个事情都抄送邮件给一堆人。实际上这种沟通只会浪费别人的时间,所以管理者应该降低沟通范围,减少“噪音”。

在《人件》中,提到了高效团队的形式——胶冻团队。也就是我们所说的非常团结而且互相信任的团队。很多公司会以拓展训练和部门活动的方式去推动形成团队互信,这些都是有用的。但是在工作过程中,更重要的是让信息共享来打破可能存在的隔膜,给与更大的自由发挥空间让团队成员有机会互动,组织团队成员间一起讨论和解决问题。让团队成员都感觉到受关注,并且提醒他们互相关注。多让大家一起吃午饭。

绩效考核

绩效考核应该考核做了什么事,而不是做的怎么样;这个和很多按“结果”管理的老板很不接受。但是如果你只是想把绩效考核作为一个发奖金的机制的话,单独使用奖金考核机制就可以了。绩效考核应该是推动别人去做某件事的工具。对于已经明确的方法或者子目标,通过这种细化的方式去指导下属工作。因为是需要事后算账的,而且是量化的,所以下属会对这个事情很认真,同时那些不好量化的事情,管理者也很难执行绩效考核。所以对于“去做某些事”,是绩效考核最好的目标。

通过考核结果提供正式的工作方法意见。绩效考核本身有个反馈的过程,这个反馈的过程应该提供给下属针对每个具体事情的建议。这种具体的,单独的一对一的指导,会提高团队的稳定性。而且也让团队成员获得“受关注”的感觉。这种感觉是形成高效团队的重要工具。

考核不能代替目标,不能阻碍目标,而应该是一个沟通工具。绩效考核通常会涉及大量的文档工作,也会有很多量化的工作,但是这些工作,往往不能完全代表商业目标的完成情况。所以有些时候只是平白的增加了管理工作量。绩效考核的设定首要需要考虑是不能阻碍团队目标的实现。很多时候甚至难以覆盖目标。所以绩效考核应该是成为一种沟通的工具:定时、定量、定人的交换对于工作方法的意见。

目标达成情况作为考核的客观指标,但不应该是主要绩效考核指标。最简单的绩效考核指标就是收入或者利润率。但是这种简单指标除了在动机上提高下属的工作热情外,并没有从方法和经验上帮助团队成员。有效的考核是引导下属安装更有经验的方法去实现目标。

进度掌控

通过测试来了解进度;一切软件的开发,如果无法测试,就是无法了解的。没有人回去通过读代码了解项目的进度。所以项目本身必须具备经常提供“可测试”的功能。一旦进入测试,项目完成的数量和质量都立刻一目了然了。
用提高工作效率和提供资源的方法推进进度,而不是通过加班。加班是最有效的降低工作效率的方法。只有降低本身进度推进的损耗——内耗,改善工作方法——更好的工具和更多的知识,除此之外很难有别的加快进度的手段。但是,你也可以通过增加更多的资源,比如人力或者机器。如何把资源放在合理的地方,本身是一门学问,本文在上面很多节都有提到如何有效的使用资源——人力和金钱。

庆祝每一个进度节点。团队需要鼓励,才能保持速度。庆祝本身又是凝聚团队的一个契机,让成员觉得在这群人中间能不断取得成功,本身就会增加互相合作的意愿。如果无端端搞一些团建活动,会让人觉得厌烦,但是庆祝进度节点,则大家都会认同,可能只是一起吃个饭,但是也是非常有效的一顿饭。

确保公布进度的目标,并且争取团队支持。进度的目标必须能达成共识,“无理”的目标实际上不能成为目标,团队从此也会陷入最差的状况——没有目标。所以进度目标本身需要让所有人理解并且愿意去努力。通过共同的进度目标,来促进团队中所有人的积极性——那些喜欢偷懒的家伙也会不好意思。

在重视质量的情况下推进进度,并且为质量牺牲一定的进度。项目质量是团队和个人为之“自豪”的重要因数。如果你想拥有具备荣誉感的团队,就需要为此付出一定代价,可能牺牲一些时间,来获得更好的产品质量,实际上这是个事半功倍的事情。因为产品质量在长期来看,还是能提供更好的效益的。进度压力如果造成太多的粗制滥造,会让后期的团队越来越没信心赶上进度。最后被迫推倒重来,或者陷入一个“长期的重构”工程中。这些都是缺乏远见导致的问题。

预算制订

放松固定资产的预算,尽早投入固定资产预算。项目初期成本中有很大量的固定资产投入。而这些预算看起来可以用更便宜但是质量更差的替代品来替换。但是从长期来看,人力成本才是最重要的大头。而如果你的固定资产太少,也会难以招聘或者稳住最优秀的那批员工。优质的固定资产不但折旧率更低,而且能让程序员发挥出更好的功效,直接节省那些高额的人力成本。
收紧人力资源预算,重视10倍效率员工的作用。人员的沟通成本,是所有关于人员的成本中最高昂的。人越少,需要的沟通就越少。很多公司在发展期喜欢快速扩充团队,希望能抓住机会尽快占领市场。但是请记住,一个母亲怀一个孩子需要10个月,100个母亲去怀一个孩子,同样需要10个月。团队和项目的进度并非靠人的数量增加就能加快的。而且更重要的,优秀员工的工作效率最大可以比一般员工的效率高10倍以上,你只需要给他们5倍的工资,就能节省下另外50%的工资,同时还有不止50%的固定资产投入和其他费用。少而精的团队在更方面都比庞大良莠不齐的团队更有效率。
针对风险,不断修改预算,项目进度越后期,预算越准确。软件项目开发和拍电影一样,具备很高的风险。不但成本难以预料,而且收益也要最后才能知道。因此对于预算来说,不断的记录和回顾预算执行情况,以及其效率,修改现行预算方案,才能真正适合开发的进度。那种一年都按年度预算去执行的死板方法,是无法适应快速变化的软件开发项目的。

风险防御

留出过冬的口粮,定出必须笼络的人员,尽早进入资金循环。软件项目风险之高,难以用其他行业完全类比。就算最著名的公司如微软、IBM也频频推出失败的产品。因此在资金上留有尽量大的余地非常重要,很多项目经历过濒死体验后,才能实现商业成功。软件项目能继续高效的开发下去,核心人员是至关重要的,让他们拥有足够的耐心和薪水,和项目继续下去,才有收获那天的希望。而降低风险的最好方法,则是尽快去接触风险,尽快让产品上市,让产品开始尝试赚钱,能让开发方向尽快明确到最实际的内容上来。团队内的争论和疑惑也会因为实际的财务数字而消散,大家的目标会更清晰——赚钱。

师徒制度预备人员流动。人员流动是不可避免的,就算你做到100分,还是会有别的原因导致程序员离职。因此除了在代码上提倡可读性和良好的结构,重视文档和过程记录,采用预备接替人员是很重要的手段。让资深程序员和一个学徒程序员一起工作,能尽快提高学徒程序员的能力,同时也让代码能有多一个人熟悉。在师傅离职后,徒弟就可以升职到师傅的地位,也是一种良性的激励。而徒弟对于这个代码的感情,是别人所无法比拟的。

尽早进入团队和项目估值,寻找合适的买家。项目除了直接到用户市场上去实现商业价值外,还应该看到,软件项目因为他的结构性和扩展性,往往会在更大的范围内具有价值。一些其他商业方向的公司或者投资人,可能会看重你的项目的扩展价值。而这些扩展价值需要一定时间来让别人认识到,因此对于管理者,在清晰的掌握项目的全面价值的情况下,尽早的介绍给潜在的投资人和客户,能让项目拥有更广阔的前景,从而降低其天然的风险。

 

posted @ 2016-12-11 21:50  偶素浅小浅  阅读(342)  评论(0编辑  收藏  举报