# 软件开发

  • 20 世纪,计算机软件变得越来越复杂,因此人们总结了关于软件的各种技术、方法论,形成一门新的学科,软件工程(Software Engineering,SE)。
    • 研究软件工程的主要目的是:优化软件的开发过程,使得开发速度快、成本低、软件质量好。
    • 个人开发软件时,可能比较随意,不考虑软件工程。
    • 企业开发软件时,需要考虑多人协作、工程排期等问题,有必要考虑软件工程。
    • 在企业中,参与软件工程的工作人员,通常称为软件工程师(Software Engineer),俗称为程序员(Programmer)。
  • 软件工程涉及到多种学科:
    • 管理学:人们将土木工程、车辆工程等传统的管理学经验,沿用到了软件工程。
    • 计算机科学(Computer Science,CS):泛指对计算机的科学研究,比如计算机组成、计算机网络、操作系统、编程语言、数据结构、算法、人工智能。
    • 互联网技术(Internet Technology,IT):泛指基于计算机互联网的技术,比如 Web 网站。

# 软件开发流程

  • 假设一个企业,想售卖软件来营利,则总体流程如下:

    • 分析需求
    • 设计软件
    • 开发软件
    • 售卖软件
    • 售后(包括运维、技术支持)
  • 企业开发软件时,标准流程如下:

    • 开发(develop)
      • 该环节,主要是修改软件的源代码。
    • 构建(build)
      • 很多软件是用 C、Java 等编译型语言开发的,源代码不能直接被人类使用,需要编译成二进制文件,才能被人类使用。
      • 很多软件比较复杂,除了编译,还需要进行一些加工操作,这些操作统称为构建。
    • 测试(test)
      • 每次修改软件之后,可能引入 bug 等问题,因此需要经过测试,检查没有问题之后,才能交给用户使用。
    • 发版(release)
      • 如果测试环节发现了软件的问题,就需要修改软件,重新开始流程:开发 -> 构建 -> 测试
      • 软件测试环节没发现问题,则需要将软件的当前状态记录下来,作为一个版本。
        • 例如记录:v1 版本的软件,源代码是什么内容,配置文件是什么内容。
        • 早期的软件工程师,需要手动记录每个版本的内容,很麻烦。后来发明了 Git 等版本管理工具,可以自动记录版本。
      • 开发一个软件时,可能先后发布多个版本,每个版本都应该记录下来。
        • 这样能看出软件的修改历史,每个版本改了哪些内容。
        • 客户默认会使用软件的最新版本,但有的客户希望使用旧版本,比如客户没时间升级新版本、不习惯新版本。
    • 部署(deploy)
      • 该环节,是将软件拷贝到一个计算机中,正式运行。
      • 在测试环节,为了节省成本,通常将软件放在一个性能较差的计算机中运行。等到正式部署时,才将软件放到正式的计算机中。
    • 交付(delivery)
      • 该环节,是通知客户,将软件交给客户使用。

# 加快流程

  • 开发一个软件时,可能先后发布多个版本,因此需要多次执行 分析需求 -> 设计软件 -> 开发软件 的流程。

    • 但每次修改软件,都重新走一遍整个流程,耗时较久。
    • 为了加快流程,人们提出了以下几种方法。
  • 瀑布开发(waterfall development)

    • :遵守标准的软件开发流程,一步一步执行。完成一个环节之后,再专心执行下一环节。
    • 优点:
      • 对于管理者比较简单,同时只需要专心考虑一个环节。
    • 缺点:
      • 开发软件的速度慢。比如开发环节结束之后,工程师们才开会讨论测试方案。
      • 交付周期长。每次客户的需求变化时,需要重新走一遍整个流程,增加时间成本、人力成本。
    • 瀑布开发是一种传统的软件开发模式,被 20 世纪的企业广泛采用,但缺点明显。
  • 迭代开发(iterative development)

    • :每几周发布一个新版本(称为一次迭代),每次只开发软件 n% 的功能,并且让客户试用这部分功能,接收客户的反馈。等软件 100% 的功能开发完毕之后,才正式交付。
    • 优点:
      • 一边开发软件,一边根据客户的反馈来修正软件,这样能保证软件一直让客户差不多满意。
        • 否则,如果软件让客户很不满意,客户会要求对软件作出大幅修改,增加很多时间成本、人力成本。甚至,客户可能放弃购买该软件。
  • 敏捷开发(agile development)

    • 2001 年,一些程序员开始宣扬敏捷开发的思想,提倡以下职业行为:
      • 同事之间及时沟通、快速协作。
        • 传统的工作模式是,同事之间通过企业行政流程进行协作,速度慢、效率低。
      • 在开发软件的初期,就与客户沟通、让客户试用,从而及时了解客户的需求,避免开发软件的方向走偏。
      • 与其给客户编写文档说明,不如直接展示可用的软件,即使只有部分功能可用。
      • 每次收到客户的新需求时,能快速迭代出一个新版本。
    • 缺点:
      • 敏捷开发需要企业有高效的管理能力,但很多传统企业只有能力实施瀑布开发。
    • 后来,敏捷开发衍生出几种方案:
      • Scrum
        • 要求同事们每天早上开一个几分钟的站会,交流昨天的工作进度、今天的工作安排。
        • 要求每两周完成一次迭代。
      • 极限编程
        • 它认为,如果一项措施的效果是好的,则将它做到极致。
        • 它提倡自动化测试、代码评审、快速迭代等措施。
  • 测试驱动的开发

    • :先按照需求,编写测试用例,然后开发能通过这些测试用例的代码。
    • 例如客户希望软件提供某一功能,则编写测试用例,检查软件能否实现该功能。然后开发代码,直到通过测试用例。
    • 优点:
      • 开发代码时,目标更明确,只要通过测试用例即可。
    • 缺点:
      • 客户的一个需求,可能需要用大量测试用例来描述,而且不一定能准确描述。

# 相关经验

  • 分析需求时

    • 需要确定这个软件,希望实现什么功能、达到什么效果。
    • 评价一个软件好不好,需要衡量多个因素,做出取舍:
      • 成本
      • 功能(是否满足用户的需求)
      • 性能
      • 安全性(例如银行的软件系统最看重安全性)
    • 难点:程序员按照自己的想法开发软件,但不满足客户的需求,就赚不到钱
    • 难点:客户的需求不清晰
      • 客户通常不确定自己想要什么效果,只有个粗略想法。
      • 客户通常不了解计算机软件的原理,可能提出一些很难实现的需求,或者需要很大成本才能实现。
    • 难点:客户的需求会变化,导致软件开发成本增加
      • 例如客户突发奇想提了一个需求,过了一段时间却不想要这个需求了。
      • 例如客户方存在多人,提出不同的需求。
  • 设计软件时

    • 实现常见的功能时,应该先到网上找找,有没有现成的方案。也许别人已经提出了良好的方案,可以供你借鉴,或者被你复用,避免重复造轮子。
  • 编写代码时

    • 应该先规划,再行动。继续规划,继续行动。
      • 先想象出代码是什么样子,再把它编写出来,这样效率高。而不是先写一行代码,再构思下一行代码怎么写。
      • 为了避免努力的方向错误,埋头苦干几个小时之后,就暂停一下,检查工作效果。
    • 为了方便以后新增需求,编写的代码,应该具有良好的可扩展性。
    • 为了多年之后,这段代码还能被人看懂,应该将代码写得尽量容易理解,并加上注释。
    • 不要随便重构代码,这要花费很大代价,甚至代价可能超过重新开发整个软件。
    • 编写软件时,首先实现功能,然后才考虑性能。
      • 甚至,可以不考虑性能,只留下可扩展的代码,以后有需要时再花时间优化性能。
      • 在一个软件的开发初期,需求可能变化,代码架构可能大改,此时优化性能,可能做无用功。
    • 为了编写健壮的软件,
      • 将一个复合功能,分解成多个基础功能,便于单独修改、单元测试。
      • 一个软件被第三方调用时,应该不信任外部的输入值,总是加一段代码来检查输入值是否合理。毕竟第三方可能恶意攻击。
      • 一个软件调用第三方时,应该不信任第三方的输出值,总是加一段代码来检查输出值是否合理。
      • 严谨的情况下,假设每个环节都可能出现异常,然后编写一些代码来处理这些异常。例如调用一个函数的耗时太久怎么办?
  • 测试时

    • 建议给软件编写很多自动化测试脚本。这样每次修改软件,都可以立即测试,检查软件能否正常工作,更放心。

# 软件版本

  • 每次给软件发布一个版本时,版本名称应该采用什么格式?

    • 简单的软件,可以采用当前日期,作为版本名称。例如 20220101
      • 优点:容易看出这个版本是什么时候发布的。
    • 复杂的软件,建议采用语义化版本,让人大致看出这个版本的改动幅度有多大。格式如下:
      主版本号.子版本号[.修订版本号[.编译版本号]]
      
      • 如果新版本,不再兼容某些旧 API ,则将主版本号递增。(递增的同时,将右侧的各个版本号复位为 0 )
      • 如果新版本,新增了功能、特性,但依然兼容所有旧 API ,则主版本号不变,子版本号递增。
      • 如果新版本,没有新增功能、特性,只是修改了一些代码(通常是为了修复一些 bug ),则将修订版本号递增。
      • 如果同一版本的代码,进行了多次编译(比如针对 Linux、Window 平台分别编译一次),则使用不同的编译版本号。
  • 在软件的开发流程中,可以使用几种版本代号:

    α, alpha              # 表示该版本只是一个初步完成品,通常有很多 bug 。因此该版本只用于内部测试,不建议客户使用
    β, beta               # 消除了严重错误,可以提供给部分用户试用,即公开测试
    RC, Release Candidate # 候选发布版,公开测试一段时间,如果没有发现问题,就晋升为 release 版本
    release               # 正式发布的版本
    
  • 企业在售卖软件时,可以提供多种版本,售价不同:

    standard              # 标准版,提供了软件的基本功能,满足普通用户的需求
    professional          # 专业版,在标准版的基础上增加了一些高级功能,满足专业用户的需求
    enterprise            # 企业版,提供该软件的所有功能。可能按该企业的用户数量收费