Code Complete — 编程之前

前言

《代码大全》是本经典著作,不同阶段,不同水平的人看了必然会有不同的感受。像我这种新手最关注的可能是代码质量部分,而高手可能会更加关注架构。本系列博客是自己在学习时的一些记录和感想,偏前端,或许很多内容你都了解了,但你一定会发现亮点。

软件构建

软件开发过程中包含了许多不同活动,一套专业完整的流程是这样滴:

  • 定义问题
  • 需求分析
  • 规划架构
  • 软件架构或高层设计
  • 详细设计
  • 编码与调试
  • 单元测试与集成测试
  • 系统集成和测试
  • 保障维护

这是传统的软件开发模型,许多高校的毕业设计就是按这个模型来的。当时最大的感觉就是文档写的飞起,写代码反而意犹未尽。事实上,这才是真正意义上的“编程”(除去定义问题之外的其他所有活动),写文档的过程其实包含了规划和设计的工作,当详细设计的工作完成后其实程序逻辑已经非常清晰了,这时候”编码”就成了一种机械化的体力活。

而软件构建,就是以编码与调试为核心的编程,俗曰编程。

编程(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)”的程序员首先决定他要表达的思想是什么,然后决定如何使用特定语言提供的工具来表达这些思想。

选择主要的构建实践方法

构建实践方法有很多,从编码,团队协作,质量保证,开发工具各个方面有意识的选择最适合你的项目的实践方法。

以上,是在编程之前的工作。

结语

编程前的工作看着有些无聊,但却非常重要。之后会继续分享自己的一些心得:

可以很明显的看出对于架构部分我基本是略过.....额,不过有趣的是,等再过两三年自己水平提高了一个档次之后,再回来看架构部分必然会有更深刻的体会,如此也是记录自己成长的一部分,善哉。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 156,130评论 24 683
  • 关注“文哥解课”,一起分享陪伴孩子的快乐! 许巍的一首<蓝莲花>唱出了多少人心声,还包含启迪了多少人曾经的、当今的...
    文哥解课阅读 38评论 0 0
  • 有句话说:“不优秀的人才整天纠结爱不爱的 丑的人才一天到晚想着找对象 。 有人说单身好,有人说恋爱好,还有人说同性...
    你喜欢nba阅读 2,974评论 0 12
  • 老舅把信的封口仔细地折好,递到我手上,盯着我说:“交给英子!”那神情,好像是电影里政委交代给某地下党员一项艰巨而重...
    深巷客阅读 188评论 4 16
  • P1-33 喂故事书长大的孩子 激情可以使事情有好的开始。但是,“热情”才是持续下去的力量。十年的持续力,就是我爱...
    喵妈爱小鱼阅读 69评论 0 0