首先谈谈我对软件工程的总结,软件工程是将系统的、科学的和严密的方法应用于设计、开发、运行和维护软件,以及对这些方法本身的研究,也就是将工程应用于软件,它由方法、工具和过程三部分组成。软件工程的框架可以概括为:目标、过程和原则。(1)目标:生产具有正确性、可用性以及开销合宜的产品。正确性指软件产品达到预期功能的程度。可用性指软件基本结构、实现及文档为用户可用的程度。开销合宜是指软件开发、运行的整个开销满足用户要求的程度。这些目标的实现不论在理论上还是在实践中均存在很多待解决的问题,它们形成了对过程、过程模型及工程方法选取的约束。(2)过程:生产一个最终能满足需求且达到工程目标的软件产品所需要的步骤。软件工程过程主要包括开发过程、运作过程、维护过程。它们覆盖了需求、设计、实现、确认以及维护等活动。需求活动包括问题分析和需求分析。问题分析获取需求定义,又称软件需求规约。需求分析生成功能规约。设计活动一般包括概要设计和详细设计。概要设计建立整个软件系统结构,包括子系统、模块以及相关层次的说明、每一模块的接口定义。详细设计产生程序员可用的模块说明,包括每一模块中数据结构说明及加工描述。实现活动把设计结果转换为可执行的程序代码。确认活动贯穿于整个开发过程,实现完成后的确认,保证最终产品满足用户的要求。维护活动包括使用过程中的扩充、修改与完善。伴随以上过程,还有管理过程、支持过程、培训过程等。(3)原则是指围绕工程设计、工程支持以及工程管理在软件开发过程中必须遵循的原则。
SA21225108
以下是课程总结:
代码风格规范总结
- 缩进:4个空格;
- 行宽:< 100个字符;
- 代码行内要适当多留空格,如“=”、“+=” “>=”、“<=”、“+”、“*”、“%”、“&&”、“||”、“<<”,“^”等二元操作符的前后应当加空格。对于表达式比较长的for语句和if语句,为了紧凑起见可以适当地去掉一些空格,如for (i=0; i<10; i++)和if ((a<=b) && (c<=d));
- 在一个函数体内,逻揖上密切相关的语句之间不加空行,逻辑上不相关的代码块之间要适当留有空行以示区隔;
- 在复杂的表达式中要用括号来清楚的表示逻辑优先级;
- 花括号:所有 ‘{’ 和 ‘}’ 应独占一行且成对对齐;
- 不要把多条语句和多个变量的定义放在同一行;
- 注释和版权信息:注释也要使用英文,不要使用中文或特殊字符,要保持源代码是ASCII字符格式文件;
- 不要解释程序是如何工作的,要解释程序做什么,为什么这么做,以及特别需要注意的地方;
- 每个源文件头部应该有版权、作者、版本、描述等相关信息。
编写高质量代码的基本方法
通过控制结构简化代码
通过合理的控制结构简化之后的代码
代码的基本结构分为
顺序执行
条件分支
循环结构
还有很多语言中支持的递归结构
通过数据结构简化代码
如果我们从需求挖掘和需求分析中发现业务层面的操作规律或者有向用户学习的积极心态,很可能我们会找出一个如下的数据结构,那么计税将变得非常简单,实际上财会人员一般人工计税时也会使用类似的数据结构表格来速算税金
性能优先策略背后隐藏的代价
- cost to write the code faster。当软件工程师的人力成本远大于所消耗的计算资源成本时,提高代码编写的工作效率将更有价值;
- cost to test the code。质量保证的人力成本和质量保证的成效也比所消耗的计算资源成本更有价值;
- cost to understand the code。性能优先的策略往往会让代码很难理解,结果需要消耗更多的工时;
- cost to modify the code。面向机器的代码修改起来更困难,可扩展性差,同样会消耗更多工时。
拒绝修修补补要不断重构代码
耦合度(Coupling)
- 耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。
一般在软件设计中我们追求松散耦合。
内聚度(Cohesion)
- 内聚度是指一个软件模块内部各种元素之间互相依赖的紧密程度。
- 理想的内聚是功能内聚,也就是一个软件模块只做一件事,只完成一个主要功能点或者一个软件特性(Feather)。
-
软件设计中的一些基本方法
- KISS(Keep It Simple & Stupid)原则
- 使用本地化外部接口来提高代码的适应能力
- 先写伪代码的代码结构更好一些
- using design to frame the code(matching design with implementation)
KISS(Keep It Simple & Stupid)原则
- 一行代码只做一件事
- 一个块代码只做一件事
- 一个函数只做一件事
- 一个软件模块只做一件事
使用本地化外部接口来提高代码的适应能力
Consumer Reuse
- 消费者重用是指软件开发者在项目中重用已有的一些软件模块代码,以加快项目工作进度。软件开发者在重用已有的软件模块代码时一般会重点考虑如下四个关键因素:
- 该软件模块是否能满足项目所要求的功能;
- 采用该软件模块代码是否比从头构建一个需要更少的工作量,包括构建软件模块和集成软件模块等相关的工作;
- 该软件模块是否有完善的文档说明;
该软件模块是否有完整的测试及修订记录
Producer Reuse
- 我们清楚了消费者重用时考虑的因素,那么生产者在进行可重用软件设计时需要重点考虑的因素也就清楚了,但是除此之外还有一些事项在进行可重用软件设计时牢记在心,我们简要列举如下:
- 通用的模块才有更多重用的机会;
- 给软件模块设计通用的接口,并对接口进行清晰完善的定义描述;
- 记录下发现的缺陷及修订缺陷的情况;
- 使用清晰一致的命名规则;
- 对用到的数据结构和算法要给出清晰的文档描述;
- 与外部的参数传递及错误处理部分要单独存放易于修改;
接口要素
- 接口规格是软件系统的开发者正确使用一个软件模块需要知道的所有信息,那么这个软件模块的接口规格定义就必须清晰明确地说明正确使用本软件模块的信息。一般来说,接口规格包含五个基本要素:
- 接口的目的;
- 接口使用前所需要满足的条件,一般称为前置条件或假定条件;
- 使用接口的双方遵守的协议规范;
- 接口使用之后的效果,一般称为后置条件;
接口所隐含的质量属性
微服务接口一般使用RESTful API来定义接口。
- 由一系列独立的微服务共同组成软件系统的一种架构模式;
- 每个微服务单独部署,跑在自己的进程中,也就是说每个微服务可以有一个自己独立的运行环境和软件堆栈;
- 每个微服务为独立的业务功能开发,一般每个微服务应分解到最小可变产品(MVP),达到功能内聚的理想状态。微服务一般通过RESTful API接口方式进行封装;
- 系统中的各微服务是分布式管理的,各微服务之间非常强调隔离性,互相之间无耦合或者极为松散的耦合,系统通过前端应用或API网关来聚合各微服务完成整体系统的业务功能。
微服务架构的基本概念可以简单概括为通过模块化的思想垂直划分业务功能
RESTful API
- REST即REpresentational State Transfer的缩写,可以翻译为”表现层状态转化”。有表现层就有背后的信息实体,信息实体就是URI代表的资源,也可以是一种服务,状态转化就是通过HTTP协议里定义的四个表示操作方式的动词:GET、POST、PUT、DELETE,分别对应四种基本操作:
- GET用来获取资源;
- POST用来新建资源(也可以用于更新资源);
- PUT用来更新资源;
DELETE用来删除资源
接口与耦合度之间的关系
- 对于软件模块之间的耦合度,前文中提到,耦合度是指软件模块之间的依赖程度,一般可以分为紧密耦合(Tightly Coupled)、松散耦合(Loosely Coupled)和无耦合(Uncoupled)。一般在软件设计中我们追求松散耦合。
- 更细致地对耦合度进一步划分的话,耦合度依次递增可以分为无耦合、数据耦合、标记耦合、控制耦合、公共耦合和内容耦合。这些耦合度划分的依据就是接口的定义方式,我们接下来重点分析一下公共耦合、数据耦合和标记耦合。
- 公共耦合
- 当软件模块之间共享数据区或变量名的软件模块之间即是公共耦合,显然两个软件模块之间的接口定义不是通过显式的调用方式,而是隐式的共享了共享了数据区或变量名。
- 数据耦合
- 在软件模块之间仅通过显式的调用传递基本数据类型即为数据耦合。
- 标记耦合
- 在软件模块之间仅通过显式的调用传递复杂的数据结构(结构化数据)即为标记耦合,这时数据的结构成为调用双方软件模块隐含的规格约定,因此耦合度要比数据耦合高。但相比公共耦合没有经过显式的调用传递数据的方式耦合度要低。
可重入函数
- 可重入(reentrant)函数可以由多于一个任务并发使用,而不必担心数据错误。相反,不可重入(non-reentrant)函数不能由超过一个任务所共享,除非能确保函数的互斥(或者使用信号量,或者在代码的关键部分禁用中断)。可重入函数可以在任意时刻被中断,稍后再继续运行,不会丢失数据。可重入函数要么使用局部变量,要么在使用全局变量时保护自己的数据。
可重入函数的基本要求
- 不为连续的调用持有静态数据;
- 不返回指向静态数据的指针;
- 所有数据都由函数的调用者提供;
- 使用局部变量,或者通过制作全局数据的局部变量拷贝来保护全局数据;
- 使用静态数据或全局变量时做周密的并行时序分析,通过临界区互斥避免临界区冲突;
- 绝不调用任何不可重入函数。
- 什么是线程安全
- 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
- 线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行读写操作,一般都需要考虑线程同步,否则就可能影响线程安全。
- 函数的可重入性与线程安全之间的关系
- 可重入的函数不一定是线程安全的,可能是线程安全的也可能不是线程安全的;可重入的函数在多个线程中并发使用时是线程安全的,但不同的可重入函数(共享全局变量及静态变量)在多个线程中并发使用时会有线程安全问题;
- 不可重入的函数一定不是线程安全的。
-
正则表达式
- . 任意字符
- + 一次或多次
- * 零次或多次
- ? 可能存在前一个元素或使用最短匹配
- [] 匹配组
- - 匹配的字符范围
- ^ 不相匹配的字符或字符串开头
- $ 字符串结尾
- \W [^A-Za-z0-9_]
- \w [A-Za-z0-9_]
- \D [^0-9]
- \d [0-9]
- () 捕获组
需求类型
- 功能性需求:根据需要的活动描述需要的行为
- 质量需求或非功能需求:描述软件必须具备的一些质量特征
- 设计约束:设计决策,如平台或接口组件的选择
- 过程约束:对可用于构建系统的技术或资源的限制
高质量需求
- 可测试性
- 处理冲突
- 特点:准确的,一致的、无二义性的、完整的、可行的、无与主要目标不相关的需求、可测试的、可追踪的
需求分析的两种基本方法
原型化方法、建模
用例
用例的基本要素
- 是一个业务过程
- 由某个参与者触发开始
- 显式或隐式地终止于某个参与者
- 为某个参与者完成了有用的业务工作
面向对象分析
- 对象和属性(类和对象的UML图)
- 继承关系(空心箭头指向父类)
- 聚合关系(菱形箭头指向整体类)
- 关联关系(实线)
业务领域建模
- 收集信息
- 头脑风暴
- 概念分类及关系
- 画出UML图
敏捷统一过程
敏捷统一过程进一步将软件过程中每一次迭代过程划分为计划阶段和增量阶段。
4个关键步骤
- 确定需求
- 通过用例满足需求
- 将用例分配到各增量阶段
- 完成各增量阶段的任务
增量阶段的5个步骤
- 用例建模
- 业务领域建模
- 对象交互建模
- 形成设计类图
- 软件的编码实现和软件应用部署
统一过程
统一过程的核心要义是用例驱动(以用例为开发目标)、以架构为中心(保持架构稳定,减少重构)、增量且迭代的过程
涉及UML图
用例图、序列图(顺序图)、类图
设计模式
优点:
- 可以提高程序员的思维能力、编程能力和设计能力。
- 使程序设计更加标准化、代码编制更加工程化,使软件开发效率大大提高,从而缩短软件的开发周期。
- 使设计的代码可重用性高、可读性强、可靠性高、灵活性好、可维护性强
分类:
- 类模式、对象模式
- 创建型模式、结构型模式、行为型模式
常用的设计模式
- 单例模式:只有一个实例
- 原型模式:新实例均为某实例的拷贝
- 建造者模式:根据需求添加功能模块最终形成复杂对象
- 代理模式:使某对象对外提供指定/统一的接口
- 适配器模式:连接两个不兼容接口
- 装饰模式:不改变对象结构,使用对象组合方式扩展其功能
- 外观模式(门面模式):为系统各功能提供统一的对外接口集合
- 享元模式:提取相同的部分以复用,节省空间
- 策略模式:某些函数可替换
- 命令模式:将命令封装为对象并调用
- 模板方法模式:继承重载重定义某些函数
- 责任链模式:请求沿处理对象链传递直到被处理
- 中介者模式:使用中介者对象协调其他所有对象间的交互
- 观察者模式:某个观察者发生变化时通知其他观察者
设计原则
- 开闭原则:对扩展开放,对修改关闭
- 里氏(Liskov)替换原则:子类可扩展不可重写父类
- 依赖倒置原则:面向接口编程,调用使用抽象或接口而非具体类
- 单一职责原则:一个类只负责一个职责
- 迪米特法则:最少知道原则,对其他类了解越少越好
- 合成复用原则:复用时优先考虑组合、聚合,其次才是遵循里氏替换原则的继承
常见软件架构
- 三层架构
- MVC(模型-视图-控制器)架构:包容变化,三部分分离提高灵活性
- MVVM(C->ViewModel)架构:通过拦截绑定监听器自动更新视图
没有银弹
软件工程中不存在银弹——没有任何一项技术或方法可使软件工程的生产力在十年内提高十倍。软件工程之所以不存在银弹是因为,软件工程本身存在复杂性,一致性,不可预见性,不可视化性。
软件危机的根本问题
软件概念结构的复杂性,无法达成软件概念的完整性和一致性,自然无法从根本上解决软件危机带来的困境。
软件的生命周期
- 分析:需求分析和定义
- 设计:软件架构设计、软件详细设计
- 实现:编码和测试(单元、集成、系统测试)
- 交付:部署、交付、培训
- 维护
瀑布模型
几乎不会发生需求变化/变更,无任何迭代
原型化瀑布模型:瀑布模型增加原型化阶段,即先做基本雏形,将风险前移,增加可控性
原型分为用户接口原型和软件架构原型
V模型
将瀑布模型中的测试与开发结合,改善开发效率
分阶段的交付开发策略
- 增量开发:部分->整体,扩展功能
- 迭代开发:整体->整体,升级功能
分阶段开发优点
- 在开发完成之前即可进行交付和用户培训
- 可以让开发者及时处理意外情况
- 开发团队在不同版本关注不同功能,提高效率
- 提前抢占市场
生死相依原则:特定过程活动和评估该过程的过程活动成对出现
螺旋模型
基本策略:在每个迭代阶段构建原型以减小风险
每个循环重复计划,确定目标、替代方案和约束条件,评估替代方案和风险,开发和测试
团队的基本要素
建设高效团队
- 建设具有凝聚力的团队
- 设定有挑战性的目标
- 反馈
- 共同遵守的工作流程和框架
基本策略
- 计划先行,也就是做出承诺之前先计划
- 完成概念设计
- 选择开发策略
- 完成初步规模估算
- 完成初步时间估算
- 评估风险
- 建立策略文档
- 制定配置管理计划
参考资料:代码中的软件工程 https://gitee.com/mengning997/se
SA21225108