前言
《代码大全》是本经典著作,不同阶段,不同水平的人看了必然会有不同的感受。像我这种新手最关注的可能是代码质量部分,而高手可能会更加关注架构。本系列博客是自己在学习时的一些记录和感想,偏前端,或许很多内容你都了解了,但你一定会发现亮点。
软件构建
软件开发过程中包含了许多不同活动,一套专业完整的流程是这样滴:
- 定义问题
- 需求分析
- 规划架构
- 软件架构或高层设计
- 详细设计
- 编码与调试
- 单元测试与集成测试
- 系统集成和测试
- 保障维护
这是传统的软件开发模型,许多高校的毕业设计就是按这个模型来的。当时最大的感觉就是文档写的飞起,写代码反而意犹未尽。事实上,这才是真正意义上的“编程”(除去定义问题之外的其他所有活动),写文档的过程其实包含了规划和设计的工作,当详细设计的工作完成后其实程序逻辑已经非常清晰了,这时候”编码”就成了一种机械化的体力活。
而软件构建,就是以编码与调试为核心的编程,俗曰编程。
编程(programming)和编码(coding)的区别就好比工程师和码农的区别。前端在接需求做开发时很多活动是在脑子里完成的,这样减少了时间和文档设计成本,但是增加了编码和维护成本。
前端编码的工程化现在发展的越来越完善了,但是软件构建的工程化貌似还不太成熟(或许跟公司和部门以及业务有关),一些部门在开发项目时大概活动是这样滴:
- 定义问题(产品经理提需求啦)
- 需求分析(大家快来开会,我们来讨论下这个需求)
- 规划架构(这部分变成了排期,需求定好了,视觉,前端,后台,测试,你们排下期...)
- 软件架构或高层设计(自己边设计边写代码去...)
- 详细设计(自己边设计边写代码去...)
- 编码与调试(写得飞起)
- 单元测试与集成测试(自测通过没?通过可以提测了)
- 系统集成和测试(测试一边测试一边提bug,改完bug就能上线了)
- 保障维护(啥?体验不好?线上故障?赶紧保障保障...)
可见在商业公司的软件构建流程中,最重要的软件设计部分并没有和实际编码分开进行,而是很”敏捷”的融合在一起了,这种方式各有利弊,在于取舍。
重视构建过程
提高软件的质量和开发者的生产率非常重要——这是所有码农的共识。
而软件构建的质量直接影响软件的质量和生产效率:
- 构建活动是软件开发的主要组成部分。构建活动在整个项目开发期中占据了30%~80%的时间;
- 构建活动是软件开发中得核心活动。
- 提高程序员的生产率。在构建活动期间,不同程序员的生产率差率可达10~20倍。是不是好奇为什么你总是很忙么?好奇为什么大神的开发效率总是那么高么?后面会讲到。
- 构建活动的产物——源代码,是对软件的唯一精确描述。文档只是参考,代码才是硬道理。
- 构建活动是唯一一项确保会完成的工作。
现实情况下软件项目往往跳过需求和设计直接进入构建环节,之后又由于bug太多导致时间不够,测试环节也不那么严谨。但是无论项目有多匆忙,构建活动都是不可或缺的,对构建活动进行改进,是改进软件开发过程的有效途径。
通过隐喻来更好的理解软件开发
使用隐喻可以更好的理解编程,也能很形象的向别人解释一些晦涩的技术。
隐喻这个装逼的词汇其实就是一种比喻,比如计算机世界里面所说的病毒(virus)、蠕虫(worm)、臭虫(bug)等等,都是隐喻。隐喻描述了软件领域中各种特定现象和事物,借助这些隐喻,我们能够更深刻的理解软件开发的过程。
隐喻的重要性
重要的研发成果往往来自类比,通过把你不理解的东西和一些你比较理解又很类似的东西作比较,你可以对这些不理解的东西产生更深刻的理解。这种使用隐喻的方法也叫建模。
就比如气体的分子运动理论是基于”撞球“模型,它把气体分子想象成有质量且彼此发生弹性碰撞的小球,有很多有用的理论就是基于这个提出来的。
又比如光的波动理论是通过类比声波发展起来的。并提出了”以太“的概念,但很不幸的是,这次通过类比声波来建模的研究失败了,因为并没有找到”以太“这种东西。
从托勒密的地心说到哥白尼的日心说,花了1400年。而在这一千多年间,人民坚信不移的认为地心说模型是对的,当一个更加合理的日心说模型出来时,所有人都觉得这是错的。当人们相信新的理论时,都会觉得旧理论很荒谬;而当人们还在相信旧理论时,同样会认为新理论很荒谬。科学的发展史并不是一系列从”错误“的隐喻到”正确“的隐喻的转变,而是一系列”不太合适“的隐喻到”更好“的隐喻的转变。
相对于其他学科而言,软件开发还很年轻,还没有一套成熟标准的隐喻。因此必然存在许多互补或互斥的隐喻。你对隐喻有多理解,也就决定了你对软件开发有多理解。不同的隐喻彼此不一定排斥,应当使用对你最有益的。
我想起了一篇译文章进程与线程的一个简单解释,如果你觉得文章隐喻的非常巧妙,那说明你并未深入理解进程和线程的关系。
如何使用软件隐喻
隐喻是一种启发式的方法,那么该如何使用它呢?
- 通过它来提高你对编程问题和编程过程的洞察力
- 帮助你思考编程过程中得活动,想出更好的解法
相对于不善用隐喻的人,那些使用隐喻来照亮自己软件开发过程的人,他们对于编程的理解会更好,并且能够更快的写出好代码。
软件构建的前期工作
项目的成败很大程度上在构建活动开始之前就已经注定了,如果地基没打好,或者计划不充分,那么你在构建期间能做的无非是尽量让损害最小罢了。
前期准备的重要性
准备工作的核心目标是降低风险,要根据不同项目特点来选择不同的降低风险的方法。准备工作不足导致的直接后果就是项目延期,项目质量低,线上风险大,上线之后又频繁改需求。
大部分程序员都明白前期准备的重要性,但是大部分程序员都无法抵抗”尽快开始编码“的欲望。除了程序员,管理层也是这样。有个很装逼的词汇叫做WISCA综合症(Why Isn't Sam Coding Anything?为什么Sam不在写代码?),或者WIMP综合症(Why Isn't Mary Programing?为什么Mary不在编程?),介于这两个因素,实际项目开发时往往是准备不够充分。
在某部门,他们做一个项目的大部分时间都花在了会议和撕逼上,也就是花在了软件设计上;而在某某部门,项目时间大多花在编码和测试上。到底哪种模式好?需要数据来论证,也跟项目类型密切相关。但是单从质量上来看,肯定是前者更优。
两种模式都关注开发质量,但是一个在项目初期,一个在中/末期。如果你想开发高质量的软件,软件开发过程必须自始至终关注质量,在项目初期关注质量,对产品质量的正面影响比在项目末期关注质量的影响更大。
程序员的一部分工作就是教育老板和合作者,告诉他们软件开发过程做好准备工作的重要性。
辨明你所从事的软件类型
不同类型的项目,需要在”准备工作”和“构建活动”之间找到平衡。目前常用的开发方式有序列式和迭代式,前者适用于需求稳定,技术熟练,风险小得项目,后者适用于设计负载,需求并未理解透彻,项目包含了诸多风险等项目。
商业软件开发中常用迭代式。
问题定义
问题定义在需求分析之前,需求分析是对问题的深入描述。如果没有一个良好的问题定义,你努力解决的可能是一个错误的问题。
明确需求
要有一套明确的需求,这很重要。理由很多:
- 明确需求有助于确保是用户(或产品经理)来驾驭系统的功能。否则程序员就会常常在编程期间自行决定需求。
- 明确需求有利于避免争论。
- 重视需求有助于减少开始编程开发之后的系统变更情况。如果在编程过程中发现一个代码上得错误,你可能只需要改几行代码,但是如果发现一个需求错误或变更,哈哈。
稳定的需求是软件开发的圣杯。一旦需求稳定,项目就能有序平稳的进行。但实际上,IBM和其他公司研究发现,平均水平的项目在开发过程中,需求会有25%的变化。所以在构建期间,我们不得不应对这种变更: - 建立一套变更控制程序。这个成熟的公司都会有的,比如阿里的aone。
- 使用能适应变化的开发方法,也就是选择合适的开发模型。
- 注意项目的商业案例。比如有时视觉做了个很酷炫的功能,但这个功能是不是用户真的需要的?是否能提高转化率?还是视觉只是想突破自我?有些需求作为功能特色来看是不错的想法,但是当你评估”这个需求到底增加了多大的商业价值“时就会觉得它糟透了。那些记得“考虑自己的决定所带来的商业影响”的程序员的身价堪比黄金。
- 确保每一个人都知道需求变更的代价。这是程序员的工作之一,很多产品经理/运营或其他需求方根本不懂技术,他们认为很小的一个改动,应该一两个小时就能解决的。但有时候最麻烦的可能是那些懂一点点技术的需求方,他们可能接触过,或者写过一点demo,所以自然而然认为这很简单。你最好让他们知道实际成本,否则坑的是自己。
- 使用需求核对表来评估你得需求质量。
如果连你自己也不知道这需求是否合理,要做多久,你可以列个表格来核对一下。这样能帮你自己理清思路:
功能需求核对
1.是否详细定义了系统的全部输入/输出,包括来源、精度、取值范围?
2.是否详细定义了软件/硬件的外部接口?
3.是否列出了用户想要做的全部事情?
4.是否定义了每个任务所用到得数据?
非功能需求核对
1.是否为全部必要的操作?
2.是否描述了期望响应时间,处理时间,吞吐量等指标?
3.是否详细定义了安全级别?
4.是否定义了可靠性?错误检测和恢复策略?
需求质量核对
1.需求是用用户的语言书写的马?
2.需求之间是否冲突?
3.需求是否足够清晰?
4.需求是否都可测试?
5.是否描述了所有可能对需求的改动,包括各项改动的可能性?
需求的完备性
1.对于开发前无法获得的信息,是否详细描述?
2.你对全部需求都感到舒服吗?你是否已经去掉了那些不可能实现的需求?或者说并没有什么卵用的需求?
上面这个表格只是列出了一些点,不同项目的核对点肯定不同,但是可以参考,帮助你自己理清需求思路非常重要,当需求来临之后,我们不只是考虑到什么时候能够上,还应考虑上述因素以及因此而带来的成本。
天啦撸,看到这里已经感同身受了。
架构的先决条件
架构的质量决定了系统的最终质量。离开了良好的软件架构,你可能瞄准了正确的问题,却使用了错误的解决方案。
在前端,架构的选择好像就是框架的选择,构建工具的选择。但是抛开任何框架,或者自己要从头开始搭建,你需要考虑下面问题:
程序组织
主要的类
数据设计
业务规则
用户界面设计
资源管理
安全性
性能
可伸缩性
互用性
国际化/本地化
输入输出
错误处理
容错性
架构的可行性
过度工程
是”买“还是”造“?
复用性
变更策略
架构的总体质量
这上面的每一个点水都很深,就拿错误处理来说,你的设计应该考虑到:
- 错误处理是进行纠正还是进行检测?
- 错误检测是主动还是被动?
- 程序如何传播错误?
- 错误消息的处理有什么约定?
- 如何处理异常?
- 在程序中,在什么层次上处理异常?是在发现错误的时候处理?还是传递错误到统一的地方处理?还是沿函数调用链向上传递?
- 每个类在验证其输入数据的有效性方面需要负什么责任?
- 你是希望在运行环境内处理错误,还是建立一套自己的机制?
天啦撸,架构师真不容易。
最后,架构不应该包含任何仅仅为了取悦老板的东西。
关键的”构建“决策
选择编程语言
任何一种GPL都有跨界的能力,如果你爱折腾,你可以用Python,用Haskell来写前端,但这并不是一个好主意。任何时候,都不能为了使用某种语言而选择它。
编程约定
编程实现必须与指导该实现的架构保持一致,团队必须使用同一套编码规范,这点倒是比较容易做到,尤其是在大公司。
你在技术浪潮中的位置
在技术浪潮前期,针对新兴技术的编程语言和框架都比较少,程序员花费了大量时间,都是为了弄清语言是如何工作的,而非编写新的代码。程序员还花了大量时间来绕过语言产品的bug、下层OS的bug以及其他工具的bug。
在技术浪潮末尾,我们有大量的编程语言和框架可选,拥有完善的错误检查和调试工具以及自动化优化工具,以及各种学习资源和训练课程。
举个栗子,VR的开发现在正处在浪潮前期,而前端开发则处在浪潮之巅。
理解自己在浪潮中的位置,有助于自己更好的面对编程工作。如果在前期,你可能需要花大部分时间来”造轮子“或”修轮子”,如果在后期,你只需要关注编写新功能。
编程工具不应该决定你的编程思路
“在一种语言上编程(programming in a language)”的程序员将他们的思想限制于”语言直接支持的那些构件”,如果语言工具是初级的,那么程序员的思想也是初级的。
“深入一种语言去编程(programming into a language)”的程序员首先决定他要表达的思想是什么,然后决定如何使用特定语言提供的工具来表达这些思想。
选择主要的构建实践方法
构建实践方法有很多,从编码,团队协作,质量保证,开发工具各个方面有意识的选择最适合你的项目的实践方法。
以上,是在编程之前的工作。
结语
编程前的工作看着有些无聊,但却非常重要。之后会继续分享自己的一些心得:
- Code Complete — 编程之前
- Code Complete — 创建高质量的代码
- Code Complete — 代码改善
- Code Complete — 软件工艺(未发布)
可以很明显的看出对于架构部分我基本是略过.....额,不过有趣的是,等再过两三年自己水平提高了一个档次之后,再回来看架构部分必然会有更深刻的体会,如此也是记录自己成长的一部分,善哉。