软件制造工程学习笔记(详尽版)
软件制造工程学习笔记
- 第一章 软件工程学概述
- 软件危机
- 软件工程
- 第二章 软件设计基本概念
- 设计过程
- 设计原理*
- 模块化
- 抽象
- 第三章 详细设计
- 结构程序设计
- 人机界面设计
- 过程设计工具
- 程序流程图
- 盒图(N-S图)
- PAD图
- 判定表
- 判定树
- 过程设计语言(PDL)
- 面向数据结构的设计方法
- Jackson图
- 改进的Jackson图
- Jackson方法
- 程序复杂度的定量度量
- McCabe方法
- Halstead方法
- 小结
- 第四章 软件编码
- 选择程序设计语言
- 代码规范
- 代码组织与风格
- 注释
- 命名
- 声明
- 语法结构
- 代码版本控制(SVN)
- 第五章 软件测试
- 软件测试基础
- 目标
- 准则
- 方法
- 测试步骤
- 测试阶段的信息流
- 单元测试
- 代码审查
- 计算机测试
- 集成测试
- 确认测试
- 白盒测试
- 逻辑覆盖
- 控制结构测试
- 黑盒测试技术
- 等价划分
第一章 软件工程学概述
软件危机
- 典型表现
- 对软件的开发成本和进度的估计常常很不准确
- 用户对“已完成的”软件系统不满意的现象经常发生
- 软件产品的质量往往靠不住
- 软件常常是不可维护的(难以或无法添加新功能)
- 软件通常没有适当的文档资料
- 软件成本在计算机系统总成本中所占的比例逐年上升
- 软件开发生产率的提高速度远赶不上软件需求的提高速度
- 产生原因
- 与软件本身特点有关
- 软件是逻辑部件
- 软件不会被“用坏”,如果发现了错误,很可能是开发时期引入
- 软件规模爆发,而且程序复杂性将随着程序规模的增加而呈指数上升
- 与软件开发和维护的方法不正确有关
- 忽视软件需求分析的重要性
- 轻视软件维护
- 与软件本身特点有关
- 消除软件危机的途径
- 消除“软件就是程序”的错误观念
- 软件开发应是组织良好、管理严密、各类人员协同配合共同完成的项目
- 成熟的软件开发技术和方法
- 软件工具和软件工程支持环境
软件工程
软件工程是指导计算机软件开发和维护的一门工程学科。采用工程的概念、原理、技术和方法来开发和维护软件,把经过时间考验而证明正确的管理技术和当前能够得到的最好的技术方法结合起来,以经济地开发出高质量的软件并有效地维护它,这就是软件工程
本质特性
关注于大型程序的构造
中心课题是控制复杂性
将复杂问题分解成可理解的问题,并使各部分之间保持简单的通信关系,此法不能降低复杂性但可使其变为可管理的
软件经常变化
需求一直在变化
开发软件的效率非常重要
和谐地合作是开发软件的关键
必须有效的支持它的用户
意味着需要仔细研究用户以确定适当的功能需求、可用性要求等,应写出用户手册和培训材料,注意建立使用新系统的环境(培训用户,使其习惯于新的工作流程)
在软件工程领域中通常由具有一种文化背景的人替具有另一种文化背景的人创造产品
软件开发人员通常并不是其他领域专家但需要为其他领域开发软件,就需要事先了解那些领域的流程
基本原理
用分阶段的生命周期计划严格管理
坚持进行阶段评审
不能等到编码阶段结束后在进行质量保证工作,理由如下:
- 错误出现往往是由于设计造成的(设计错误占比63%,编码错误占比37%)
- 错误发现与改正得越晚,改正所需付出的代价越高
实行严格的产品控制
基准配置管理(基线配置、变动控制):为保持软件各个配置成分的一致性而进行的管理
基线配置:他们是经过阶段评审后的软件配置成分(各个阶段产生的文档或程序代码)
变动控制:一切有关修改软件的建议,特别是涉及对基准配置的修改建议都必须经过审批才可修改采用现代程序设计技术
结果应能清楚地审查
根据软件开发项目的总目标及完成期限,规定开发组织的责任和产品标准,从而使所得到的结果能够清楚地审查
开发小组的人员应该少而精
承认不断改进软件工程实践的必要性
软件工程方法学
软件工程包括技术和管理两方面的内容
管理:通过计划、组织和控制等一系列活动,合理地配置和使用各种资源,以达到既定目标的过程
三要素:工具、方法、过程
方法:完成软件开发的各项任务的技术方法,回答“怎样做”的问题
工具:运用方法而提供的自动的或半自动的软件工程支撑环境过程:为了获得高质量软件所需要完成的一系列任务的框架,它规定了完成各项任务的工作步骤
传统方法学:也称为生命周期方法学或结构化范型
采用结构化技术(结构化分析、设计、实现)
面向对象方法学——强调主动地多次反复迭代
面向对象方法:把数据和行为看成同等重要,它是一种以数据为主线,把数据和对数据的操作紧密地结合起来的方法。
四个要点:对象、类、继承、消息
软件生命周期
三个时期(软件定义、软件开发、软件维护),八个阶段(问题研究、可行性研究、需求分析、概要设计、详细设计、编码和单元测试、综合测试、软件发布运行维护)
- 问题定义:问题是什么
1. 研究出客户所需要解决的问题,确认客户待解决的问题。
2. 结果:写出关于问题性质、工程目标和工程规模的书面报告,并得到客户确认
2. 可行性研究:有可行的解吗
3. 系统分析员压缩和简化系统分析设计,探索这个问题是否值得去解,研究出可行的解决方案
4. 结果:系统的高层逻辑模型;可行性论证报告
5. 需求分析:必须做什么
6. 弄清用户的全部需求,对需求进行分析建模
7. 结果:系统的逻辑模型;用规格说明书准确的记录对目标系统的需求
8. 概要设计:概括地说怎样实现目标系统
9. 完成系统的数据设计、体系结构设计和接口设计;撰写概要设计书
10. 结果:可能的解法(系统流程图、成本效益分析);推荐的系统体系结构(层次图或结构图)
11. 详细设计:怎样具体实现该系统
12. 对概要设计所划分的模块进一步细化;详细描述项目所有内容,并完成详细设计书编制
13. 结果:每个模块的算法和数据结构(程序流程图、PAD图、N-S图、类图等)
14. 编码和单元测试:得到正确、易理解维护的程序模块
15. 综合测试:通过各种类型的测试使软件达到预定的要求
16. 通过各种测试对目标系统进一步测试检验
17. 结果:测试设计及结果
18. 软件发布、运行、维护:使系统持久地满足用户的需要
19. 改正性维护、适应性维护、完善性维护、预防性维护
20. 结果:完整准确地维护记录
- 问题定义:问题是什么
软件过程
软件过程是为了获得高质量软件所需要完成的一系列任务的框架,它规定了完成各项任务的工作步骤
需求分析与验证、规格说明与验证、设计与验证、编码与验证、综合测试、维护
瀑布模型
按软件开发流程进行顺序开发,适用于目的需求明确的情况
特点
阶段间具有顺序性和依赖性
- 必须等前一段工作完成后才能开始后一段工作
- 前一段的输出文档就是后一段的输入文档,所以需要保证每个阶段的文档正确才能输出最终正确的结果
推迟实现的观点
急于求成而忽略先前的分析会导致总工作量的增加
质量保证的观点
文档驱动型,文档作为每个阶段开始和验收的依据
优点
- 强迫开发人员采用规范的方法
- 严格规定每个阶段必须提交的文档
- 要求每个阶段都需要进行质量保证测试
缺点
文档驱动型脱离实际,只是停留在纸面上,在实践中可能不能满足客户真实需要
快速原型模型
在用户不能给出完整、准确的需求说明或开发者不能确定算法的有效性、操作系统的适应性或人机交互的形式等多种情况下,可以根据用户的一组基本需求,快速建造一个模型,评估,然后再进一步精化,调整模型,适用于需求不明确的情况
1. 快速原型1. 快速建立起来的可以在计算机上运行的程序,他所能完成的功能往往是最终产品能完成的功能的子集2. 一般在得到需求分析之后就不再使用2. 特点1. 不带反馈环
- 迅速
增量模型
把软件产品作为一系列的增量构件来设计、编码、集成和测试,每个构件由多个相互作用的模块构成,并且能够完成特定的功能(类似于版本管理,一个个版本更新并最终得到版本)
1. 优点1. 短期内可以提供可完成部分工作的初代产品给客户,客户可以早期熟悉软件而不必等到软件全部开发完2. 用户有较为充裕的时间学习和适应新产品3. 可以更早的知道客户需求的变化,对软件进行修改4. 对软件公司来说可以降低开发成本2. 风险大1. 可能提高开发速度,但需要密切地监控整个开发过程,否则将冒构件无法集成到一起的风险,使整个工程毁于一旦3. 注意事项1. 增量构件规模适中2. 系统设计需要构架为开放性的,防止后期增量无法集成4. 适用对象1. 经常改变的开发过程2. 人手不足的软件公司
螺旋模型
使用原型及其他方法来尽量降低风险
1. 简化版本1. 快速原型+瀑布模型+风险分析(在每个阶段前进行风险分析,每个阶段后进行评估)2. 优点1. 风险驱动2. 减少测试过多或不足所带来的风险3. 对可选方案和约束条件的强调用力与已有软件的重用4. 维护知识模型的一个周期3. 缺点1. 需要具有相当丰富的风险评估经验的人员参与开发,增加成本2. 过多的迭代次数会增加开发成本,延迟提交时间4. 适用对象1. 庞大复杂高风险系统2. 大型软件开发
喷泉模型*
典型的面向对象生命周期模型
Rational统一过程*
RUP是一种迭代的,以架构为中心的,用例驱动的模型
最佳实践
迭代式开发
允许软件需求变化
管理需求
使用基于构件的体系结构
子系统模块
可视化建模
画UML图
验证软件质量
控制软件变更
敏捷过程与极限编程*
敏捷软件开发宣言(敏捷价值观)
- 个体和交互胜过过程和工具
- 可以工作的软件胜过面面俱到的文档
弱化文档的重要性,强调客户需求的是一个可以使用的软件
- 客户合作胜过合同谈判
- 响应变化胜过遵循计划
极限编程(XP)
敏捷过程中最负盛名的一个
适用于需求模糊且经常改变的场合
适用于有限时间有限开发人员的环境
重构:不改变系统行为的前提下,重新调整和优化系统内部结构,以提高代码的可维护性,降低复杂性,消除冗余
集体所有:每个小组成员都拥有修改代码的权利,每个成员都对全部代码质量负责
持续集成:极限编程主张在一天内多次集成系统,随着需求的更变,应该不断的进行回归测试
现场客户:至少有一名客户代表在项目的整个周期中与开发人员一起紧密地配合工作
系统隐喻:整个系统联系在一起的全局视图,它描述系统如何运作,以及用何种方式把新功能加入到系统中
可持续的开发速度:每周工作不超过40h,连续加班不超过两周
- 极限编程的整体开发过程
- XP适用范围
- 适合规模小、进度紧、需求变化大的软件开发
- 不适用于中大型项目(团队超过十人)、重构会导致大量开销的app
微软过程
- 微软过程准则
- 微软软件生命周期
- 微软过程模型
小结
- 软件=程序+数据+文档
- 软件危机:原因,现象。办法(软件工程学)
- 软件工程三要素:方法、工具和过程
- 软件生命周期:定义、开发、维护
- 每种模型都有自己适用的软件开发,选择合适的模型进行开发
第二章 软件设计基本概念
设计过程
从工程管理的角度,可以将软件设计分为概要设计阶段和详细设计阶段
- 系统设计阶段:确定系统的具体实现方案
- 设想供选择的方案
- 选取合理的方案
- 推荐最佳方案
- 结构设计阶段:确定软件结构
- 功能分解
- 设计软件结构
- 设计数据库
- 制定测试计划
- 书写文档
- 审查和复审
设计原理*
模块化
把复杂问题分解成许多容易解决的小问题
- 模块:是由边界元素限定的相邻程序元素的序列,而且有一个总体标识符代表它
- 模块化:每一个模块实现一个子功能
- 目的:使一个复杂的大型程序能被人的智力所管理,软件应该具备的唯一属性。
- 每个程序都相应的有一个最适当的模块数目M,使得系统的开发成本最小
- 作用:
- 使软件结构清晰,容易阅读理解
- 使软件容易测试和调试,有助于提高软件可靠性
- 能够提高软件的可修改性
- 有助于软件开发工程的组织管理
- 模块间的信息传递:传送参数数据
- 模块的调用关系和接口:模块间用单向箭头连接,从调用模块指向被调用模块
抽象
现实世界中的一些事物、状态或过程之间总存在着某些相似的方面(共性(。把这些相似的方面集中和概括起来,暂时忽略它们之间的差异
一般抽象过程
- 使用层次的方式构造和分析复杂系统
- 对于复杂的动态系统首先可以用一些高级的抽象概念构造和理解,高级概念又可以用一些低级概念构造和理解,如此进行下去直至最低层次的具体元素
软件工程抽象过程
- 每一步都是对软件解法的抽象层次的一次精化
- 在可行性研究阶段,软件作为系统的一个完整部件
- 需求分析阶段,软件解法是使用在问题环境内熟悉的方式描述的
- 当由总体设计向详细设计过渡时,抽象的程度也就随之减少了
- 当程序写出来以后,也就达到了抽象的最低层
逐步求精
为了能集中精力解决主要问题而尽量推迟对问题细节的考虑。逐步求精是人类解决复杂问题时采用的基本方法,也是许多软件工程技术的基础。
Miller法则:一个人在任何时候都只能把注意力集中在(7±2)个知识块上
- 可以把逐步求精看作是一项把一个是期内必须解决的种种问题按优先级排序的技术。逐步求精方法却把每个问题都将被解决的,而且每个问题都将在合适的时候被解决,但是在任何一个时候一个人都不需要同时处理七个以上的知识块
- 求精实际上是细化过程
- 求精和抽象是两组互补的概念
信息隐藏和局部化
信息隐藏
一个模块内包含的信息(过程和数据)对于不需要这些信息的模块来说应是不能访问的
局部化
把一些关系关系密切的软件元素物理的放的彼此靠近,局部化有利于实现信息隐藏
作用
- 隐藏意味着有效的模块化可以通过定义一组独立的模块实现,这些模块彼此间紧紧交换那些为了完成系统功能而必须交换的信息
- 对程序模块修改的时候将最小程度影响到其它模块,易于维护
模块独立
模块独立的概念是模块化、抽象、信息隐藏和局部化概念的直接结果
每个模块完成一个相对独立的特定子功能,并且和其他模块的关系很简单
模块独立程度的两个定性标准度量
- 耦合衡量不同模块彼此间互相依赖的紧密程度,耦合要低,即每个模块和其它模块之间的关系要简单
- 内聚衡量一个模块内部各个元素彼此结合的紧密程度。内聚要高,每个模块完成一个相对独立的特定子功能
耦合
是对一个软件结构内不同模块之间连接程度的考量。耦合度强弱取决于模块间接口的复杂程度,进入或访问一个模块的点,以及通过接口的数据
非直接耦合/完全独立
- 两个模块能独立工作而不需要另一个模块的存在,那么他们完全独立
- 在一个软件系统中不可能所有模块之间都没有任何连接
数据耦合:两个模块彼此间通过参数交换信息,而且交换的信息仅仅是数据
控制耦合:两个模块彼此间传递的信息中有控制信息
- 控制耦合往往是多余的,在把模块适当分解后通常可以用数据耦合代替它
特征耦合:把整个数据结构作为参数传递而被调用的模块只需要使用其中一部分数据元素时,就出现了特征耦合
- 导致对数据的访问失去了控制,从而给计算机犯罪提供了机会
公共环境耦合:两个或以上模块通过一个公共数据环境相互作用时,他们之间的耦合称为公共环境耦合
公共环境可以是全程变量、共享的通信区、内存的公共覆盖区、任何存储介质上的文件、物理设备等
公共环境耦合的复杂程度随耦合的模块个数而变化,当耦合的模块个数增加时复杂程度显著增加。如果只有两个模块有公共环境,那么这种耦合有下面两种可能:
一个模块往公共环境送数据,另一个模块从公共环境取数据。这是数据耦合的一种形式,是比较松散的耦合
两个模块都既往公共环境送数据又从里面取数据,这种耦合比较紧密,介于数据耦合和控制耦合之间
如果两个模块共享的数据很多,都通过参数传递可能很不方便,这时可以利用公共环境耦合
内容耦合:最高程度的耦合
- 一个模块访问另一个模块的内部数据
- 一个模块不通过正常入口转到另一个模块的内部
- 两个模块有一部分程序代码重叠
- 一个模块有多个出入口
以上情况出现之一,这两个模块就发生了内容耦合
应该坚决避免使用内容耦合
内聚(重要性高于耦合)
标志着一个模块内各个元素彼此结合的紧密程度,他是信息隐藏和布局化概念的自然扩展,理想的内聚模块只做一件事情
- 偶然内聚:一个模块完成一组任务,这些任务彼此间即使有关系,关系也是很松散的,就叫偶然内聚。
- 偶然内聚出现错误的概率比其他内聚高很多
- 逻辑内聚:一个模块完成的任务在逻辑上属于相同或相似的一类,称为逻辑内聚
- 不同功能的模块混在一起,和用部分程序代码,即使局部功能的修改有时也会影响全局,因此,这部分的代码修改起来也比较困难
- 时间内聚:一个模块包含的任务必须在同一段时间内执行,就叫时间内聚
- 好于逻辑内聚
- 过程内聚:一个模块内的处理元素是相关的,而且必须以特定的次序进行
- 通信内聚:模块中所有元素都使用同一个输入数据和(或)产生同一个输出数据,则为通信内聚
- 顺序内聚:一个模块内的处理元素和同一个功能密切相关,而且这些处理必须顺序执行
- 功能内聚:模块中所有处理元素属于一个整体,完成一个单一的功能,则为功能内聚。(最高程度的内聚)
- 偶然内聚:一个模块完成一组任务,这些任务彼此间即使有关系,关系也是很松散的,就叫偶然内聚。
启发规则
- 改进软件结构提高模块独立性
- 通过模块分解或合并,降低耦合提高内聚
- 模块规模应当适中
- 过大的模块往往是由于分解不充分
- 过小的模块开销大于有效操作,会使系统接口变得复杂
- 深度、宽度、扇入扇出都应当适当
- 深度表示软件结构中控制的层数,粗略地标志一个系统的大小和复杂程度
- 宽度是软件结构内同一层次上的模块总数的最大值,一般来说,宽度越大系统越复杂。对宽度影响最大的因素是模块的扇出
- 扇出是一个模块直接控制或调用的模块数目
- 设计的好的软件结构通常顶层扇出比较高,中层扇出较少,底层扇入到公共的实用模块中去
- 模块的作用域应该在控制域之内
- 模块的作用域:受到该模块内一个判定影响的所有模块的集合
- 模块的控制域:是这个模块本身以及所有直接或间接从属于它的模块的集合
- 力争降低模块接口的复杂度
- 设计单入口单出口的模块
- 模块功能应该可以预测:只要输入的数据相同就产生相同的输出
- 改进软件结构提高模块独立性
小结
- 总体设计阶段的基本目的:用比较抽象概括的方式确定系统如何完成任务
第三章 详细设计
详细设计阶段的根本目标:确定应该怎样具体的实现所要求的系统。经过这个阶段的设计工作,应该得出对目标系统的精准描述,从而在编码阶段可以把这个描述直接翻译成用某种程序设计语言的代码
结构程序设计
- 经典定义:如果一个程序的代码块仅仅通过顺序选择和循环这三种基本控制结构进行连接,并且每个代码块只有一个入口和出口,则称这个程序是结构化的
- 更全面定义:结构程序设计是尽可能少用GOTO语句的程序设计方法。最好仅在检测出错误时才使用GOTO语句,且应该总是使用向前的GOTO语句
- 三条黄金规则
- 置用户于控制之下
- 减少用户记忆负担
- 保持界面一致
人机界面设计
设计问题
系统响应时间
- 指从用户完成某个控制动作,到软件给出预期的响应之间的这段时间
- 不能过长(用户会感到紧张和沮丧),也不能太短(迫使用户加快操作节奏,可能会犯错)
用户帮助设施
大多数现代软件都提供练级帮助设施,这使得用户无需离开用户界面就能解决自己的问题
集成的帮助设施
设计在软件里面,对用户操作敏感,用户可从与刚刚完成的操作有关的主体中选择一个请求帮助
附加的帮助设施
在系统建成后再添加到软件中的,它实际上是一种查询能力有限的联机用户手册
集成的优于附加的帮助设施
出错信息处理
- 出错信息和警告信息,是出现问题是交互式系统给出的坏消息
命令交互
- 多数情况下,用户既可以用菜单也可以用键盘命令进行软件交互
设计过程
是一个迭代的过程,通常先创建设计模型,再用原型实现这个设计模型,并由用户试用和评估,根据用户意见进行修改
人机界面设计指南
界面分类
- 菜单型
- 对话框型
一般交互界面
一般交互指南涉及信息显示,数据输入和系统整体控制
保持一致性。
使用一致的格式提供有意义的反馈(视觉、听觉)
在执行有较大破坏性的动作之前要求用户确认。
允许取消绝大多数操作减少两次操作之间必须记忆的信息量
提高对话、移动和思考的效率
允许犯错误
按功能对动作分类,并据此设计屏幕布局提供对用户工作敏感的帮助设施
用简单动词或动词短语作为命令名
信息显示界面
数据输入界面
过程设计工具
程序流程图
又称为程序框图,历史悠久使用最为广泛的描述过程设计的方法,但也是最混乱的一种方法
- 缺点
- 本质上不是逐步求精的好工具
- 用箭头代表控制流,因此程序猿可以不受约束不顾结构程序设计的精神,随意转移控制
- 程序流程图不易表示数据结构
盒图(N-S图)
一种不允许违背结构程序设计精神的图形工具
- 特点
- 功能域明确
- 不可能任意转移控制
- 很容易确定局部和全程数据的作用域
- 很容易表现嵌套关系,也可以表示模块的层次结构
PAD图
- 优点
- 使用表示结构化控制结构的PAD符号所设计出来的程序必然是结构化程序
- PAD图所秒回的程序结构十分清晰。途中最左边的竖线是程序的主线,即第一层结构
- 用PAD图表现程序逻辑,易读、易懂、易记
- 容易将PAD图转换成高级语言源程序,这种转换可用软件工具自动完成
- 即可用于表示程序逻辑,也可用于描绘数据结构
- PAD图的符号支持自顶向下、逐步求精方法的使用。
判定表
包含多重嵌套的条件选择,用判定表能够清晰地表示复杂的条件组合与应做的动作之间的相应关系
判定表需要考虑到所有可能出现的组合,不然就容易隐藏bug
判定树
过程设计语言(PDL)
也称伪码
优点:
1. 可以作为注释直接插在源程序中间,保持文档和程序的一致性,提高文档质量
- 可以使用普通的正文编辑程序或文字处理系统方便地完成PDL的书写和编辑
- 已经有自动处理PDL的程序存在,而且可以自动由PDL生成程序代码
缺点:
不如图形工具形象直观,描述复杂的条件组合与动作间的对应关系时不如判定表清晰简单
面向数据结构的设计方法
Jackson图
顺序结构
选择结构
在顺序结构的标记右上角有一个°标记
重复结构
在顺序结构的标记右上角有一个*标记
改进的Jackson图
Jackson图的缺点是,这种工具表示选择或重复结构时,选择条件或者循环结束条件不能直接在图上表示出来,影响了图的表达能力,并且连线为斜线,在行式打印机上不易输出
Jackson方法
五个步骤:
分析并确定输入数据和输出数据的逻辑结构,并用Jackson图庙会这些数据结构
找出输入数据结构和输出数据结构中有对应关系的数据单元
对应关系:指有直接的因果关系,在程序中可以同时处理的数据单元。但对于重复出现的数据单元,重复的次序和次数必须都相同才有可能有对应关系
导出描绘程序结构的Jackson图,此图应该综合输入数据结构和输出数据结构的层次关系而导出来
列出所有操作和条件(包括分支条件和循环结束条件),并且把它们分配到程序结构图的适当位置
用伪代码表示程序
程序复杂度的定量度量
McCabe方法
- 流图:为了突出表示程序的控制流,实际上是退化了的程序流程图,仅仅描绘程序的控制流程
- 任何方法表示的过程设计结果,都可以翻译成流图
- 在过程设计中包含复合条件时,应把复合条件分解为若干个简单条件,每个简单条件对应流图中的一个节点
- 计算环形复杂度的方法
- 环形复杂度定量度量程序的逻辑复杂度
- 计算方法
- 流图中线性无关的区域数等于环形复杂度
- 环形复杂度V(G)=E-N+2,其中E为流图中边的条数,N是节点数
- V(G)=P+1,其中P是流图中判定结点的数目
- 环形复杂度的用途
- 是一种对测试难度的一种定量度量,也能对软件最终的可靠性给出某种预测
- 环形复杂度越高,程序往往越困难、越容易出问题,V(G)≤10为宜
Halstead方法
根据程序中运算符和操作数的总数来度量程序的复杂程度
N=N1+N2,N1为程序中运算符出现的总次数,N2为操作数出现的总次数,N为程序长度
预测程序长度的公式
H=n1log2n1+n2log2n2H=n_1log_2n_1+n_2log_2n_2 H=n1log2n1+n2log2n2预测程序中包含错误的个数的公式
E=Nlog2(n1+n2)/3000E=Nlog_2(n_1+n_2)/3000 E=Nlog2(n1+n2)/3000
小结
- 详细设计阶段的关键任务是确定怎样具体的实现用户需要的软件系统,即设计出程序的蓝图
- 人机界面设计是接口设计的一个重要的组成部分
- 过程设计应该在数据设计、体系结构设计和接口设计完成后进行
- 许多应用领域中信息都有清楚地层次结构,在开发这类应用系统时可以采用棉线数据结构的设计方法完成过程设计
- 使用环形复杂度可以定量度量程序的复杂程度,V(G)=10是模块规模的合理上限
第四章 软件编码
选择程序设计语言
划代 | 语言 | 特点 | 级别 |
---|---|---|---|
1GL | 机器语言 | 程序不直观,编程出错率高 运行效率高,使用CPU硬件 | 低级 |
2GL | 汇编语言 | 比机器语言直观,减少了出错率 与机器码一样长 使用于嵌入式开发,驱动程序 | |
3GL | 面向过程语言, C | 语法简洁,速度快,使用嵌入式,驱动程序,系统软件,服务器程序 | 高级 |
4GL | 面向对象语言, JAVA**,**C#, C++ | Java:虚拟机,跨平台,垃圾回收机制,使用Web****应用程序,信息管理系统 **C#:微软平台,**垃圾回收机制 **C++:**自己管理内存,速度快,C/S结构程序,服务器程序 |
代码规范
代码组织与风格
- 基本原则
- 代码组织的目的:美观
- 代码组织和风格基本原则:便于自己的开发,易于他人的交流
- 因个人习惯和编辑器等可以设置和形成自己的风格,但必须前后一致并符合规范
- 缩进
- 子功能模块应在父模块后缩进
- 当功能块过多导致缩进过深时,应当提取出来作为子函数
- 代码缩进一个TAB(4个字符),不同的编译器对TAB的解释不一样,需要进行设置
- 长度
- 为了便于阅读理解,单个函数的有效代码应当控制在100行内(不包括注释行),当功能模块过大会造成阅读困难,应当提取出子函数,也方便重用
- 单个类也不宜过大,加上注释行不宜超过1500行
- 尽量避免使用大类和长方法
- 行宽
- 每行代码不要超过屏幕宽度,折行后应使用tab缩进以便于阅读
- 间隔
- 类、方法、功能块间以空行间隔,但不得有无规则的大面积空行
- 操作符两端应空一格以增加可读性
- 相应独立的功能模块之间可使用注释行间隔,并标明相应内容
- 对齐
- 关系密切的行应对齐
- 连续赋值时应对齐操作符
- 方法参数过多时应当在每个参数后换行并对齐
- 当控制或循环中的条件比较长时当换行(操作符前)、对齐并注释各条件。
- 变量定义最好通过添加空格形成对齐,同一类型的变量应放在一起
- 括号
- {} 中的语句应该单独作为一行,左括号"{“当紧跟其语句后,右括号”}"永远单独作为一行且与其匹配行对齐,并尽量在其后说明其匹配的功能模块。
- 较长的方法以及类、接口等的右括号后应使用//end …等标识其结束。
- 左括号是否换行等随个人习惯而定,若换行则当与其前导语句首字符对齐。
注释
注释分为序言性注释和功能性注释
注释基本原则
(1) 注释应该增加代码的清晰度。代码注释的目的是要使代码更易于被其他开发人员等理解。
(2) 避免使用装饰性内容。
(3) 保持注释的简洁。
(4) 注释信息不仅要包括代码的功能,还应给出原因。
(5) 不要为注释而注释。
(6) 除变量定义等较短语句的注释可用行尾注释外,其他注释当避免使用行尾注释。文件注释
- 在每个文件的头部都应该包含该文件的功能、作用、作者、版权以及创建、修改记录等
类、接口注释
- 在类、接口定义之前当对其进行注释,包括类、接口的目的、作用、功能、继承何种父类,实现的接口、算法、使用方法 、示例程序等
方法注释
- 明确该方法功能、作用、各参数及返回值等
- 参数注释时当注明其取值范围
- 返回值应当注释出失败、错误、异常时的返回情况
- 异常应注释出什么情况、什么时候、什么条件下引发什么样的异常
其他注释
- 对重要的变量加以注释
- 对不易理解的分支加以注释
- 对于异常处理,表明正常情况以及异常情况发生的条件,并说明发生异常时程序如何处理
- 注释占源代码量的20%左右
Java注释
- 行注释 //
- 用于注释某行代码
- 块注释 /* */
- 用于注释某块代码或说明文件、方法、数据结构等的意义和用途
- 文档注释 /** */
- 可引入javadoc文档
- 注释文档将用来生成HTML格式的代码报告,所以注释文档必须书写在类、域、构造函数、方法,以及字段(field)定义之前。
- 行注释 //
命名
基本命名规则
(1) 名字应能反映它所代表的实际东西,应有一定实际意义。例如,表示次数的量用Times,表示总量的用Total,表示平均值的用Average,表示和的量用Sum等
(2) 使用可以准确说明变量/字段/类/接口/包等的完整的英文描述符。例如,采用类似 firstName,listAllUsers 或 CorporateCustomer 这样的名字,严禁使用汉语拼音及不相关单词命名。
(3) 采用该领域的术语。如果用户称他们的“客户” (clients) 为“顾客” (customers),那么就采用术语 Customer 来命名这个类,而不用 Client。(4) 采用大小写混合,提高名字的可读性。一般应该采用小写字母,但是类和接口的名字的首字母,以及任何中间单词的首字母应该大写。
(5) 尽量少用缩写,但如果一定要使用,当使用公共缩写和习惯缩写等,如实现(implement)可缩写成impl,经理(manager)可缩写成mgr等,严禁滥用缩写。
(6) 避免使用长名字(最好不超过 25 个字母)。
(7) 避免使用相似或者仅在大小写上有区别的名字。类、接口
- 所有单词首字母大写,能准确反映该类、接口含义、功能的词、一般采用名词
常量
- 采用完整的英文大写单词,词与词之间用下划线连接
变量和参数
- 变量命名一般由前缀和修饰语构成
- 类型前缀:用s(String)表示字符串, c(char)表示字符, n(number)数值, i(intger)表示整数, d(double)表示双精度,f (float)浮点型, b(bool)布尔型, dt(date)表示日期型.
部件/组件
- 采用完整的英文描述命名组件
- 控件命名由(前缀+修饰语)构成。前缀即为控件的名称或缩写。
方法
- 方法的命名应采用完整的英文描述符,大小写混合使用:所有中间单词的第一个字母大写。方法名称的第一个单词常常采用一个有强烈动作色彩的动词。
- 取值类使用get前缀,设值类使用set前缀,判断类使用is(has)前缀。
声明
- 方法
- 为减小类与类之间的耦合,所遵循的法则:尽量限制成员函数的可见性
- 声明顺序:构造方法、静态公有方法、静态私有方法、公共方法、受保护方法、私有方法
- 字段(成员变量)
- 没有足够理由不要把实例变量或类变量声明为公有,所有字段都建议设置为私有然后设置get/set方法
- 不要把局部变量名字和更大范围的字段的名字设置相同
- 一行代码只声明一个变量,一个变量只做一件事
- 声明顺序
- 常量、类变量、公有字段、受保护字段、私有字段
语法结构
语句构造力求简单直接,不要为了片面追求效率而使语句复杂化
- 程序编写首先应当考虑清晰性,不要可以追求技巧使程序过于紧凑
- 程序要能直接了当地说明程序员的用意
- 程序编写做到清晰第一效率第二,提升效率应主要考虑提升算法
- 首先保证程序正确,然后才要求提高速度
- 避免使用临时变量,会降低可读性
- 让编译程序做简单优化
- 尽量使用库函数
- 避免使用空的if else语句
- 避免采用过于复杂的条件测试
- 尽量减少否定条件的条件语句
- 不要修补不好的程序,要重新编写,不要一味地追求代码复用,要重新组织
- 对太大的程序,要分块编写、测试,然后再集成
代码版本控制(SVN)
- 好处
- 便于团队代码共享
- 保证整个团队使用统一的代码版本
- 能获得版本控制工具中保存的任何版本
- 能够把出错或误操作的最新版本恢复到正确的历史版本
- 快速的集成
第五章 软件测试
软件测试基础
软件测试阶段,测试人员将努力设计出一系列测试方案,目的是破坏已经建造好的软件系统——竭力证明程序中有错误,不能按照预定要求正确工作。发现问题不是最终目的,是为了解决问题。
目标
- 测试的目标或定义
- 测试是为了发现程序中的错误而执行程序的过程
- 好的测试方案是极可能发现迄今为止尚未发现的错误的测试方案
- 好的测试是发现了至今为止尚未发现的错误的测试
- 从心理学角度,测试不能由开发人员自行测试,需要另组成测试小组
- 测试只能查找程序中的错误,不能证明程序中没有错误
准则
- 从用户角度测试
- 应远在程序开始测试之前就设计好测试计划
- 把Pareto原理应用到软件测试中
Pareto原理:程序中80%的错误一般是由20%的软件模块造成的
- 应从小规模测试开始,首先测试重点模块
- 穷举测试是不可能的,只能精心设计测试方案,充分覆盖程序测试范围
- 由独立第三方人员进行测试
方法
黑盒测试
已经知道产品应该具有的功能,通过测试来检验是否每个功能都能正常使用
- 不必知道内部结构和处理过程,也就是对程序接口进行测试,又称功能测试
白盒测试
已经知道产品的内部工作过程,通过测试来检验产品内部动作是否按照规格说明书的规定正常进行
- 知道程序结构和处理算法,按照程序内部的逻辑测试程序,又称结构测试
测试步骤
模块测试
- 在设计的好的软件中,每个模块完成一个清晰定义的子功能,所以有可能可以把单个模块作为一个实体来测试,而且通常比较容易检验模块正确性的测试方案。
- 模块测试又称为单元测试。
- 往往发现的是编码和详细设计的错误
子系统测试
- 把经过测试的模块放在一起形成一个子系统测试
- 主要测试模块间的互相协调和通信
- 着重测试模块接口
系统测试
- 把测试过的子模块装配成一个完整系统,此过程不仅应该发现设计和编码的错误,还应该验证系统确实能够提供需求说明书中指定的功能,且系统的动态特性也符合预定要求
- 不论子系统测试还是系统测试,都有检测和组装两重含义,通常称为集成测试
验收测试
- 把软件系统作为单一实体进行测试
- 测试内容与系统测试基本类似,但是在用户的参与下进行的,且可能使用实际数据来进行测试
- 目的是验证系统确实能满足用户的需要,也称为确认测试
平行运行
比较重大的软件产品在验收后往往不立即投入生成性运行,而是要经过一段时间的平行运行时间的考验,即新旧系统同时运行,以便比较新旧系统的处理结构
- 目的
- 用户可以在准生产环境中运行新系统而又不冒风险
- 用户能有一段熟悉新系统的时间
- 可以验证用户指南和使用手册之类的文档
- 能够以准生产模式对新系统进行全负荷测试,可以用测试结果验证性能指标
- 目的
测试阶段的信息流
- 输入信息
- 软件配置:需求说明书、设计说明书和源程序清单等
- 测试配置:测试计划和测试方案(测试用例、每组输入预定检验的功能及应得到的正确输出)
- 调试
- 任务:比较测试得出的结论和预期结果,如果不一样则很可能是程序中出现的错误,设定确定错的的准确位置并且改正它
- 通常由程序编写者完成
单元测试
着重对五个模块进行测试:
- 模块接口
- 首先对模块接口的数据流进行测试,如果数据不能正确进出,所有其他测试也是没意义的
- 主要检查:参数的数目、次序、属性或单位系统与变元是否一致等
- 局部数据结构
- 对模块来说局部数据结构是常见的错误来源
- 主要发现局部数据说明、初始化、默认值等方面的错误
- 重要的执行通路
- 选择最具代表性、最可能发现错误的执行通路
- 出错处理同理
- 软件应预见一些可能出现的错误,并提供出错处理通路
- 测试时应着重测试:
- 对错误的描述是难以理解的
- 记下的错误与实际遇到的错误不同
- 在对错误进行处理之前,错误条件已经引起系统干预
- 对错误的处理不正确
- 描述错误的信息不足以帮助确定造成错误的位置
- 边界条件
- 软件常常在他的边界上失效:如处理n元数组的第n个元素时
代码审查
由审查小组正式进行人工测试源程序称为代码审查
- 审查前,成员应先研究设计说明书,力求理解这个设计
- 一次代码审查可以发现比计算机测试更多的错误,减少系统验证的总工作量
- 与计算机测试相辅相成,相互补充,缺一不可
计算机测试
为每个单元测试开发驱动软件或存根软件,通常驱动程序就是一个“主程序”,接收测试数据并传送给测试模块,在印出有关结果。存根程序代替被测试的模块所调用的模块,因此可被称为“虚拟子程序”。
- 驱动程序和存根程序代表开销,是为了进行单元测试所必须编写的测试软件,但通常并不把它们作为软件的一部分交给用户
- 模块的内聚程度高可以简化单元测试过程,若每个模块只完成一个功能,则需要的测试方案数目将明显减少,模块中的错误也更容易预测和发现
集成测试
是测试和组装软件的系统化技术,测试可能发生的接口问题
模块组装成程序有两种方法:
- 非渐增式测试
- 分别测试每个模块,再把所有模块按设计要求放在一起组合成所要的程序
- 一下把所有模块放到一起,测试者面对的情况十分复杂,难以定位改正错误,并且改正后会马上遇到下一个错误,看起来无穷无尽
- 渐增式测试
- 把下一个要测试的模块同已经测试好的那些模块结合起来进行测试,测试完以后再把下一个应该测试的模块结合进来测试
- 把程序划分成小段来构造和测试,比较容易定位和改正错误,对接口可以进行更彻底的测试,可以使用系统化的测试方法
当使用渐增方式吧模块结合到程序中时,有自顶向下和自底向上两种集成策略
自顶向下集成
从主控制模块开始,沿着程序的控制层次向下移动,逐渐把各个模块结合起来。在把附属于(及最终附属于)主控制模块的那些模块组装到程序结构中去,或者使用深度或宽度优先的策略
具体过程
- 对主控制模块测试,测试时用存根程序代替所有直接附属于主控制模块的模块
- 根据选定的结合策略,每次用一个实际模块代换一个存根程序
- 在结合进一个模块的同时进行测试
- 为了保证加入模块没有引进新的错误,需要进行回归测试(全部或部分的重复做做过的测试)
从2开始不断重复以上过程,知道构造起完整的软件结构为止。
优点:
- 能够在测试的早期对主要的控制或关键抉择进行检验,有错则改正,没错则可以提高开发人员和用户双方的信心
问题
容易遇到逻辑上的问题,最常见的是因为存根程序替代了低层次的模块,使得软件结构中没有重要数据自下往上流
解决:
- 把许多测试推迟到用真实模块代替了存根程序后再进行(失去在特定测试和组装特定模块之间的精确对应关系
- 从层次系统的底部向上组装软件(自底向上的测试)
自底向上集成
从原子模块(最低层模块)开始组装和测试。因为时从底部向上结合模块,总能得到所需的下层模块处理功能,所以不需要存根程序
- 具体过程
- 把低层模块组合成实现摸个特定的软件子功能的族
- 写一个驱动程序(用于测试的控制程序),协调测试数据的输入和输出
- 对由模块组成的子功能族进行测试
- 去掉驱动程序,沿软件结构自下向上移动,把子功能族组合起来形成更大的子功能族
- 随着组合向上移动,对测试驱动程序的需要也减少了
- 软件结构的顶部两层用自顶向下的方法组装,可以明显减少驱动程序的数目,而且族的结合也将大大简化
- 具体过程
不同集成测试策略的比较
自顶向下
- 优点
- 不需要测试驱动程序
- 能够在测试阶段的早期实现并验证系统的主要功能
- 能在早期发现上层模块的接口错误
- 缺点
- 需要存根程序,可能遇到与此相联系的测试困难,低层关键模块中的错误发现较晚
- 在早期不能充分展开人力
- 优点
自底向上
与自顶向下刚好相反
实际使用时一般使用两种方法的混合
回归测试
指重新执行依据做过的测试的某个子集,以保证上述这些变化没有带来非预期的副作用
确认测试
也称为验收测试,目标是验证软件的有效性
验证:指保证软件正确地实现了某个特定要求的一系列活动
确认:保证软件确实满足了用户需求而进行的一系列活动
软件有效性:如果软件的功能和性能如同用户所合理期待的那样,软件就是有效的
确认测试的范围
- 确认测试必须有用户积极参与,或者以用户为主进行。为了使用户能有效地使用这个系统,通常在验收前由开发单位对用户进行培训
- 确认测试通常采用黑盒测试法。应仔细设计测试计划和测试过程
- 若软件的功能和性能与用户要求有差距,则需要与用户充分协商解决方案
软件配置复查
- 目的:保证软件配置的所有成分都齐全,质量符合要求,文档与程序完全一致,具有完成软件维护所必须的细节且已编好目录
- 确认测试过程中应严格遵循用户指南及其他操作程序,以便检验这些使用手册的完整性和正确性
Alpha和Beta测试
适用于面向许多客户开发的情况
- Alpha测试由用户在开发者的场所进行,并且在开发者对用户的“指导”下进行测试,开发者负责记录发现的错误和使用中遇到的问题,总之,Alpha测试是在受控的环境下进行的
- Beta测试由软件的最终用户们在一个或多个用户场所进行,是不受控的环境中的真实应用,用户在测试过程中遇到的一切问题都可以反馈给开发者
白盒测试
逻辑覆盖
语句覆盖
- 为暴露程序中的错误,至少每个语句应该执行一次
- 含义:使用足够多的测试数据,使被测程序中的每个语句至少执行一次
- 语句覆盖是很弱的逻辑覆盖标准,为了更充分地测试程序,可以采用下述的逻辑覆盖标准
判定覆盖(分支覆盖)
- 含义:不仅每个语句必须至少执行一次,而且每个判定分支都至少执行一次
条件覆盖
- 含义:不仅每个语句至少执行一次,而且使判定表达式中的每个条件都取到各种可能的结果
- 条件覆盖通常比判定覆盖强,因为它使判定表达式中每个条件都取到了两个不同的结果,判定覆盖却只关心整个判定表达式的值
判定/条件覆盖
能同时满足判定和条件覆盖标准的逻辑覆盖
- 含义:选取足够多的测试数据,是的判定表达式中的每个条件都取到各种可能的值,而且每个判定表达式也都取到各种可能的结果
- 有时并不比条件覆盖更强
条件组合覆盖
- 是更强的逻辑覆盖标准,要求选取足够多的测试数据,使得每个判定表达式中的条件的各种可能组合都至少出现一次
- 满足条件组合覆盖标准的测试数据,也一定能够满足判定覆盖、条件覆盖和判定/条件覆盖标准
- 但满足条件组合覆盖标准的测试数据并不一定能使程序中的每条路径都执行到
点覆盖
与语句覆盖标准一致
边覆盖
通常与判定覆盖一致
路径覆盖
- 含义:选取足够多的测试数据,是程序每条可能路径都至少执行一次(有环则要求每个环至少经过一次)
控制结构测试
基本路径测试
步骤
由程序流程图导出程序控制流图
计算流图的环路复杂度
确定程序的独立路径
独立路径又称为基本路径,至少包含一条在其它独立路径中从未有过的边的路径
独立路径条数是确保程序中,每个可执行语句至少执行一次所必需的测试用例数目的上界
独立路径条数=环路复杂度V(G)
确定测试路径的集合
针对测试路径设计测试用例
条件测试
循环测试
黑盒测试技术
等价划分
基本思想
把所有可能的输入数据(包括有效和无效),划分成若干数据类(等价类),然后从每个数据类中选取少数有代表性的数据作为测试用例
这种方法完全不考虑程序的内部结构,指依据程序的规格说明来设计测试用例
原则
- 若规定了取值范围可以确定一个或两个无效等价类
- 如果规定了输入数据的一组值,而且程序要对每组输入数据分别处理,则可为每种输入值确立一个有效等价类,此外针对这组值确立一个无效等价类,他是所有不允许的输入值的集合
- 如果规定输入数据为整型,则可划分出正整、零和负整数三个类
软件制造工程学习笔记(详尽版)相关推荐
- 计算机网络学习笔记(详尽版)
计算机网络学习笔记 计算机网络 第一章 定义和特点 网络 定义 功能 特点 组成 类别 性能 结构 本章重要概念 第二章 物理层 基本概念和标准 数据通信基础知识 物理层下的传输媒体 引导型传输媒体 ...
- 软件开发流程--学习笔记
软件开发流程--学习笔记 背景 产生原因及作用 个人理解 软件开发流程 需求 需求调研 需求分析 需求确认 设计 概要设计 详细设计 编码 测试 交付 维护 知识拓展 软件开发生命周期(SDLC) 软 ...
- 《软件需求》学习笔记
为什么80%的码农都做不了架构师?>>> <软件需求>学习笔记 前几天读了Karl E.Wiegers<软件需求>,书的内容写得非常好.我这里谈谈读了此 ...
- 【Python学习笔记—保姆版】第四章—关于Pandas、数据准备、数据处理、数据分析、数据可视化
第四章 欢迎访问我搞事情的[知乎账号]:Coffee 以及我的[B站漫威剪辑账号]:VideosMan 若我的笔记对你有帮助,请用小小的手指,点一个大大的赞哦. #编译器使用的是sypder,其中&q ...
- 设计模式学习笔记(C#版)
目录 系列文章目录 前言 1.设计模式的6大原则 1.开闭原则 2.单一职责原则 3.迪米特法则(最少知道原则) 4.里氏代换原则 5.依赖倒转原则 6.接口隔离原则 2.设计模式(创建型) 1.单例 ...
- 33万字!深度学习笔记在线版发布!
吴恩达老师的深度学习课程(deeplearning.ai),可以说是深度学习入门的最热门课程,我和志愿者编写了这门课的笔记,并在 github 开源,为满足手机阅读的需要,我将笔记做成了在线版,可以在 ...
- ipad分屏大小怎么调整_ipad软件应用推荐——学习笔记篇
文末有 苹果ipad软件 应用APP 下载方式 前言 随着新型iPad Pro和妙控键盘的发布. iPad从原来的娱乐游戏机逐渐地变成了生产力工具. 搭配Apple pencil和蓝牙键盘,瞬间变成 ...
- RT-Thread 软件定时器(学习笔记)
本文参考自[野火EmbedFire]<RT-Thread内核实现与应用开发实战--基于STM32>,仅作为个人学习笔记.更详细的内容和步骤请查看原文(可到野火资料下载中心下载) 文章目录 ...
- 【数据挖掘】心跳信号分类预测 之 特征工程 —— 学习笔记(三)
目录 三.特征工程 3.1 特征工程概述 3.2 学习目标 3.3 内容介绍 3.4 代码与理论学习 3.4.1 安装.导入依赖并读取数据 3.4.2 数据预处理 3.4.3 使用 tsfresh 进 ...
最新文章
- python进展_Python3的介绍和进展-“filter()”“time()”和decorator。,入门,与,进阶,filtertime,装饰,器...
- boost::type_erasure::tuple相关的测试程序
- tcp伪报头_TCP和UDP报头的比较
- Centos7 yum安装Python3.6环境,超简单
- 20145214 《Java程序设计》第3周学习总结
- C# list与数组互相转换
- mysql用户的权限分配
- python如何强制结束主线程_强制结束线程
- 请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符go时,第一个只出现一次的字符是g。当从该字符流中读出前六个字符“google时,,,(C++实现)
- objective-c中@autoreleasepool的用法
- 四种大数据分析方法 ,大数据学习入门必须掌握!
- 0910期即将上市:优秀产品三部曲
- python自动输入饥荒控制台代码
- 谷歌浏览器播放器声音
- Tapestry(二):Tapestry基本知识
- SELinux 宽容模式、 强制模式、关闭模式转换
- python中extend什么意思_python中extend是什么意思
- 如何修改QColorDialog窗口的背景颜色和上面的字体
- 约瑟夫环问题- leetcode 1823. 找出游戏的获胜者
- 【JAVA8】Set排序四种写法-倒序
热门文章
- org.springframework.mail.MailAuthenticationException: Authentication failed;解决方法
- 雷神笔记本官网_ ThundeRobot笔记本官方网站- ThundeRobot.com
- PyQt5界面与逻辑的分离
- 如何打开BAK文件?
- 第一章 概述与第一个程序
- C#调用C++DLL 使用 德卡 读卡器 获取身份证信息
- HTML表格鼠标滑过变色 和奇数偶数行变色+点击变色
- redis mysql 原子计数器_使用redis的increment()方法实现计数器功能案例
- 1/3倍频程与1/1倍频程作用(十四)
- 2021年土建方向-岗位技能(质量员)多少分及格及土建方向-岗位技能(质量员)模拟考试题库