开发项目管理

此处记录开发思想,项目管理,组织架构,流程标准等等

项目开发的一般流程

提出需求–>需求分析–>概要设计–>详细设计–>编码–>测试(内部自己测试和专门人员测试)–>项目验收(提供相关上线材料)–>上线(投入运营)–>日常维护–>版本更新(发现bug并解决bug)–>下线(弃用)

  • 需求提炼
  • 项目的子系统划分,每个子系统的模块分解
  • 项目的开发经历、经验积累
    • 开发流程
    • 项目调试
  • 第三方框架/开源库的积累
  • 锻炼快读阅读代码的能力
  • 锻炼对封装好的API的快速上手能力
  • 锻炼处理问题的逻辑思维能力

案例

image-20221224150907746

涉及的知识

  1. 网络通信 select,poll,epoll结合的多线程或多进程第三方库:libevent
  2. 报文编解码
  3. 进程间通信 socket,pipe,fifo,mmap 套接字,管道,命名管道,共享内存
  4. 数据库操作相关 oracle 的 occi 库
  5. QT,守护进程创建,信号相关等等
  6. shell编程相关
  7. 加密算法相关
  8. 多线程开发

子系统划分:

  1. 秘钥协商客户端子系统
  2. 秘钥协商服务端子系统
  3. 客户端信息注册报备图形化界面系统

模块划分

  1. 报文编解码模块
  2. 网络通信模块
  3. 共享内存操作模块
  4. 数据库操作模块
  5. 外联接口

web企业开发架构

教程|720x360

架构讲解

编程思想

什么时候需要抽象

抽象可以减少重复代码,但也会带来耦合

**代码耦合(COUPLING)**是指代码之间的依赖关系和相互影响程度。代码耦合越高,不同部分之间的依赖性越强,修改一个部分可能会影响其他部分,导致代码难以维护和扩展。为了降低代码耦合,可以采取一些措施,比如模块化设计、接口隔离、依赖注入等。这样可以使代码更加灵活、可维护和可扩展。

抽象和耦合如影随形

只有下面两种情况才真正有价值

  • 有不同的处理方式,特别是不同的处理方式需要的参数也不相同
  • 该统一操作需要自动执行(调度程序不应该关注具体处理程序的种类和细节)

总的来说:只有当抽象化带来的价值超过耦合时,才适合应用抽象化

一点重复代码带来的痛苦总是比过度耦合要小

相对于过耦合,重复代码的技术债也总是最廉价的

命名建议

计算机科学中只有两个难题

缓存失效和命名

糟糕的命名模式

  • 不要以Base或Abstract命名一个类

    如果你发现自己无法给父类起个好名字,那很可能意味着应该重新命名子类

  • 当很多代码都放在Utils中时,请考虑重构

另有一条建议:除非类型已经包含单位信息,否则请在变量中添加单位

去除3层缩进

Linux代码指南中提到:如果你需要超过3层的缩进,说明你的程序已经开始混乱,应该修复你的程序

下面罗列了一些去除3层缩进的方法

表驱动法

凡是可以通过逻辑语句来选择的事物,都可以通过查表法来解决

对于简单的语句,逻辑语句更加简单直白,但随着选项越来越多,表驱动法就更有优势了

优势:

  • 逻辑数据分离
  • 易维护
image-20240510203824077

使用c++实现的表驱动法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <iostream>
#include <unordered_map>

void case1() {
std::cout << "Case 1" << std::endl;
}

void case2() {
std::cout << "Case 2" << std::endl;
}

void case3() {
std::cout << "Case 3" << std::endl;
}

int main() {
std::unordered_map<int, void(*)()> cases = {
{1, case1},
{2, case2},
{3, case3}
};

int input;
std::cout << "Enter a case number (1-3): ";
std::cin >> input;

if (cases.find(input) != cases.end()) {
cases[input]();
} else {
std::cout << "Invalid case number" << std::endl;
}

return 0;
}

提早返回

防御性编程:子程序应该不因传入错误数据而破坏,哪怕是由其他子程序产生的逻辑错误

此时通过早退出或早返回可以简化复杂的参数验证

使用断言的话,甚至能省去参数验证的if语句

甚至还可以将参数验证提取出来作为一个独立的函数也是不错的选择

面向对象

多态调用其实类似switch分支(如图)

image-20240510204522433

多态能把分支逻辑隐藏起来,所以利用面向对象的多态加上工厂模式,可以将分支逻辑分离并独立出来,让主逻辑更加清晰

优秀的程序设计思想

分离逻辑层和表示层

由于表示层和业务逻辑相互独立,在程序维护阶段所需要做出的变化将变得相当容易。分离了表示层和业务逻辑的程序也很容易被测试

代码清晰化表达

在字典和创建新类传达数据的情况下,选择创建新类

因为字典很不明确。你根本就无法得知字典里关键字的真实意思。字典不能确定称买东西的人是顾客还是客户?即使是拼写错误也无法检查出来。更重要的是,即使你看完代码还是不能回答这样的问题:“访问这些数据的接口是什么?通过这些数据我能得到什么?”所以说,字典是很不明确的。而使用类的话——即使你不得不重写所有成员变量及其访问函数——一切就变得明确起来。这时只需要查看源代码就知道这些数据是什么了。

公共接口与发布接口

作为一个从头开发大型系统且人数众多的团队的管理者,首要任务是将系统分成几个主要的子系统,然后定义这些子系统的接口。这些接口在某种程度上就是对外发布的接口。

接口并不是不能被改变。相反,最初要经常改变接口,随着时间的推移,需要改动的地方越来越少。原因很简单,接口随着时间的推移而成熟。除此以外,随着时间的推移,越来越多的代码绑定在这些接口上,并且开发者对接口越来越熟悉。当到达某个点的时候,你就会说,“好吧,这就是接口。我们往下走吧。”

强代码所有权和弱代码所有权

马丁:我把代码所有权分为三类。在极限编程中用到的代码所有权有时称为集体代码所有权,有时也可以说“无代码所有权”。在这种所有权中,团队中任何人都不具有对代码的所有权。也就是说,任何成员可以在任意时间内改动系统中的任何代码。这就是极限编程采取的方式。

和这种所有权相对立的是强代码所有权。在强代码所有权中,严格区分我的代码和你的代码,我不能改动你的代码。当我想改动我的某个方法的名字时,如果这个方法被你的代码所调用,那么我就必须先通知你,让你先把所有调用代码改过来,然后我才可以改动这个方法的名字。另一种办法就是,我得将这个方法“过期化”(deprecation),然后完成后面一系列的步骤。实际上,在这种情况下,因为我绝对不能碰你的代码,因而你所用过的我的任何接口都是发布的。

弱代码所有权则介于两者之间。弱代码所有权中,还是会区分代码的所有者,不同的是它允许开发者改动其他人的代码。开发者对自己的代码质量仍然负有责任。如果我想要改变我的代码中某个方法的名称,改就好了。不过,假如我想将某个类的功能转赋到另一个类上,而这些代码的所有权属于你,那么至少 在这么做之前我应该让你知道。这一点是和集体代码所有权不同的。

不论是在弱代码所有权下,还是集体代码所有权下,都可以进行代码的重构。但是在强代码所有权下进行重构是个问题,因为你想做的许多重构根本就无法进行,比如,你不能去改动别人的调用代码。这就是强代码所有权下不适合做重构而弱代码所有权下可以做重构的原因。

计划型设计和进化型设计

设计强调的是构造——将程序划分为若干分割清晰的部分

马丁:我将设计区分为计划型设计和进化型设计。当开发者着手实施一个软件时,他首先需要做设计,然后再按照这个设计进行编码实现软件,这就是我所说的计划型设计。计划型设计可能借助UML;或者把整个系统分为若干子系统,定义这些子系统间的接口。在计划型设计中,在设计和代码实现这二者之间存在明确的切换。而这二者又往往由不同的人来完成。架构师构思设计,开发者编码实现。做好的设计并不是说一点都不能改变,但基本上是固定的。你可能会说,设计做得越好,在编码的时候,就会越少对设计做出改动。

而在进化型设计中,开发者在编程实践的过程中逐渐完善设计。刚开始的时候并没有设计,而是先实现一些小的功能。随着实现的功能越来越多,设计才逐渐成型。

我在《设计是否已死》一文中想要强调的是,很多人在尝试进化型设计时,往往是在一种无约束无原则的环境里,最终的设计必然很蹩脚。这是人们之所以倾向于计划型设计的原因之一。

但是,在我看来,极限编程实践中,通过持续不断的集成、测试和重构,进化型设计能够做到比计划型设计更有效。计划型设计的弱点就是,要想做出一个好的设计非常难。

有趣的是,很多进化型设计的倡导者,比如肯特·贝克和沃德·坎宁安,都是非常出色的设计师。但正是他们,最后认识到自己所做的预先设计往往不够好。他们容易把一些事情过于工程化,在不需要灵活性的地方设计灵活性,而在需要灵活性的地方又未予以考虑。因此,他们最终采用了进化型设计,并通过运用一套规则,保证了设计效果。其结果是,不但最终的设计更加出色,并且速度也加快了。拿我自己来说,80%左右的时间里,进化型设计会得到不错的结果。 而不客气地说一句,我认为我的设计水平要比一般人高。因此,我认为进化型设计应该可以适用于更广泛的人群。

重构改变了预先设计的地位,其正是进化型设计的关键

重构

下面相关内容摘自<<对话软件大师_-_Martin_Fowler>>

重构就是对代码本身做出修改,以改善它的内部结构,但又不改变它的外部表现

优势:

重构改善了设计。而一个良好的设计,其商业目的何在?我认为,它使你能在未来更容易地对软件作出改动

重构实际上是在说,“来吧,让我们把系统结构重新调整一下,好让将来的任何改动都更容易些。”其潜台词是,如果你不会改动你的系统,那么也就没有必要做重构,因为不会有任何回报。但如果你将要对你的系统作出改动;不管是消除漏洞也好,还是添加新功能也好;那么,一个好的或更好的系统结构,会使你在做修改时有所受益。

Q: 重构是如何帮助你提高编程速度的呢?

马丁:因为一个设计良好的程序,修改起来会更容易。程序的设计越好,修改起来就越容易,从而提高了效率。

马丁:对程序进行改动是主要的动因。我们不得不经常对软件作出一些改动;只要这个软件还在使用。重构是用来改善设计的。 我们需要一个好的设计以使任何改动都更容易些。重构跟性能优化有些类似,都是在行为不变性(behavior-preserving)前提下的改进。不过,性能优化的步骤有异于重构,整个过程也有所不同,因为性能优化的驱动要素是性能分析(profiling)

重构使得事情一瞬间清晰起来,因而你能够一眼就看出漏洞所在。

人们不注重设计的一个原因是由于工作的流动性。程序员因为自己的糟糕设计而自食其果的情况很少发生,因此他们没有足够的动因去注重设计。此外,即便他们注重,但设计毕竟是一项费力不讨好的工作,好的设计需要时间,而开发中的时间压力往往很大。

但保持代码的良好构造以及编写测试反而能加快工作速度,人们把改进设计所花的时间看作是“失去”的时间,但却没有看到将来对代码的改动会容易得多,往往只需要几分钟的时间,否则可能要花上两三个小时

人们往往还低估了在调试上所花的时间,低估了他们用来追踪一个“潜伏”很久的漏洞所花的时间。我在写代码的时候可以立即察觉产生的漏洞, 这使得我能在它潜伏下来之前就解决它。没有几件事比调试更花时间和更令人沮丧的了

重构是如何改进设计的呢?

比如,提取方法(Extract Method)通过把一个很长的、令人费解的方法拆分成一些小方法来改进设计。改进后的方法读起来就像是一份文档;一张调用那些小方法的列表。

每种重构方法都会对针对某些特定的设计元素做出改进。应用的时候要具体情况具体分析。很多重构方法都能找到相对立的另一个重构方法。比如,如果一个方法,除了方法本身的代码所表达的意义之外,没有任何附加的含义,那么你可能会内联它。内联方法(Inline Method)与提取方法就是对立的。很多时候,到底应用哪种方法取决于具体情况。

  • 提取出一个函数
  • 内联就是直接用原本的代码,不用函数封装

简单系统的四个条件

  1. 通过所有的测试
  2. 揭示所有的意图
  3. 没有重复代码
  4. 使用最少的类和方法

重构与重写

如果你有一堆乱七八糟的代码且又没有测试,那么你最好是扔掉它们从头开始,否则你就得重新做所有的测试。反之,如果你有一堆乱七八糟的代码同时还有很多测试的话,情况就不一样了。假如代码中满是漏洞,那么在行为不变性下,不管怎么变换,那些漏洞 都会被保留下来。这时,是否重构就是一个值得争论的问题。我想,这个问题的答案也会随着你对重构熟悉程度的深入而改变。随着对重构越来越有信心,你可能会对以前想要重写的一些东西改用重构,因为你有更强的重构能力了

在某些时刻,如果代码完全没有结构,那么重写是比重构更有效的一种方法。

因此在决定重写代码之前,也许值得花些时间在重构上,来看看能做多少改进

<<重构>>一书里有句话: 如果打算重构,那么最基本的前提是有完善的测试 (测试对于重构来说,是非常重要的支撑)

灵活性与复杂性

比尔:在《重构》一书中你写道:“在学会重构之前,我总是力图找到灵活的方案。因为设计变动的代价非常高,因而我希望我的设计能够胜任我所能预知的变化。但问题是,灵活性是有代价的。”那么,灵活性的代价是什么?有什么解决之道么?

马丁:灵活性的代价就是复杂性。每次当你往代码中加入一些额外的东西以提高灵活性时,通常也使你的代码变得更加复杂。假如你的预期是对的,未来确实需要这种灵活性,那么你的超前工作得到了回报。但如果你的预期是错的,那么你所引入的复杂性将使软件变得更加难以改动,因而该灵活性是毫无意义的。

而这种预期是很容易出错的。比如,当需求发生变化时,你所以为的对灵活性的需求可能随之变化甚至有可能不复存在。再比如,你添加了一些额外的代码,指望它们能提高灵活性,但这些代码本身就有问题。结果是既增加了复杂性,又未能实现灵活性,真是“赔了夫人又折兵”。

而解决之道就是极限编程。事实上,你根本就不需要考虑灵活性。极限编程理论认为,既然我们的预期在大多数情况下都是错的,那么就把灵活性放在一边好了。那种冒进式的提升设计的办法是拔苗助长;平稳地改进设计才是可取之道。事实上,设计的改进是一个自我强化(self-reinforcing)的过程。如果你能够使设计尽可能简洁,避免那些无谓的灵活性,那么你所要面对的复杂性就会小很多,也就越容易对代码做出改动。代码会更容易被读懂和被改动,你也能够更快地对软件做出调整。

不要试图一开始就定义一个可重用的框架然后在此基础上开发应用,相反,应该是在开发过程的过程中,逐渐形成和完善框架。

重构实例参考

在写《企业应用架构模式》(Patterns of Enterprise Applications Architecture Design)这本书的时候,曾碰到过这样一个增量式设计的例子。当时,我需要构建一个关联表映射(associative table mappings)的模式实例。假如在内存中你有一个多对多的关系,并且需要把它持久化到一个关系型数据库中。这时,你需要一个额外的连接表。因此一共有三张表。有很多种方法可以将数据从数据库中读入到内存里:有一种比较简单的办法,但是需要执行多个 select 语句;也有一种比较快的方法,可以只用一个 select 语句,但是当需要把返回的数据提取出来并拆分到不同的对象中时,就会很别扭。

我用增量式设计构建了这个模式实例。起先,我针对三张具体的表和两个具体的类编写了一段写死的代码,根本就没有考虑通用化的问题。我只是让这个非常特定、具体的例子能够运行起来。在通过测试之后,我着手重构这个例子以使它的应用范围更广一些。花了一点时间之后,我就得到了一个通用的机制。 我所要做的一切,就是写一个很小的映射类,就能够让这个例子对任意的表和类都适用。

我发现,从具体的实例入手然后再把它重构成一个抽象的例子是非常容易的;反之,如果从抽象的例子入手而把它应用到具体的案例中,则要困难得多。我还发现,前者会给人一种更平静和从容的感觉,而实际的进展又非常之快。我能够始终清楚目前我在哪里,又在做什么。我对进度的把握也更加得心应手, 再也不会有那种“何时才能让这段代码运转起来”的无力感。

上面有个很重要的概念

单一思考

“单一”是指在任一时刻,都只使用一种逻辑,一种思考模式。当我构建前面提到的那个例子时,我只考虑如何使那个很具体的例子运行起来;而当我进入到重构阶段时,我只考虑如何抽象化那个具体的例子。我不会同时去考虑两件事情;一次只做一件事情。我发现这样做的体验非常宁静而愉快

测试

马丁:没有测试支撑的重构,就如同不系安全带走钢丝。如果你很擅长走钢丝,而且钢丝又不是悬得很高的话,那不妨试试。但如果你从没走过钢丝,而钢丝又是悬在尼亚加拉瀑布上空,那你最好还是有个保靠的安全带

类似 JUnit 的测试的最大好处就是让你能通过运行它们看看是否有什么东西被破坏了。如果你不打算碰你的代码,那当然平安无事;但只要你加新的功能或是修补漏洞,那么你就有可能破坏某些东西。你的测试越完善,你对能做的改动就越有信心。最终,你能实现比较高的可靠度

以测试为基础的可靠性是极限编程中不大被人们注意的要点之一

测试能提高鲁棒性、质量和可靠性

鲁棒性是指系统在面对不同环境、输入或干扰时能够保持稳定和可靠的性能。在计算机科学中,鲁棒性通常用来描述算法或系统对于错误、异常情况或变化的处理能力。一个鲁棒性强的系统能够有效地处理各种情况而不会崩溃或产生不可预测的行为。

花在写测试上的时间,可以因为不用修补漏洞而补回来,因为花在跟踪调试上的时间大大减少了。花在测试上的成本很快就能收回。随之而来的还有其它好处

通过添加一些测试,你能很快获得回报,因为你开始发现问题了。如果你把测试集中在你需要做改动的代码部分,那么当你犯错误的时候,测试会告知你这些错误。显然,全面综合的测试会使你受益最大;但是,就算只写几个测试,你也会从中受益。

针对包含漏洞的代码段编写单元测试,是一种很好的调试技术。其带来的好处,不仅仅是让你对代码的理解更深刻,还让你建立起测试库,从而意味着将来不会有问题发生。

测试优先设计会使你体会到一种难以言传的从容不迫之感。你的进展其实非常快,但却不会让你感到很紧张,因为你为自己设定的都是一些微目标(micro-goals)。在每个时间点上,你知道自己是在实现某一个微目标。一旦测试通过,该目标就实现了。这是一种很平和的过程。它缩小了你的关注范围。你不需要去考虑每一件事情,只需要专注于某一小块功能。你实现了这个功能,然后重构它,使得其中每个环节的设计都近于完美。然后再进行下一步。我以前用的是你所描述的方法,我不得不常常问自己,“这个东西的接口是什么?”而现在,我转向了增量式设计 (incremental design),并且觉得这种方式要大大优于之前的方法。

测试与接口

这是一个潜移默化的过程——你的的确确是在构思接口,而且是以一种渐进的方式。你不会对自己说,“啊,我需要构造这个类,让我们来把这个类的所有接口都搞清楚,然后再实现之吧!”相反,你会说,“嗯,这个类需要实现这么一小块功能。来为此写个测试吧!”在编写测试的时候,接口就随之浮现出来。

单元测试

Q: 单元是什么意思

马丁:哦,这很难。最粗略地讲,它是一个类。但随着你与之打交道越多,你就会意识到你是在测试功能 的一小块区域,而这一小块区域有可能是一个类的一部分,也有可能是几个类合起来的作用。我这里只是粗略地一说,不过,如果你想开始试试的话,可以把单元测试看成是为每个类编写个测试案例。

性能与调优

可维护性与效率

比尔:记得当时你曾对我说过,应该以程序员能读懂的字符格式来序列化对象,而不是以二进制代码格式。当我提到字符格式要比二进制码格式慢时,你说,从效率的角度来看,二进制代码格式使得软件更加难以维护。那么,能否请你谈谈关于序列化方式的具体案例?一般地说,你如何在可维护性与效率之间寻找平衡点?

马丁:效率永远是第一位的,前提是你能正确理解它。很多时候问题在于,人们以为做某些事情是为了效率着想,但他们却从不使用性能分析器(profiler)。如果你出于效率的考虑而做某件事,但却不使用性能分析器,那么你所宣称的根本就不着调。

序列化所牵扯的问题要多一些。使用二进制代码做序列化的问题之一就是你无法去查看结果。当你需要存储序列化的对象时,这个问题就更加突出。Java的一个典型问题就是如果你改变了一个类,那么就无法读取以前所序列化的对象。类似的,如果一个客户端和一台服务器正通过序列化的对象进行通讯,假如一端的数据结构进行了更新而另一端没有,那么整个通讯就彻底失效了。

有一个小窍门可以让你绕开这个问题。不要序列化对象本身,而是把数据从对象中提取出来,放到一个字典里,然后再序列化那个字典。这么做会使你能够应对一些变化。

比尔:但是,字典是“不明确的”。我们之前刚刚说起过这点。

马丁:的确,字典不是“明确的”。不过,如果你往类里添加一个字段,并把这个多出来的值放到字典里的话,不会有什么问题。因此,这是一个比较强壮的机制。XML一般也比较强壮,因为你可以对你所不了解的数据视而不见。二进制序列化的主要问题就是它的脆弱性。在我的书《企业应用架构模式》中,更多地提到了序列化的方式。例如,在数据库中传输和存储数据时,就需要考虑介于字符和二进制之间的序列化格式。

编写可性能调优的软件实际上就是编写结构合理的软件

优化

马丁:还有一件事需要牢记:性能优化与版本和具体的实现是密切相关的。当你拿到Java 的一个新版本时,一定要把以前所做的优化都撤消,然后重走一遍优化过程,以确保那些优化手段仍然奏效。通常你会发现,你为上一个版本的虚拟机 (virtual machine)或优化型编译器(optimizing compiler)所做的性能优化往往使当前的版本变慢,也即,之前的优化手段如今往往起到适得其反的作用。

比尔:要记住以前为了提升性能都做了哪些改动可不是件容易的事情。

马丁:你必须这么做——先撤销,再重新应用。我知道这不容易。这就要求你对优化过程中所做的每个改动都要有详细的记录。要知道,旧的优化所造成的一些微不足道的性能损失,在新的版本下有时候可能会变得非常显著。

Craig Larman(拉曼 C [3]) 曾经讲过一个故事,我到现在都还很喜欢这个故事。Craig 有一次在JavaOne的大会上做性能优化的讲座。他提到了两个广为人知的技术:对象池(object pooling)和线程池(thread pooling),对象池就是重用已有的对象,而不是每次都创建新的对象,线程池的原理基本类似。讲座结束后,有两个人来到Craig跟前。这两个人都是设计高性能虚拟机的。其中一个虚拟机是Hotspot,另一个好像是 JRocket。一个人告诉 Craig,线程池的效果不错,但对象池则使得虚拟机的运行变慢;而另一个人告诉Craig 的恰恰相反。

对象池是一种设计模式,用于管理和重复利用对象实例,以提高性能和减少资源消耗。在对象池中,对象实例被预先创建并存储在一个池中,当需要时可以从池中获取对象实例,使用完毕后再放回池中,而不是频繁地创建和销毁对象实例。这样可以减少内存分配和垃圾回收的开销,提高系统的性能和效率。对象池常用于需要频繁创建和销毁对象实例的场景,例如线程池、数据库连接池等。

所以,你有可能在一种虚拟机上优化了性能,但拿到另一种虚拟机上,却减慢了其运行速度。对此,你要特别小心。对象池就是一个很好的例子。 很多人热衷于对象池,但起码有一半的情况下,人们并不去测量对象池的效果到底是好是坏。在Java的早期日子里,对象池非常重要,因为垃圾回收(garbage collection)功能还不是很完善。但在垃圾回收技术更新换代之后,对象池的效果就大大降低了,因为那些生存周期很短的对象可以被低成本地回收。只有那些生存周期很长的对象,才适合使用对象池技术,因为对它们进行垃圾回收的成本很高。

从这里可以看出,规则也是在不断变化的。这就是为什么要对性能调优很仔细的原因所在。不要妄想根据源代码就能预测机器会做什么。当你与虚拟机或优化型编译器打交道时,性能调优是唯一的手段,因为编译器和虚拟机所做的事情,远远超出你的想象。记住,不要预测,要实测。

敏捷宣言

敏捷宣言中的四条核心价值观

  1. 个体和互动重于流程和工具

    这条原则大意是说,与其借重过程和工具来加强对软件开发的管理,不如更多地关注于团队及其成员,关注于每个个体以及他们之间在个人层面上的交互。它包括了提升技能;它还包括要竭尽全力使程序员们身心愉悦,从而得以留住人才;它还意味着更认真地对待个性冲突,注重人与人的相处,而不是试图找出某个完美的软件开发过程,然后要求大家都来遵守这个过程。我对这条原则的理解是,应该是团队选择适合其的软件开发过程,而不是让团队来适应指定的开发过程

  2. 可工作的软件重于详尽的文档

  3. 客户合作重于合同谈判

  4. 响应变化重于遵循计划 这四条原则指导着敏捷团队在软件开发过程中注重人与沟通、可交付的软件产品、与客户合作和适应变化。

马丁:尽管在那次聚会上,我们中的许多人都津津乐道于自己所采用的开发过程,并且我们当中的几个人还是软件工具销售商,但我们一致同意,对于一个项目的成功来说,软件开发过程和工具只是次要的因素,最主要的因素还是团队,是团队中的成员,是他们人性化的合作与努力

代码设计原则

[[设计模式#面向对象设计原则|完整的6个设计原则]]

开闭原则

OCP

开闭原则规定,软件实体(如类或模块)应该:

  • 开放 扩展:可以在不修改源代码的情况下添加新功能。
  • 封闭 修改:实体不应该被修改以影响其现有功能。

换言之,开闭原则规定,软件实体应该被设计为允许添加新功能,而不需要修改其底层结构。

OCP 的优点:

  • 提高灵活性:新功能可以被添加,而不需要修改现有代码。
  • 减少耦合:实体被解耦合,使得维护和演进变得更容易。
  • 提高可扩展性:实体可以被扩展以满足新要求,而不影响其现有功能。

依赖倒置原则

DIP

依赖倒置原则规定:

  • 高级模块不应该依赖低级模块,而应该依赖抽象。
  • 抽象不应该依赖细节,细节应该依赖抽象。

换言之,DIP 规定,高级模块不应该依赖低级模块,而应该依赖抽象。这个抽象可以是一个接口或抽象类。

DIP 的优点:

  • 减少耦合:高级模块被解耦合,使得维护和演进变得更容易。
  • 提高灵活性:系统变得更加模块化和易于扩展。
  • 提高可扩展性:系统可以更容易地扩展,以满足新要求。

接口隔离原则

ISP

接口隔离原则规定:

  • 客户端不应该被迫依赖它不使用的接口。
  • 相反,接口应该被设计以满足特定客户端的需求。

换言之,ISP 规定,接口应该被设计以满足特定客户端的需求,而不是强迫客户端依赖一个大型的通用接口。

ISP 的优点:

  • 减少耦合:客户端被解耦合,使得维护和演进变得更容易。
  • 提高灵活性:接口被设计以满足特定客户端的需求,使得添加新功能变得更容易。
  • 提高可扩展性:系统变得更加模块化和易于扩展。

软件开发方式

测试驱动开发 (TDD)

测试驱动开发是一种软件开发方法,它强调在编写代码之前先编写测试用例。这个过程可以分为三个步骤:

  1. 编写测试用例:首先,开发者编写测试用例,以确保代码满足要求。
  2. 运行测试用例:然后,开发者运行测试用例,以确保代码通过测试。
  3. 编写代码:最后,开发者编写代码,以使其通过测试用例。

TDD 的优点包括:

  • 提高代码质量:TDD 确保代码满足要求,减少 bug 的可能性。
  • 提高开发速度:TDD 帮助开发者快速编写代码,减少 debug 时间。
  • 提高代码可维护性:TDD 使得代码更易于维护和更新。

行为驱动开发 (BDD)

行为驱动开发是一种软件开发方法,它强调在编写代码之前先定义软件的行为。这个过程可以分为三个步骤:

  1. 定义行为:首先,开发者定义软件的行为,以确保软件满足要求。
  2. 编写测试用例:然后,开发者编写测试用例,以确保软件行为正确。
  3. 编写代码:最后,开发者编写代码,以使其满足软件行为。

BDD 的优点包括:

  • 提高软件质量:BDD 确保软件满足要求,减少 bug 的可能性。
  • 提高开发速度:BDD 帮助开发者快速编写代码,减少 debug 时间。
  • 提高软件可维护性:BDD 使得软件更易于维护和更新。

制造业相关流程标准

FA功能开发流程

​ 1. 客户需求调研分析:理解客户的业务需求,评估现有的工厂流程,并确定自动化系统需要实现的功能和目标。

​ 2. 设备与客户环境的对接:按照 SECS/GEM 等 SEMI(Semiconductor Equipment and Materials International)标准,开发接口和通讯协议,使工厂设备能够与客户的自动化环境进行无缝集成。

​ 3. 软件需求分析和方案设计:根据客户需求,制定详细的功能需求文档和技术方案,设计软件架构和模块。

​ 4. 代码编写和单元测试:根据设计方案编写软件代码,并进行单元测试,以确保各个模块的功能正确性和稳定性。

​ 5. 现场测试:在客户现场进行系统集成测试,验证软件在实际操作环境中的性能,并解决出现的问题。

​ 6. 文档编写:编写相关的技术文档和用户手册,详细记录系统功能、操作指南、维护步骤等。

​ 7. 其他任务:根据公司需求,完成其他相关的开发或支持工作。

FA 功能开发的目标是通过软件和自动化技术来优化生产过程,提高生产效率,降低成本,并确保产品的一致性和质量。这在现代制造业,尤其是半导体、汽车制造、电子装配等行业中显得尤为重要。

SEMI标准

  1. SECS/GEM
    • **SECS (SEMI Equipment Communications Standard)**:定义了半导体制造设备与工厂计算机系统之间的通信协议。SECS 标准包括 SECS-I 和 SECS-II,其中 SECS-I 是基于 RS-232 的通信协议,SECS-II 则定义了数据内容和格式。
    • **GEM (Generic Equipment Model)**:基于 SECS 标准的扩展,定义了通用设备模型,提供了标准化的设备控制和状态报告接口。GEM 标准使工厂管理系统能够更容易地控制和监控不同制造设备。
  2. **EHS (Equipment Health Standards)**:包括 E10、E58 等标准,专注于设备的健康管理,提供了一套用于监控和报告设备状态的方法,以提高设备的可用性和可靠性。
  3. PV(Photovoltaic)标准:涉及太阳能电池和光伏组件制造,涵盖从材料、制造过程到最终产品的标准化。
  4. Interface A:提供了一种标准化的方法,用于在不同供应商的设备和工厂管理系统之间交换过程数据,以实现更好的数据分析和制造优化。
  5. 其他领域:SEMI标准还包括材料处理、设备设计、环境、健康和安全(EHS)、技术文档、测试方法等各个方面。

这些标准的主要目标是促进半导体制造过程中的兼容性和互操作性,减少因不同设备和系统之间不兼容带来的问题,从而提高整体生产效率和产品质量。SEMI 标准通过详细的技术规范和操作指南,为半导体和相关行业提供了一个共同的基础,确保了全球制造和供应链的协同运作。

表现模式/设计模式/架构模式

  • 表现模式(Presentation Pattern)

    通过分离关注点来改进代码的组织方式。表现模式侧重于解决代码组织,往往使用了多种设计模式,因此其也称作复合设计模式。

    MVC/MVVM属于这种

  • 设计模式(Design Pattern)

    为了解决一类问题而总结出来的抽象方法。

  • 架构模式(Architecture Pattern)

    描述软件系统里的基本的结构组织或纲要。架构模式提供一些呈现定义好的子系统,指定它们的责任,并给出把它们组织在一起的法则和指南。

    三层架构属于这种

架构模式和表现模式是可以共存的

架构模式

三层架构

概述

三层架构以高内聚、低耦合的思想,把程序的各个功能模块划分为三层架构,分别是

  • 表示层(UI)
  • 业务逻辑层(BLL)
  • 数据访问层(DAL)

三层架构的分层模式是典型的上下关系,并且是上层依赖于下层.在三层架构的隔层模块之间,通过对象模型的实体类(Model)作为数据传递的载体,不同的对象模型的实体类一般对应于数据库的不同表,实体类的属性与数据库表的字段名一致

image-20240612174421140

三层架构的优势

  • 高内聚、低耦合,可以降低层与层之间的依赖。
  • 各层互相独立,完成自己该完成的任务,项目可以多人同时开发,开发人员可以只关注整个结构中的其中某一层。
  • 容易移植、维护,如B/S转C/S、SQL Server 转 Oracle、添加、修改、删除等。
  • 有利于标准化和各层逻辑的复用
  • 安全性高。用户端只能通过业务逻辑层来调用数据访问层,减少了入口点。

三层架构模式是一种基于业务逻辑来分的软件架构模式,是一种整体的软件架构

MVC/MVVM等表现模式是基于页面划分的一种复合设计模式,是一种页面框架设计

MVC/MVVM作用于三层架构中的UI层,也就是说将三层架构中的UI层再度进行了分化

数据操作层

有一个额外的层单独拿出来可以称为数据操作层

数据操作层包含公共数据访问代码,是用于操作和交互数据库中数据的逻辑代码。

将数据操作层划分在业务逻辑层还是数据层是当下项目设计的两种常见模式

  • 将数据操作的逻辑代码置于业务逻辑层时,数据访问成为一种业务逻辑。表示层对于数据的访问与业务逻辑层对于数据的操作调用的是相同的方法。

    iShot_2024-06-13_08.38.47
  • 将所有的数据读取操作存放在数据层时,只需要在业务层再定义一个方法供表示层调用

    iShot_2024-06-13_08.38.31

在架构设计中,根据高内聚低耦合的原则,上层的模块不必关心下层模块,尽可能独立.因此更标准的做法是将数据操作层置于数据层中(数据操作层置于数据层后,我们一般把这个称为数据访问层)

以C#为例,除了WPF,表示层还可以采用其他表示层技术

  • WinForm 桌面应用程序
  • ASP.NET 动态web页
  • Silverlight 网络交互程序
  • Avalonia 跨平台应用程序

对于同一逻辑功能层的项目,无论使用何种技术来进行表示层的开发,其逻辑层和数据层都是相同的

表现模式

MVC

概述

MVC和MVVM是我们在进行应用程序开发中最常用的两种表现模式

MVC模式是GUI界面开发的指导模式。它基于表现层功能划分的思想把程序分为三大部分:Model-View-Controller呈三角形结构。Model是数据模型,View是用户界面,Controller是控制器。MVC的设计目的是实现功能结构的规划

通过Controller使得Model的数据和View的呈现同步

iShot_2024-06-13_09.07.49
  • 模型(Model)数据模型,处理程序逻辑;获取和存放数据。

    Model的代码包括业务逻辑及具体的实现以及状态管理等

  • 视图(View):显示数据;提供用户交互界面。

    程序中界面相关的部分,是用户看到并与之交互的界面,通常实现数据相对于用户的输入与输出功能

  • 控制器(Controller):处理用户交互;从View读取数据(用户输入);向Model发送数据。

    根据用户的交互操作,控制用户界面数据的显示与更新,.model的对象状态,起到控制整个业务流程的作用,实现View层与Model层的协同工作他有关

可见在MVC模式中,controler是这个模式的核心,view和model的数据需要经由controller进行传递.MVC的通信核心就在于控制器.以控制器为核心划分了视图和数据,但并非完全分离的,view和model之间是有联系的,mvc之间的通信是单向进行的,view和controller也是单向引用.但在实际当中,view和controller其实也是有数据交互的.虽然这并不违背 MVC 模式的基本原则。只要这种交互是在控制器的协调下进行的,并且遵循了各组件之间的职责划分,就仍然可以保持代码的结构清晰和可维护性

为什么说view和controller之间并不是完全分离的呢?

拿c#的winform举例:当我们给一个视图上的控件去命名的时候,我们就可以通用逻辑页面去通过它的名字调用到他.但是一旦你的这个view改变了它的名字,就会导致model也需要修改.所以在MVC模式当中,view和model之间多多少少都会存在一些依赖和捆绑的关系

用户请求被路由到控制器,后者负责使用模型来执行用户操作或检索查询结果。控制器选择要显示给用户的视图,并为其提供所需的任何模型数据。

优点

有利于软件工程化管理,由于不同的层各司其职,每一层不同的应用具有某些特性,有利于通过工程化、工程化管理程序代码,可以使用控制器来连接不同的模型和视图去完成客户的需求。

MVC与三层架构的关系

iShot_2024-06-13_09.18.27

MVC是表现模式(Presentation Pattern),三层架构是典型的架构模式(Architecture Pattern),三层架构的分层模式是典型的上下关系,上层依赖于下层。但MVC作为表现模式是不存在上下关系的,而是相互协作关系。MVC和三层架构基本没有可比性,是应用于不同领域的技术。

MVVM

概述

MVVM(Model-View-ViewModel)是一种基于前端开发的表现模式,其核心是提供对View和ViewModel的双向数据绑定,使得model和view的数据状态可以自动变更.在WPF中应用到MVVM是非常常见的,MVVM全称为Model、View、ViewModel

iShot_2024-06-13_09.27.01
  • View:代表窗体、控件等可视化资源。
  • ViewModel:代表View的业务处理类,将获取到的数据处理好与View进行关联绑定。
  • Model:通常代表数据模型,用于定义和管理应用程序的数据。它将支持ViewModel中用到的一些字段。还有一种用法就是在Model里完成业务逻辑的编写;ViewModel只需要处理视图和模型之间的关联和交互

其中ViewModel是MVVM模式的核心,他是连接View和Model的桥梁,他有两个方向:

  • 将Model转化为View,即将后端传递的数据转化成所看到的页面: 数据绑定
  • 将View转化为Model,即将所看到的页面转化成后端的数据: 事件监听

这两个方向共同称之为数据的双向绑定.

MVVM在概念上是真正将页面与数据逻辑分离的模式,View和Model彻底分离.

ViewModel通常会需要实现一个observer的观察者

在实际开发中,如何在 Model 和 ViewModel 中分配业务逻辑的实现,可以根据项目的具体需求和特点来决定。毕竞MVVM只是一个规范我们尽量遵守即可。

优点

  • 低耦合:视图View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Mode可以不变,当Model变化的时候View也可以不变。

  • 可重用性:可以把一些视图逻辑放在ViewModel里面,让很多view重用这段视图逻辑。

  • 独立开发:开发人员可以专注业务逻辑和数据的开发,设计人员可以专注页面设计。

  • 可测试:界面向来比较难预测时,测试可针对ViewModel来写。

    View最有可能与平台紧密耦合,即使对其进行编码也难以进行单元测试

MVC与MVVM的区别

  • MVC模型关注的是页面功能的划分,将UI层面上的代码和数据逻辑相关的代码分开,但并不是完全分离,且在于强调控制器的作用

  • MVVM在概念上是真正将页面与数据逻辑分离的模式,View和Model彻底分离,核心在于提供对View 和 ViewModel 的双向数据绑定,使得Model和View数据状态的改变可以自动变更

    view和model不知道彼此的存在,通过viewModel来进行绑定,即ViewModel可以直接获取到Model的信息,直接访问模型上的属性和方法;但要注意的是,Model和ViewModel不能直接获取ViewModel的信息

驱动方式

事件驱动

事件驱动通过“事件一订阅一事件处理”的关系组织应用程序。事件驱动下,用户进行每一个操作会激发程序发生的一个事件,事件发生后,用于响应事件的事件处理器就会执行。

事件驱动对应的表示模式正是MVC

数据驱动

数据驱动对应的表示模式是MVVM

函数式编程

参考视频

大部分编程范式的区别都在于如何管理状态

函数式编程的核心是状态不存在

输入会转换为输出

控制反转

控制反转IOC

控制反转是一种设计原则,其核心思想是将对象之间的依赖关系控制权从对象本身转移到外部环境。通过 IoC,一个对象不再直接创建依赖对象或管理依赖的生命周期,而是将这种责任交给容器或框架处理。这种控制权的转移让代码更加灵活、易于测试且更具可维护性。

依赖注入(DI)是实现 IoC 的一种方式

依赖注入是一种具体实现 IoC 的设计模式。通过 DI,一个对象的依赖项(或称依赖对象)由外部传入,而不是由对象内部自行创建。DI 是实现 IoC 最常用的手段,主要有以下几种方式:

  • 构造函数注入:通过构造函数将依赖对象传入。
  • 属性注入:通过属性设置依赖对象。
  • 方法注入:通过方法参数传递依赖对象。

面向切片编程

AOP(Aspect-Oriented Programming,面向切面编程)是一种编程范式,专注于将横切关注点(如日志、事务管理、异常处理)从业务逻辑中分离。AOP 框架可以通过拦截方法调用的方式,在特定代码执行前后自动执行一些通用逻辑,常见的 AOP 框架有 PostSharpCastle DynamicProxy 等。AOP 框架的核心机制是动态代理,通过“切面”实现方法执行前后或异常发生时的特定操作

通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术

是对OOP的一种补充,在不修改原始类的情况下,给程序动态添加统一功能的一种技术。

面向切片编程AOP

AOP 框架在减少重复代码、实现一致的全局处理方面非常有帮助,通过动态插入功能的方式,便于管理跨业务的代码逻辑

AOP 的关键概念

  • 切面(Aspect):关注特定任务(如日志、异常处理)的模块。
  • 连接点(Join Point):程序中的一个执行点(如方法调用或异常抛出)。
  • 切入点(Pointcut):定义在哪些连接点插入切面的规则。
  • 通知(Advice):在连接点实际执行的操作,如方法前后的日志记录、异常拦截等。

使用场景

  • 日志记录:在方法执行前后记录日志,无需在每个方法内手动添加。
  • 事务管理:在数据库操作前后自动开启和提交事务。
  • 全局异常处理:集中捕获和处理异常,并记录在日志中。
  • 性能监控:自动测量方法的执行时间以便优化。
  • 权限控制:强制执行安全策略,确保只有授权的用户能够执行特定操作。

实现AOP有两种方式:

  1. 静态代理实现。所谓静态代理,就是我们自己来写代理对象。
  2. 动态代理实现。所谓动态代理,就是在程序运行时,去生成一个代理对象。

实现静态代理需要使用到两种设计模式:[[设计模式#装饰器模式]]和[[设计模式#代理模式]]。

Spring的aop是靠[[设计模式#代理模式]]实现的

动态代理

两种实现方式

  • 通过代码织入的方式。例如PostSharp第三方插件。我们知道.NET程序最终会编译成IL中间语言,在编译程序的时候,PostSharp会动态的去修改IL,在IL里面添加代码,这就是代码织入的方式。
  • 通过反射的方式实现。通过反射实现的方法非常多,也有很多实现了AOP的框架,例如Unity、MVC过滤器、Autofac等。

软件工程

image-20250522111344191 image-20250522111401718

软件的生存周期

image-20250520110453135

通俗理解:

  • 概要设计: 大框架方面的设计,包括将一个系统划分为子系统或模块等分配过程都叫概要设计,还要设计划开的部分之间通信的过程.
  • 详细设计: 子系统或子模块之间的具体设计,具体的数据结构算法等东西
  • 编码: 实现,开发阶段
  • 测试: 验证功能是否正常
  • 维护: 也叫做运维,将系统交付给用户后,在用户环境下运行,包括更新换代,打补丁等很多工作

概要设计文档的内容不包括(C
A. 体系结构设计
B. 数据库设计 ✅: 需要全局的数据库设计
C. 模块内算法设计
D. 逻辑数据结构设计 ✅: 需要全局的逻辑数据结构设计

结构化开发方法中,(D)主要包含对数据结构和算法的设计。
A. 体系结构设计 ❌: 概要设计
B. 数据设计 ❌: E-R图
C. 接口设计 ❌: 人机交互
D. 过程设计 ✅

在采用结构化开发方法进行软件开发时,设计阶段接口设计主要依据需
求分析阶段的(A)。接口设计的任务主要是(C)。
A. 数据流图 B. ER图 C. 状态-迁移图 D. 加工规格说明
A. 定义软件的主要结构元素及其之间的关系 ❌: 体系结构设计
B. 确定软件涉及的文件系统的结构及数据库的表结构 ❌: 数据设计
C. 描述软件与外部环境之间的交互关系,软件内模块之间的调用关系 ✅: 这就是接口设计
D. 确定软件各个模块内部的算法和数据结构 ❌: 过程设计阶段

软件过程

软件过程改进的几种模型(软考中主要考察的点就是这几种模型)

CMM模型

软件过程能力成熟度模型CMM

(很多公司会评定CMM等级)

  • 初始级: 杂乱无章,甚至混乱,几乎没有明确定义的步骤,项目的成功完全依赖个人的努力和英雄式核心人物的作用
  • 可重复级: 建立了基本的项目管理过程和实践来跟踪项目费用,进度和功能特性,有必要的过程准则来重复以前在同类项目中成功
  • 已定义级: 管理和工程两方面的软件过程已经文档化,标准化,并综合整个软件开发组织的标准过程
  • 已管理级: 指定了软件过程和产品质量的详细度量标准
  • 优化级: 加强了定量分析,通过来自过程质量反馈和来自新观念,新技术的反馈使过程能不断持续地改进

初始级是1级,优化级是5级

除了CMM模型,后续还有一个进阶版,叫CMMI,阶段式模型

以下关于CMM的叙述中,不正确的是(C
A. CMM是指软件过程能力成熟度模型
B. CMM根据软件过程的不同成熟度划分了5个等级,其中,1级被认为
成熱度最高,5级被认为成熟度最低
C. CMMI的任务是将已有的几个CMM模型结合在一起,使之构成“集成
模型”
D. 采用更成熟的CMM模型,一般来说可以提高最终产品的质量

CMMI模型

阶段式模型 5级模型

image-20250520111737895

连续式模型 6级模型

只需要完成等级和关键字的匹配,即可满足软考要求

等级CL 说明 关键字
CL0(未完成的) 过程域未执行或未得到CL1中定义的所有目标。 未执行或未得到
CL1(已执行的) 其共性目标是过程将可标识的输入工作产品转换成可标识
的输出工作产品,以实现支持过程域的特定目标。
可标识的输入工作产品转换成可标识的输出工作产品
CL2(已管理的) 其共性目标是集中于已管理的过程的制度化。根据组织级
政策规定过程的运作将使用哪个过程,项目遵循已文档化
的计划和过程描述,所有正在工作的人都有权使用足够的
资源,所有工作任务和工作产品都被监控、控制、和评审。
已管理的过程的制度化
CL3(已定义级的) 其共性目标集中于已定义的过程的制度化。过程是按照组
织的裁剪指南从组织的标准过程中裁剪得到的,还必须收
集过程资产和过程的度量,并用于将来对过程的改进。
已定义的过程的制度化
CL4(定量管理的) 其共性目标集中于可定量管理的过程的制度化。使用测量
和质量保证来控制和改进过程域,建立和使用关于质量和
过程执行的质量目标作为管理准则。
可定量管理的过程的制度化
CL5(优化的) 使用量化(统计学)手段改变和优化过程域,以满足客户
的改变和持续改进计划中的过程域的功效。
量化(统计学)手段改变和优化过程域

CL5的量化(统计学)是用来优化自身的,别理解错成CL4了

例题

能力成熟度模型集成(CMMI)是若干过程模型的综合和改进。连续式
模型和阶段式模型是CMMI提供的两种表示方法,而连续式模型包括6
个过程域能力等级,其中(D)使用量化(统计学)手段改变和优化过
程域,以应对客户要求的改变和持续改进计划中的过程域的功效。
A. CL2(已管理的) B. CL3(已定义级的)
C. CL4(定量管理的) D. CL5(优化的)

软件开发方法

结构化方法

非常严谨,是一种面向过程的开发,适用于需求明确的项目(一般认为做二次开发或已有行业经验被称为需求明确)

特点:

  • 用户至上
  • 严格区分工作阶段,每阶段有任务和结果
  • 强调系统开发过程的整体性和全局性
  • 系统开发过程工程化,文档资料标准化
  • 自顶向下,逐步分解(求精)

需求本身是有渐进明晰性的,当需求改进时,很多时候要推导重来

原型法

使用于需求不明确的项目

使用了一种演化迭代的思想,主要是帮助用户明确需求的

比如提供界面原型给用户来提需求

面向对象的方法

适用于复杂大项目(p.s.结构化方法不适用于复杂项目,因为复杂项目变数太多了)

特点:

  • 更好的复用性
  • 关键在于建立一个全面,合理,统一的模型
  • 分析,设计,实现三个阶段,界限不明确

整个过程是迭代无间隙的,相互之间可以有一些交叠的过程

面向服务的方法

软考中目前仅在高级中出现过

服务可以理解为更高级别的抽象

抽象级别: 操作,服务,业务流程

例题

若用户需求不清晰且经常发生变化,但系统规模不太大且不太复杂,则
最适宜采用(C)开发方法,对于数据处理领域的问题,若系统规模不
太大且不太复杂,需求变化也不大,则最适宜采用(A)开发方法。
A. 结构化 B. Jackson C. 原型化 D. 面向对象
A. 结构化 B. Jackson C. 原型化 D. 面向对象

p.s. Jackson这种方法是面向数据结构的

软件开发模型

软考必考知识点,主要是给定一种情形,让考生判断适合什么开发模型

瀑布模型与V模型

这个考点出现特别频繁

瀑布模型

适合于需求明确的情况

文档驱动

image-20250520140042348

V模型

属于瀑布模型的一个变种

image-20250520140213255

强调的是测试贯穿始终

image-20250521173547022
  • 单元测试: 模块测试,模块功能,性能,接口等

    一般只有单元测试是自己测试自己

  • 集成测试: 模块间的接口

    打桩,桩模块,只用来测试的模块

  • 系统测试: 真实环境下,验证完整的软件配置项能否和系统正确连接

  • 确认测试: 验证软件与需求的一致性.内部确认测试,Alpha测试,Beta测试,验收测试

  • 回归测试: 测试软件变更之后,变更部分的正确性对变更需求的符合性

例题

某开发小组欲为一公司开发一个产品控制软件,监控产品的生产和销售过程,从购买各种材料开始,到产品的加工和销售进行全程跟踪。购买材料的流程、产品的加工过程以及销售过程可能会发生变化。该软件的开发最不适宜采用(A)模型,主要是因为这种模型(C)。
A. 瀑布 B. 原型 C. 增量 D. 喷泉
A. 不能解決风险 B. 不能快速提交软件
C. 难以适应变化的需求 D. 不能理解用户的需求

演化模型

演化模型:演化模型是迭代的过程模型,使得软件开发人员能够逐步开发出更完整的软件版本。演化模型特别适用于对软件需求缺乏准确认识的情况。

其中

原型模型

原型模型强调的是明确需求,而不是长期投入使用的迭代过程

image-20250520141404815

第一个维度区分

  • 抛弃型原型

    前一个迭代会被抛弃掉

  • 演化型原型

    最初的迭代会被保留

第二个维度区分

  • 探索型原型

    主要用于需求分析阶段,探索多种可能的设计方案的可行性

  • 实验型原型

    针对特技术方案或架构进行验证,评估实现方案的可行性和性能表现

  • 演化型原型

    贯穿 开发全过程,从核心功能出发逐步迭代,最终演化为完整的软件系统

例题

以下关于系统原型的叙述中,不正确的是(C
A. 可以帮助导出系统需求并验证需求的有效性
B. 可以用来探索特殊的软件解決方案
C. 可以用来指导代码优化
D. 可以用来支持用户界面设计

A是探索型原型,B是实验性原型,D是最初的原型,一般会保留在系统中,所以是演化型原型

螺旋模型

螺旋模型是由瀑布模型和演化模型结合,加入风险分析,特别适用于庞大,复杂并且具有高风险的系统

他是唯一适用于高风险的系统开发模型

image-20250520141739227
例题

以下关于螺旋模型的叙述中,不正确的是(D
A. 它是风险驱动的,要求开发人员必须具有丰富的风险评估知识和经验
B. 它可以降低过多测试或测试不足带来的风险
C. 它包含维护周期,因此维护和开发之间没有本质区别✅
D. 它不适用于大型软件开发

某企业拟开发一个企业信息管理系统,系统功能与多个部门的业务相关。现希望该系统能够尽快投入使用,系统功能可以在使用过程中不断改善。则最适宜采用的软件过程模型为(``)
A. 瀑布模型 ❌: 题干提到了不断改善,即需求不明确
B. 原型模型 ❌: 原型模型强调的是需求明确,而不是长期迭代使用
C. 演化(迭代)模型 ✅: 最符合的是演化模型
D. 螺旋模型 ❌: 强调风险管理,但是题干没有提到

原型模型和螺旋模型都是演化模型的一种,但是当这些选项都出现的时候,应该选择最合适的选项

增量模型

image-20250520143023300

第1个增量往往是核心产品,将需求分段为一系列增量产品,每一增量可以分别开发

用户用的最多的功能其实只有所有功能中的20%,每一轮都能将核心增量进行测试,以此保证核心功能的最稳定和最优

这种模型对用户体验会更好,每一个增量都会提供一个用户可使用的版本

但缺点是判定哪些功能属于核心增量,对于开发者来说比较困难

例题

以下关于增量开发模型的叙述中,不正确的是(D)。
A. 不必等到整个系统开发完成就可以使用
B. 可以使用较早的增量构件作为原型,从而获得稍后的增量构件需求
C. 优先级最高的服务先交付,这样最重要的服务接受最多的测试
D. 有利于进行好的模块划分 ❌:增量模型中最难做的就是模块划分

喷泉模型

以用户需求为动力,以对象作为驱动的模型,适合于面向对象的开发方法

特点: 迭代无间隙

image-20250520143351425

例题

喷泉模型是一种适合于面向(A)开发方法的软件过程模型。该过程模型的特点不包括(D)。
A. 对象 B. 数据 C. 数据流 D. 事件
A. 以用户需求为动力 B. 支持软件重用
C. 具有选代性 D. 开发活动之间存在明显的界限

统一过程UP

UP和RUP都表示统一过程

image-20250520143625103

每一轮迭代为: 初始/初启 -> 细化/精化 -> 构建 -> 交付

构建中的测试叫做α测试,交付阶段的测试叫β测试

面向架构,很大的优势在于复用构件/组件

敏捷方法

考得比较多

总体目标是通过”尽可能早的,持续的对有价值的软件的交付”,使客户满意,适用于:”小步快跑”的思想,适合小项目小团队

image-20250520144429101

结对编程: 让至少两个人作为一对来进行编程,一个人进行写的同时,另一个人来看,另一个维度是防止人员流失

具体的敏捷开发涉及到一些模型

敏捷方法 特点
极限编程XP 4大价值观、5个原则、12个最佳实践
水晶法(Crystal) 认为每一个不同的项目都需要一套不同的策略、约定和方法论,认为人对软件质量有重要的影响(以人为本),因此随着项目质量和开发人员素质的提高,项目和过程的质量也随之提高。通过更好地交流和经常性交付,软件生产力得到提高。
开放式源码 程序开发人员在地域上分布很广
并列争球法(SCRUM) 把每30天一次的迭代称为一个“冲刺”,并按需求的优先级来实现产品。多个自组织和自治的小组并行地递增实现产品。协调是通过简短的日常情况会议来进行,就像橄榄球中的“并列争球”。
功用驱动开发方法FDD 首席程序员和“类”程序员
自适应软件开发ASD 核心是三个非线性的、重叠的开发阶段:猜测、合作与学习。ASD有6个基本的原则:有一个使命作为指导;特征被视为客户价值的关键点;过程中的等待是很重要的,因此“量做”与“做”同样关键;变化不被视为改正,而是被视为对软件开发实际情况的调整;确定的交付时间迫使开发人员认真考虑每一个生产版本的关键需求;风险也包含其中。

极限编程XP

常考,经常是选择题的形式

4大价值观

  • 沟通
  • 简单
  • 反馈
  • 勇气: 勇于面对变化

5大原则

  • 快速反馈
  • 简单性假设
  • 逐步修改
  • 提倡更改
  • 优质工作

12大最大实践

  • 计划游戏: 快速制定计划,随着细节的不断变化而完善
  • 小型发布: 系统的设计要能够尽可能早的交付
  • 隐喻: 找到合适的比喻传达信息
  • 简单设计: 只处理当前需求,是设计保持简单
  • 测试先行: 先写测试代码,然后再编写程序
  • 重构: 重新审视需求和设计,重新明确的描述他们以符合新的现有的需求
  • 结对编程
  • 集体代码所有制
  • 持续集成: 可以按日甚至按小时为客户提供可运行的版本
  • 每周工作40小时
  • 现场顾客: 系统最终用户代表应该全程配合XP团队
  • 编码标准

例题

在敏捷过程的开发方法中,(C)使用了迭代的方法,其中,把每段时间(30天)一次的选代称为一个“冲刺”,并按需求的优先级别来实现产品,多个自组织和自治的小组并行地递增实现产品。
A. 极限编程XP
B. 水晶法
C. 并列争球法
D. 自适应软件开发

以下关于极限编程 (XP)的最佳实践的叙述中,不正确的是(B
A. 只处理当前的需求,使设计保持简单
B. 编写完程序之后编写测试代码
C. 可以按日甚至按小时为客户提供可运行的版本
D. 系统最终用户代表应该全程配合XP团队

需求分析

  • 问题识别

  • 分析与综合

  • 编制需求分析文档

    文档产物一般是: 需求规格说明书SRS

  • 需求分析与评审

    让需求的提出方评审,签字确认12*9

结构化分析的结果: 一套分层的数据流图,一本数据词典(对数据流图来加以说明),一组小说明(也称加工逻辑说明),补充材料

软件开发过程中,需求分析阶段的输出不包括(D)。
A. 数据流图
B. 实体联系图 ✅: E-R图是数据库设计阶段的概念设计阶段的产物
C. 数据字典
D. 软件体系结构图 ❌: 在需求分析阶段是得不到的,是架构设计即概要分析的产物

需求的分类

两种维度的分类:

image-20250520160638305

上图一些名词解释:

  • 非功能需求或者叫性能需求: 一般包含性能,响应,并发,存储容量,速度等需求
  • 设计约束: 一般是对操作系统,数据库的要求,还有对法律法规的要求

在考试中,主要是考察系统需求的分类

某企业财务系统的需求中,属于功能需求的是(A)。
A. 每个月特定的时间发放员工工资
B. 系统的响应时间不超过3秒
C. 系统的计算精度符合财务规则的要求
D. 系统可以允许100个用户同时查询自己的工资

需求分析的工具

数据流图(DFD)

这一道题有15分

主要考察的点是: 1.补充实体名 2.补充存储名 3.补充加工名 4.数据流

image-20250520161016033

例子:

image-20250520161649241

经常扣掉一些字,让用户填空

数据流图自顶向下,逐步求精,其中父子层图的关系如下图:

顶层数据流图 = 上下文数据流图

image-20250520161934622

数据流图建模应遵循(B)的原则。
A. 自顶向下、从具体到抽象
B. 自顶向下、从抽象到具体
C. 自底向上、从具体到抽象
D. 自底向上、从抽象到具体

在结构化分析中,用数据流图描述(B)。当采用数据流图对一个图书馆管理系统进行分析时,(A)是一个外部实体。
A. 数据对象之间的关系,用于对数据建模 ❌: 不对,对象的关系是静态的,而数据流图是描述一种动态关系的
B. 数据在系统中如何被传送或变换,以及如何对数据流进行变换的功能或子功能,用于对功能建模 ✅
C. 系统对外部事件如何响应,如何动作,用于对行为建模 ❌: 并没有对外部事件进行考虑,只是对系统内部进行考虑
D. 数据流图中的各个组成部分 ❌: 比较接近数据词典的定义
A. 读者 B. 图书 C. 借书证 D. 借阅

数据字典是结构化分析的一个重要输出。数据字典的条目不包括(A)。
A. 外部实体 ❌
B. 数据流
C. 数据项
D. 基本加工

解题技巧

  • 补充实体

  • 补充存储

  • 补充数据流

    数据平衡原则

    1. 顶层图与0层图对比,是否有顶层图有,但0层图无得数据流,或反之
    2. 检查图中每个加工,是否存在只有入没有出,或只有出没有入,或根据输入的数据无法产生对应的输出的情况

    按题目说明与图进行匹配: 说明中的每一句话,都能与图中有对应关系,当把说明中的实体与数据流标识出来之后,容易缩小对应范围,找出纰漏

  • 补充加工名

    加工是用于处理数据流的,所以要补充加工名,可以把该加工涉及到的数据流,在说明中标识出来,再在数据流名称所在的句子中,找”动词+名词”的结构,分析是否可作为加工

    “动词+名词”如: 生成报告,发出通知,批改作业,记录分数,当然这只是普遍情况,也有例外,如物流跟踪,用户管理

例题

阅读以下说明和数据流图,回答问题1至问题4,将解答填入答题纸的对应栏内。
【说明
现准备为某银行开发一个信用卡管理系统CCMS,该系统的基本功能为:

  1. 信用卡申请非信用卡客户填写信用卡申请表,说明所要申请的信用卡类型及申请者的基本信息,提交CCMS。如果信用卡申请被银行接受,CCMS将记录该客户的基本信息,并发送确认函给该客户,告知客户信用卡的有效期及信贷限额;否则该客户将会收到一封拒绝函。非信用卡客户收到确认函后成为信用卡客户。
  2. 信用卡激活信用卡客户CCMS提交激活请求,用信用卡号和密码激活该信用卡。激活操作结束后,CCMS将激活通知发送给客户,告知客户其信用卡是否被成功激活。
  3. 信用卡客户信息管理。信用卡客户的个人信息可以在CCMS中进行在线管理。每位信用卡客户可以在线查询和修改个人信息。
  4. 交易信息查询。信用卡客户使用信用卡进行的每一笔交易都会记录在CCMS中。信用卡客户可以通过CCMS查询并核实其交易信息(包括信用卡交易记录及交易额)。图11-3和图11-4分别给出了该系统的顶层数据流图和0层数据流图的初稿。
image-20250523100255616

【问题1】(3分)
根据【说明】,将图11-3中的E1~E3填充完整。
【问题2】(3分)
图11-3中缺少三条数据流,根据【说明】,分别指出这三条数据流的起点和
终点。(注:数据流的起点和终点均采用图中的符号和描述)
【问题3】(5分)
图11-4中有两条数据流是错误的,请指出这两条数据流的名称,并改正。
(注:数据流的起点和终点均采用图中的符号和描述)
【问题4】(4分)
根据【说明】,将图11-4中P1~P4的处理名称填充完整。

一般实体较少,从实体先分析

从题干中提取出三个实体: 非信用卡客户,银行,信用卡客户(用于填充到E1~E3)

四个加工流程分别是信用卡申请,信用卡激活,信用卡客户信息管理,交易信息查询(用于填充到P1~P4)

根据数据平衡原则可分析出缺失的三条数据流:

  • 交易信息在顶层数据流图中是缺失的
  • 信用卡申请表在顶层数据流图中是缺失的
  • 激活请求在顶层数据流图中缺失

题干中可知,信用卡申请表应该是从非信用卡客户指向CCMS的,数据流图中的箭头是反了

题干中可知,激活请求应该是信用卡客户发送给P0的,而不是图示中的P4指向P3

image-20250523101303733

  • P1: 交易信息查询
  • P2: 客户信息管理
  • P3: 信用卡激活
  • P4: 信用卡申请

还有另外两道例题,暂略

数据平衡原则

查找缺失数据流的过程中,一个可以快速查找的依据,但是只能查找一些缺失数据而非全部,最终的解决方案还是要落到题目分析的过程中

包含两个维度

  • 父图与子图之间的平衡

    父图与子图之间平衡是指任何一张DFD子图边界上的输入/输出数据流必须与其父图对应加工的输入/输出数据了保持一致。如果父图中某个加工的一条数据流对应于子图中的几条数据流,而子图中组成这些数据流的数据项全体正好等于父图中的这条数据流,那么它们仍然是平衡的。

  • 子图内的平衡

    数据流图常见的3种错误:

    1. 加工只有输入没有输出,称之为“黑洞
    2. 加工只有输出没有输入,称之为“奇迹”;
    3. 加工中输入不足以产生输出,称之为“灰洞
image-20250523090253558
image-20250523090610128

分析上图的是否存在数据缺失

以前端应用为例,分析顶层数据流图

前端应用相接的数据流有:

  • 非法用户信息
  • 用户信息
  • 操作请求
  • 处理后的操作结果 ❌: 对比可知缺失了这项
  • 权限不足信息
  • 格式错误信息

而在0层数据流图中

前端应用相接的数据流有:

  • 非法用户信息
  • 用户信息
  • 操作请求
  • 权限不足信息
  • 格式错误信息

前端应用在0层数据流图中缺失了处理后的操作结果

后端数据库:

顶层数据流图:

  • 验证后的操作请求
  • 连接请求
  • 操作结果 ❌: 对比可知缺失了这项

0层数据流图

  • 验证后的连接请求(就是操作请求吧)
  • 连接请求

数据字典

image-20250520162046893

数据字典有4类条目: 数据流,数据项,数据存储和基本加工.(源点和终点不在系统之内,不在字典中说明)

1
2
3
机票=姓名+日期+航班号+起点+终点+费用
航班号=“Y7100" •• “Y8100"
终点=[长沙|上海|北京|西安]

结构化语言

常用的加工逻辑描述方法有结构化语言,判定表和判定树3种

结构化语言: 是一种介于自然语言和形式化语言之间的半形式化语言,是自然语言的一个受限子集

  1. 外层: 用来描述控制结构,采用顺序,选择和重复3种基本结构
    1. 顺序结构,一组祈使语句,选择语句,重复语句的顺序排列
    2. 选择结构,一般用IF-THEN-ELSE-ENDIF,CASE-OF-ENDCASE等关键词
    3. 重复结构,一般用DO-WHILE-ENDDO,REPEAT-UNTIL等关键词
  2. 内层: 一般采用祈使语句的自然语言短语,使用数据字典中的名词和游戏的自定义词,其动词含义要具体,尽量不用形容词和副词来修饰,还可使用一些简单的算法运算和逻辑运算符号
image-20250520164751543

判定表

某些情况下,数据流图中某个加工的一组动作依赖于多个逻辑条件的取值,此时用判定表能够清晰的表示复杂的条件组合与应做的动作之间的关系.

判定表由4个部分组成,分割成如下的4个区域:

条件定义 条件取值的组合
动作定义 在各种取值的组合下应执行的动作
image-20250520164821109

判定树

判定树是判定表的变形,一般情况下笔判定表更直观,且易于理解和适用

image-20250520164940224

系统设计/软件设计

大致可以分为两个阶段

  • 概要设计
  • 详细设计

特点:

  • 抽象化
  • 自顶向下,逐步求精
  • 信息隐蔽
  • 模块独立(高内聚,低耦合)

包含下面的设计环节

  • 体系结构设计/架构设计: 定义软件系统各主要部件之间的关系
  • 数据设计: 基于E-R图确定软件涉及的文件系统的结构及数据库的表结构
  • 接口设计(人机界面设计): 软件内部,软件和操作系统间以及软件和人之间如何通信
  • 过程设计: 系统结构部件转换成软件的过程描述.确定软件各个组成部分内的算法及内部数据结构,并选定某种过程的表达形式来描述各种算法

使用到的工具

下面的工具在考试中实际上并没有用到

  • IPO图
  • PDL图
  • PAD图
  • 程序流程图
  • N/S盒图

模块设计

考得很多

基本设计要点

  • 保持模块的大小适中

  • 尽可能减少调用的深度

  • 多扇入,少扇出

    某个模块被调用叫扇入

    模块调用别的模块叫扇出

  • 单入口,单出口

  • 模块的作用域应该在模块之内

    作用域: 指模块内定义的变量、函数或逻辑的可见范围,即哪些代码区域可以访问该模块的内容

    控制域: 指模块在运行时能直接或间接调用/控制的其他模块集合,即该模块的执行影响范围

  • 功能应该是可预测的

良好的启发式设计原则上不包括(B
A. 提高模块独立性 ✅: 内聚性
B. 模块规模越小越好 ❌: 应该是大小适中
C. 模块作用域在其控制域之内 ✅:内聚性
D. 降低模块接口复杂性 ✅: 耦合性

模块设计原则

内聚耦合

内聚性
image-20250521101443055
内聚类型 描述
功能内聚 完成一个单一功能,各个部分协同工作,缺一不可
顺序内聚 处理元素相关,而且必须顺序执行
通信内聚 所有处理元素集中在一个数据结构的区域上
过程内聚 处理元素相关,而且必须按特定的次序执行(注意可以不顺序)
瞬时内聚(时间内聚) 所包含的任务必须在同一时间间隔内执行
逻辑内聚 完成逻辑上相关的一组任务
偶然内聚(巧合内聚) 完成一组没有关系或松散关系的任务

考点注意: 顺序内聚和过程内聚的区别注意区分,最高是功能内聚,最低是偶然内聚

某模块中各个处理元素都密切相关于同一功能且必须顺序执行,前一处理元素的输出就是下一处理元素的输入,则该模块的内聚类型为(C)内聚
A. 过程
B. 时间
C. 顺序
D. 逻辑

耦合性
image-20250521102016440
耦合类型 描述
非直接耦合 两个模块之间没有直接关系它们之间的联系完全是通过主模块的控制和调用来实现的
数据耦合 一组模块借助参数表传递简单数据
标记耦合 一组模块通过参数表传递记录信息(数据结构)
控制耦合 模块之间传递的信息中包含用于控制模块内部逻辑的信息
外部耦合 一组模块都访问同一全局简单变量,而且不是通过参数表传递该全局变量的信息
公共耦合 多个模块都访问同一个公共数据环境
内容耦合 一个模块直接访问另一个模块的内部数据;一个模块不通过正常入口转到另一个模块的内部;两个模块有一部分程序代码重叠;一个模块有多个入口

模块A将学生信息,即学生姓名、学号、手机号等放到一个结构体中,传递给模块B。模块A和B之间的耦合类型为(B)耦合。
A. 数据
B. 标记
C. 控制
D. 内容

人机界面设计

黄金三原则

  • 置于用户控制之下
    • 以不强迫用户进入不必要的或不希望的动作的方式来定义交互方式
    • 提供灵活的交互
    • 允许用户交互可以被中断或撤销
    • 当技能级别增加时可以使交互流水化并允许定制交互
    • 使用户隔离内部技术细节
    • 设计应允许用户和出现在屏幕上的对象直接交互
  • 减少用户的记忆负担
    • 减少对短期记忆的要求
    • 建立有意义的缺省
    • 定义直觉性的捷径
    • 界面的视觉布局应该基于真实世界的隐喻
    • 以不断进展的方式提示信息
  • 保持界面的一致性
    • 允许用户将当前任务放入有意义的语境
    • 在应用系列内保持一致性
    • 如过去的交互模型已建立起了用户期望,除非有迫不得已的理由,不要改变它

Theo Mandel在其关于界面设计所提出的三条“黄金准则”中,不包括(B)
A. 用户操纵控制
B. 界面美观整洁
C. 减轻用户的记忆负担
D. 保持界面一致

架构设计

架构设计的一个核心问题是能否达到架构级的软件复用

架构风格

架构风格是对架构设计的归类

  • 架构风格反映了领域中众多系统所共有的结构和语义特性,并知道如何将各个构件有效地组织成一个完整的系统
  • 架构风格定义了用于描述系统的术语表和一组指导构件系统的规则

有下面的一些架构风格

数据流风格
批处理序列

构件为一系列固定顺序的计算单元,构件之间只通过数据传递交互。每个处理步骤是一个独立的程序,每一步必须在其前一步结束后才能开始,
数据必须是完整的,以整体的方式传递

管道-过滤器

每个构件都有一组输入和输出,构件读输入的数据流,经过内部处理,然后产生输出数据流。这个过程通常是通过对输入数据流的变换或计算来完成的,包括通过计算和增加信息以丰富数据、通过浓缩和删除以精简数据、通过改变记录方式以转化数据和递增地转化数据等。这里的构件称为过滤器,连接件就是数据流传输的管道,将一个过滤器的输出传到另一个过滤器的输入。

早期编译器就是采用的这种架构。要一步一步处理的,均可考虑采用此架构风格

调用/返回风格
主程序/子程序

单线程控制,把问题划分为若干个处理步骤,构件即为主程序和子程序,子程序通常可合成为模块。过程调用作为交互机制,即充当连接件的角色。调用关系具有层次性,其语义逻辑表现为主程序的正确性取决于它调用的子程序的正确性

面向对象

构件是对象,对象是抽象数据类型的实例。在抽象数据类型中,数据的表示和它们的相应操作被封装起来,对象的行为体现在其接受和请求的动作。连接件即是对象间交互的方式,对象是通过函数和过程的调用来交互的

p.s.面向对象是显示调用,而事件是隐式调用的

层次结构

构件组织成一个层次结构,连接件通过决定层间如何交互的协议来定义。每层为上一层提供服务,使用下一层的服务,只能见到与自己邻接的层。通过层次结构,可以将大的问题分解为若干个渐进的小问题逐步解决,可以隐藏问题的复杂度。修改某一层,最多影响其相邻的两层(通常只能影响上层)

优点

  • 这种风格支持基于可增加抽象层的设计,允许将一个复杂问题分解成一个增量步骤序列的实现

  • 不同层次处于不同的抽象级别

    越接近底层,抽象级别越高

    越接近顶层,抽象级别越低

  • 由于每一层最多只影响两层,同时只要给相邻层提供相同的接口,允许每层用不同的方法

    实现,同样为软件复用提供了强大的支持

缺点

  • 并不是每个系统都可以很容易地划分为分层的模式
  • 很难找到一个合适的,正确的层次抽象方法
image-20250521110712768

层次风格的拓展性非常强

image-20250521112744757

以下关于C/S(客户机/服务器)体系结构的优点的叙述中,不正确的是(D)
A. 允许合理地划分三层的功能,使之在逻辑上保持相对独立性✅
B. 允许各层灵活地选用平台和软件✅
C. 各层可以选择不同的开发语言进行并行开发✅
D. 系统安装、修改和维护均只在服务器端进行❌: C/S客户端也要更新

层次结构中的MVC架构风格

MVC架构是层次结构中一种经典的架构

  • Model(模型)是应用程序中用于 处理应用程序数据逻辑的部分.通常模型对象负责在数据库中存取数据
  • View(视图)是应用程序中处理数据显示的部分.通常视图是依据模型数据创建的
  • Controller(控制器)是应用程序中 处理用户交互的部分,通常控制器负责从视图读取数据,控制用户输入,并向模型发送数据
MVC 组件 WPF 实现 职责说明 与传统 J2EE 的对比
View XAML 文件(如 MainWindow.xaml)和部分代码隐藏(Code-Behind) 负责界面渲染和用户交互,通过数据绑定显示数据。但 WPF 的 View 通常不直接操作数据逻辑。 类似 JSP,但通过数据绑定实现动态更新,而非直接嵌入业务逻辑。
Controller 路由逻辑或事件处理类(如自定义的 Controller 类) 处理用户输入事件(如按钮点击),协调 Model 和 View 的交互。但在 WPF 中,Controller 的职责常被 ViewModel 替代。 类似 Servlet,但 WPF 的 Controller 更轻量级,且不强制要求独立层(可能与 View 代码耦合)。
Model 数据实体类(如 UserOrder)和业务逻辑层(如服务类) 管理数据和业务规则,与数据库或 API 交互。与 J2EE 的 Entity Bean/Session Bean 功能一致。 直接对应 J2EE 的 Model 层,但 WPF 中 Model 通常不直接暴露给 View,而是通过 ViewModel 封装后传递。
独立构件风格
进程通信

构件是独立的过程,连接件是消息传递。构件通常是命名过程,消息传递的方式可以是点对点、异步或同步方式,以及远程过程(方法)调用等。

事件驱动系统(隐式调用)

构件不直接调用一个过程,而是触发或广播一个或多个事件。构件中的过程在一个或多个事件中注册,当某个事件被触发时,系统自动调用在这个事件中注册的所有过程。一个事件的触发就导致了另一个模块中的过程调用。这种风格中的构件是匿名的过程,它们之间交互的连接件往往是以过程之间的隐式调用来实现的。主要优点是为软件复用提供了强大的支持,为构件的维护和演化带来了方便;其缺点是构件放弃了对系统计算的控制。

虚拟机风格
解释器

解释器通常包括一个完成解释工作的解释引擎,一个包含将被解释的代码的存储区,一个记录解释引擎当前工作状态的数据结构,以及一个记录源代码被解释执行的进度的数据结构.具有解释器风格的软件中含有一个虚拟机,可以仿真硬件的执行过程和一些关键应用,其确定是执行效率比较低

基于规则的系统

基于规则的系统包括规则集,规则解释器,规则/数据选择器和工作内存,一般用在人工智能领域和DSS中

仓库风格

是一种以数据为中心的规则

数据库系统

构件主要有两大类,一类是中央共享数据源,保存当前系统的数据状态;另一类是多个独立处理单元,处理单元对数据元素进行操作。

黑板系统

包括知识源、黑板和控制三部分。知识源包括若干独立计算的不同单元,提供解决问题的知识。知识源响应黑板的变化,也只修改黑板;黑板是一个全局数据库,包含问题域解空间的全部状态,是知识源相互作用的唯一媒介;知识源响应是通过黑板状态的变化来控制的。黑板系统通常应用在对于解决问题没有确定性算法的软件中(信号处理、问题规划和编译器优化等)。

超文本系统

构件以网状链接方式相互连接,用户可以在构件之间进行按照人类的联想思维方式任意跳转到相关构件。超文本是一种非线性的网状信息组织方法,它以结点为基本单位,链作为结点之间的联想式关联。超文本系统通常应用在互联网领域。

现代集成编译环境一般采用这种架构风格

数据仓库位于该体系结构的中心,其他构件访问该数据仓库并对其中的数据进行增、删、改等操作。以下关于该风格的叙述中,不正确的是(D)。(D)不属于仓库风格。
A. 支持可更改性和可维护性 B. 具有可复用的知识源
C. 支持容错性和健壮性 D. 测试简单 ❌: 很难测试数据库
A. 数据库系统 B. 超文本系统 C. 黑板系统 D. 编译器

软件测试

测试的基本概念及分类

  • 尽早,不断的进行测试
  • 程序员避免测试自己设计的程序
  • 既要选择有效,合理的数据,也要选择无效,不合理的数据
  • 修改后应进行回归测试
  • 尚未发现的错误数量与该程序已发现错误数成正比

测试用例的格式一般是输入数据以及他的结果,最后验证是否相符.一个测试用例,最多只覆盖一个测试错误点

软件测试的分类

根据是否人工测试来划分

  • 动态测试(机器运行)

    • 黑盒测试法

      不关心内部细节,只考虑输入输出是否有问题

    • 白盒测试法

      具体输入是如何到达输出的,是否有问题

    • 灰盒测试法

      结合黑盒与白盒

  • 静态测试(纯人工)

    • 桌前检查

      程序员自己检查

    • 代码审查

      其他人检查

    • 代码走查

      预想代码执行过程,这个过程中判断其中是否存在问题

以下关于软件测试的叙述中, 不正确的是(B)。
A. 在设计测试用例时应考虑输入数据和预期输出结果
B. 软件测试的目的是证明软件的正确性 ❌: 无法验证一个软件是百分百正确的
C. 在设计测试用例时,应该包括合理的输入条件
D. 在设计测试用例时,应该包括不合理的输入条件

招聘系统要求求职的人年龄在20岁到60岁之间(含),学历为本科、硕士或者博士,专业为计算机科学与技术、通信工程或者电子工程。其中(C)不是好的测试用例。
A.(20,本科,电子工程)✅: 合法
B.(18,本科,通信工程)❌: 一个错误点
C.(18,大专,电子工程)❌: 两个错误点
D.(25,硕士,生物学)❌: 一个错误点

一个测试用例,最多只覆盖一个测试错误点

黑盒测试法

有下面几种

等价类划分

确定无效与有效等价类,设计用例尽可能多的覆盖有效类,设计用例只覆盖一个无效类

边界值分析

处理边界情况时最容易出错,选取的测试数据应该恰好等于,稍小于或稍大于边界值

白盒测试法

重点内容,考察频率高

有以下几种

  • 基本路径测试
  • 循环覆盖测试
  • 逻辑覆盖测试(重点)
覆盖类型 定义 特点
语句覆盖 被测试程序中的每条语句至少执行一次。 对执行逻辑覆盖很低,一般认为是很弱的逻辑覆盖。
判定覆盖(分支覆盖) 被测程序每个判定表达式至少获得一次“真”值和“假”值(或者程序中每一个判定取“真”分支和取“假”分支至少通过一次)。 判定覆盖比语句覆盖更强一些。判定可以是 1 个条件,也可以是多个条件的组合。
条件覆盖 每一个判定语句中每个逻辑条件的各种可能的值至少满足一次。 条件覆盖和判断覆盖没有包含关系。
判断/条件覆盖 判定中每个条件的所有可能取值(真/假)至少出现一次,并使每个判定本身的判定结果(真/假)也至少出现一次。 同时满足判定覆盖和条件覆盖。
条件组合覆盖 每个判定中的各种可能值的组合都至少出现一次。 同时满足判定覆盖、条件覆盖、判断/条件覆盖。
路径覆盖 覆盖被测试程序中所有可能的路径。
基本路径测试 每一条独立路径都执行过(即程序中可执行语句至少执行一次)。 测试用例个数与环路复杂度一致。判定为关键控制结点,必须出现在基本路径中。
循环覆盖 循环中每个条件都得到验证。 注意数组参数可循环验证。

例题

image-20250521150050000
  • 语句覆盖: 测试用例2/3/4任意一个都满足
  • 判定覆盖: 测试用例1 + 测试用例2/3/4 组合就可以满足
  • 条件覆盖: 测试用例1 + 测试用例4测试用例2 + 测试用例3 这两种组合都可以
  • 判定/条件覆盖: 测试用例1 + 测试用例4
  • 路径覆盖: 测试用例1 + 测试用例2/3/4 组合就可以满足

例题👇🏻

image-20250521152408660 image-20250522091846468

观察顺序,发现首次测试A的同时,还测试了EFG,因此是三明治测试策略

D选项的说法不正确,自顶向下的话,测试A还是要做BCD桩模块,而测试E/F的时候,还是要做B驱动模块

系统测试

经常出现的考点

McCabe复杂度的计算

环路复杂度是环的个数+1(这里的1是顺序流程本身算一个环路)

如果上面的方式不好确定,也可以将图片转换为节点图,即每个交叉点视为一个节点,如下图的转换过程

image-20250522103333011 $$ 计算有向图G的环路复杂度公式为: V(G) = m-n+2 $$ 其中V(G)是有向图G中的环路个数,m是G中的有向弧数,n是G中的节点数

上图中右图中环路复杂度为: 15-12+2 = 5

image-20250522104132185 image-20250522104746610

上题的图:

image-20250522104809784

注意结束点不要遗漏了,虽然代码中看似没有结束点,但其实是应该有的,要有始有终

软件维护

整个软件生存周期中时间最长,耗资最多的阶段

image-20250520113925453

软件维护类型

  • 改正性维护: 针对已经出现错误的修改(错误已经出现/用户已经发现的)
  • 适应性维护: 指使应用软件适应信息技术变化和管理需求变化而进行的修改。企业的外部市场环境和管理需求的不断变化也使得各级管理人员不断提出新信息需求。
  • 预防性维护: 针对可能出现错误的修改(错误还未发生/用户还未发现的)
  • 完善性维护/改善性维护: 扩充功能和改善性能而进行的修改。对已有的软件系统增加一些在系统分析和设计阶段中没有规定的功能与性能特征。

可维护性因素决定

  • 可理解性
  • 可测试性
  • 可修改性

例题

系统交付用户使用了一段时间后发现,系统的某个功能响应非常慢。修改了某模块的一个算法使其运行速度得到了提升,则该行为属于(C)维护。
A. 改正性
B. 适应性
C. 改善性
D. 预防性

软件维护工具不包括(B)工具。
A. 版本控制
B. 配置管理
C. 文档分析
D. 逆向工程

理解:

  • 版本控制(A):明确属于维护工具,用于代码版本跟踪(如Git)
  • 文档分析(C):用于分析需求文档以指导维护活动
  • 逆向工程(D):用于恢复代码设计信息以辅助理解
  • 配置管理(B):是更广泛的管理流程,需通过策略、基线、审计等实现,其工具(如Jira)更多服务于变更控制和过程管理,而非直接参与代码维护

软件维护工具的主要功能是辅助开发人员对代码和文档进行维护活动,其核心工具包括:

  • 版本控制工具(如Git、SVN):用于管理代码版本和协作开发
  • 文档分析工具:分析需求、设计等文档以提供维护信息(如影响范围分析)
  • 逆向工程工具:将低抽象层次的代码转换为更高层次的设计信息(如恢复程序结构)
  • 再工程工具:重构代码或系统以提升性能或可维护性

软件文档

简单了解即可

按照交付目标来划分

  • 开发文档 (交付给项目团队内部成员)
    • 可行性研究和项目任务书
    • 需求规格说明
    • 功能规格说明
    • 设计规格说明(包括程序和数据规格说明)
    • 开发计划
    • 软件集成和测试计划
    • 质量保证计划,标准,进度
    • 安全和测试信息
  • 产品文档 (交付给客户的)
    • 培训手册
    • 参考手册和用户指南
    • 软件支持手册
    • 产品手册和信息广告
  • 管理文档 (交付给管理人员的文档)
    • 开发过程的每个阶段的进度和进度变更的记录
    • 软件变更情况的记录
    • 相对于开发的判定记录
    • 职责定义

以下关于各类文档撰写阶段的叙述中,不正确的是(C)。
A. 软件需求规格说明书在需求分析阶段撰写
B. 概要设计规格说明书在设计阶段撰写
C. 测试设计必须在测试阶段撰写 ❌: 需求分析阶段就可以撰写测试设计
D. 测试分析报告在测试阶段撰写

软件质量保证模型

考试只需要能正确进行分类即可

经常会考的情况是给你一个子特性,让你判断它属于哪个类型的特性

软件质量保证ISO/IEC9126

image-20250522110805322

在ISO/IEC软件质量模型中,易使用性的子特性不包括(D)。
A. 易理解性
B. 易学性
C. 易操作性
D. 易分析性 ❌: 易分析属于维护性的子特性

项目管理

image-20250522112319097

进度管理

只考到工具的使用

Gantt图

了解即可

image-20250522112818171
  • 优点: Gantt图能够清晰地描述每个任务从何时开始,到何时结束,任务的进程情况以及各个任务之间的并行关系
  • 缺点: Gantt图不能清晰地反映出任务之间的依赖关系,难以确定整个项目的关键所在,也不能反映计划中有潜力的部分。

PERT图

必考

PERT = 关键路径分析法

  • 优点: PERT图不仅给出了每个任务的开始时间、结束时间和完成该任务所需的时间,还给出了任务之间的关系,即哪些任务完成之后才能开始另外的一些任务,以及如期完成整个工程的关键路径。图中的松弛时间则反映了某些任务是可以推迟其开始时间或延长其所需完成的时间。
  • 缺点: PERT图不能反映任务之间的并行关系

主要有两种

关键路径法

关键路径法是在制订进度计划时使用的一种进度网络分析技术

参数理解

  • 关键路径: 从开始到结束,需要时间最长的路径
  • 项目工期: 完成项目的最少时间,注意由关键路径即最长路径决定
  • 总时差(松弛时间): 在不延误总工期的前提下,该活动的机动事件.活动的总时差等于该活动最迟完成时间与最早完成时间之差,或该活动最迟开始时间与最早开始时间之差

关键路线法沿着项目进度网络路线进行正向与反向分析,从而计算出
所有计划活动理论上:

  • 最早开始与完成日期
  • 最迟开始与完成日期

注:不考虑任何资源限制

前导图法

单代号网络图,PDM

image-20250522135348885

image-20250522135401008

节点表示活动,箭头表示依赖

  • ES: 最早开始时间
  • EF: 最早完成时间
  • LS: 最迟开始时间
  • LF: 最迟完成时间

最早开始时间由前趋最早完成决定(最大值后推)

最晚结束时间由后续推动(最小值前推)
$$
总时差 = 最晚开始时间 - 最早开始时间
$$
所有总时差为0的路径就是关键路径

例题

image-20250522154141556 image-20250522154137823

箭线图法

双代号网络图,ADM

image-20250522153007876

箭头表示活动,圆圈数字表示状态,箭头上的数字是持续时间

虚线表示的虚活动,持续时间为0,只是为了表示前驱关系

image-20250522153025722

例题

image-20250522155025007 image-20250522155132664

上面的情况如果需要同一个开发人员完成BC和BD,则完成该项目的最少时间为(20)天

需要考虑两种情况

  • 先BC再BD 21天

    image-20250522155748043
  • 先BD再BC 20天

    image-20250522155728511

应该选择先BD再BC,即20天

风险管理

image-20250522160611426

风险分类

  • 项目风险

    项目内部的风险

  • 技术风险

    技术选择过高或过于落后

  • 商业风险

    经济收益相关的这类,包括市场接收方面的,还包括社会人文道德政治等风险

评估风险大小
$$
风险曝光度(Risk\ Exposure) = 风险出现的概率 \times 风险可能造成的损失
$$
假设正在开发的软件项目可能存在一个未被发现的错误,而这个错误出现的概率是0.5%,给公司造成的损失将是1000000元,那么这个错误的风险曝光度就应为1000000×0.5%=5000元

以下叙述中,(B)不是一个风险。
A. 由另一个小组开发的子系统可能推迟交付,导致系统不能按时交付客户 ✅: 项目风险
B. 客户不清楚想要开发什么样的软件,因此开发小组开发原型帮助其确定需求 ❌: 客户不清楚想要开发什么样的软件是已经发生的事情,不是风险
C. 开发团队可能没有正确理解客户的需求 ✅: 项目风险
D. 开发团队核心成员可能在系统开发过程中离职 ✅: 项目风险

以下不属于软件项目风险的是(A)。
A. 团队成员可以进行良好沟通
B. 团队成员离职
C. 团队成员缺乏某方面培训
D. 招不到符合项目技术要求的团队成员

沟通管理

软考只涉及到沟通路径的计算

n个程序员的沟通数计算

  • 无主程序员的沟通数
    $$
    1+2+3+…+(n-3)+(n-2)+(n-1) = (1+n-1)*(n-1)/2
    $$

    image-20250522170647125
  • 1个主程序员的沟通数
    $$
    n-1
    $$

    image-20250522170747000

在进行软件开发时,采用无主程序员的开发小组,成员之间相互平等;而主程序员负责制的开发小组,由一个主程序员和若干成员组成,成员之间没有沟通。在一个由8名开发人员构成的小组中,无主程序员组和主程序员组的沟通路径分别是(D)。
A. 32和8 B. 32和7 C. 28和8 D. 28和7

成本管理

主要考点是COCOMO II模型

该模型主要用于成本估量的

image-20250522171443742

工作量估算模型COCOMO II的层次结构中,估算选择不包括(C)。
A. 对象点
B. 功能点
C. 用例数
D. 源代码行