Software architecture
功能需求和软件架构关系紧密,非功能需求是软件架构的选择结果(好的架构运行效率高之类的)。可以以表格或图的形式,比如UML图。
设计难以更改。敏捷开发的早期阶段就是设计系统架构。
好处:
- system analysis:利于分析系统是否满足非功能需求。
- large-scale reuse:架构可重用,更安全、更快。
- Stakeholder communication:可以 成为一个讨论点。
Project management
让项目能在有限的时间和预算范围内按预期保质落地。
软件工程比较灵活,没有标准的完全正确的方案,需要随机应变,敏锐的洞察力。
Project planning
最耗时的环节。
计划只有在项目完成时才算完成,因为计划在项目开发阶段也不断变化。
计划可能包括多种,如质量计划,员工开发计划……
Activity organisation
活动应该组织成切实的输出以及可以判断的进度。
milestone:标志阶段的结束,不一定是能输出给用户的成果。比如文档,或者逻辑阶段的结束。
Deliverables:可以交付给用户的设计,规格等成果。
Project scheduling
估计项目时间预算的花费,并按顺序排布。
比较难估计。通常根据过往项目经验估计。
先把大项目分解为几个小项目,然后并发的组织任务——尽可能充分利用劳动力 workforce,以及 Minimise task dependencies 尽量减少因为前面的任务没完成对后面任务的延误。
要对错误有预期,没错误是不可能的。约30%预料到的问题+20%未预料到的问题。
通常用图表表示:
任务表:
活动网:最长的是关键路径,因为这条路径任务要是延期了,整个工期都要延期。
甘特图:
Monitoring/Reporting:定期周会周报监督进度。
Metrics
代码行数,代码中的缺陷,测试用例通过情况。
People in the process
选择成员,管理,激励他们,解决大家遇到的技术和非技术问题。
- Consistency:对大家态度一致。
- Respect:大家拥有技能不同,互相尊重。
- Inclusion:让大家都参与进项目,聆听所有人的意见。
- Honesty:进度保持诚实。
Group working
不仅仅是个人的集合。团队能力的组成,凝聚力,沟通,大型团队的层次结构。
敏捷开发的团队合作方法:Scrum approcah,有一个Scrum master,日会,跟随进度,记录决策,和客户等沟通。
项目被分解为易于管理和理解的小部分。不稳定的需求不影响项目进度。团队成员对项目把控更到位 visibility,客户能常常看到项目进度,双方交流更相信彼此。
Risk management
失败是常有的事。我们要做好备选方案,推迟时间或者放弃目标。
风险管理是预测可能的失败,决定该在什么地方制定备选方案 alternative plans 。
Project risks:项目开发或者资源的问题。比如项目员工走了;原材料没法及时收到了;项目要求时间提前了。
Product risks:产品质量和性能的问题。比如开发软件出bug了,开发出的软件质量不行,用户对gui不满意,有没考虑到的错误。
Business risks:开发,采购组织的问题。比如上市了发现其他公司的技术,竞品比我们的好;负责该系统的组织因财务原因倒闭了。
Project+Product:需求一直变;分析阶段太慢了;开发团队技能能力不行;项目比预想的要大。
Risk Identification
识别风险。
Risk Analysis
评估风险的影响。
Risk Planning
制定应对风险的计划。
Risk Monitoring
在项目开发过程中监控风险。
Avoidance Strategies
尽量使用熟悉的模式,但是也接受新技术,时刻关注市场动向;
对员工合理培训,确保技术没被掌握在一个人手中;创造良好的工作环境;
员工不要欺瞒进度,高管要时刻了解进度以及其对项目的重要性。
Contingencies 突发事件
换人,换工具,换资源。
改变,舍弃一部分目标。
争取做的改动最小,但是损失也降到最小。
我们知道敏捷开发持续提交小版本,因此经常进行测试修改当阶段错误。但是缺陷在于敏捷开发不注重长远眼光 long term planning,因此可能带来一些问题。
Quality Management
对质量的评估,最好从软件开发过程中抽离出来。因为开发的过程会错误影响开发者自己对质量的评估(我都这么努力了,做出来的东西肯定很好吧)。
瀑布模型中在系统实现后单独测试软件质量。敏捷开发在每次发布新版本的时候测试。因为主要是在实际使用场合的测试,因此会比“为了达成测试而开发”的系统想的更多一些。
好的软件符合目的 Fitness for purpose,标准好,易于使用,高效,代码写的好(当然从用户角度来说他看不到这一层。但是代码写得好是前提,比如时间复杂度运行得快)。开发者需要考虑用户的需求前提下,自己用专业知识思维去想:这个需求实现合理吗。然后多喝用户沟通表达看法。
区分bug和特性:正常运行的是特性。有的时候哪怕系统除了意想不到的bug,但是功能意外的很合适,这也可以是特性。
特性很难移除,当用户开始依赖特性功能的时候,移除用户可能不乐意不习惯;而且向后(版本)兼容变得很难。
软件标准有很多好处,新员工快速入手,大家更熟悉项目等,但是标准可能对一些过去常常发生而现在不怎么出现的问题采取忽略的态度,从而兼容性上出现问题;而且可能浪费很多时间填文书。注重标准好的部分。
Design principle
设计的软件不仅要正确,高效运行,还要在限定时间,人力,软件,经济条件下。差代码后期可能要花很多时间弥补隐患;而且不同模块的代码经常交互,差代码还会有安全隐患。
我们仅仅想着怎么“正确”地编写代码是不够的,还要更省事省力地去开发。
Software lifespan:软件生命周期,软件是一直在持续开发的,比如新技术,客户新需求。
Software scale:项目大多数规格很大,很多人开发,代码多,多次修订。
Decomposition:项目分解为小模块开发。有Locality(实现一个模块可以不用检查其他模块的实现)和Modifiability(修改一个模块也不用考虑用使用这个模块的模块)的特点。
Specification and implementation:规范连接了设计和实现,规定了模块应该提供哪些服务以及如何使用服务。模块之间的交互应该仅仅限于规范,以此实现分解后模块的locality和modifiability。
Classes and Methods:面向对象思想中的模块为类。
Separation:分开考虑一个模块要实现的功能what和如何实现how。一个应用程序级别,一个实现级别。
Splitting Methods and Classes:设计方法和类的时候好好考虑如何拆分方法和类。类太大了考虑一下要不要拆,几个变量模式比较常用考虑一下要不要抽象成类。
Helper Methods and Classes:有一些方法和类只是为了抽象出来辅助拆分代码用的,这种一般是私有的helper method和嵌套的nested class,只在特定类里起作用,对其他类是private的。
Reuse:用以前的代码重复实现功能,节约代码,而且一改全改。重用代码可能厂商提供,编程语言提供,早期开发者开发。
Abstraction and generalisation:抽象是提取一些概念,比如接口,类;泛化是提取一些重复元素,以便代码重用,比如父类。
UML class diagrams:比代码更高的层次,是面向对象编程很重要的内容。
Static v. Dynamic:匹配静态内容和代码运行时产生的内容。对应UML中的类图 class diagram 和顺序图 sequence diagram。
图源:UML类图与顺序图_顺序图和类图的关系_lingchen336的博客-CSDN博客
class and objects:类和对象。对象是类的实例 instance。两者关系很像static 中的method和动态中的method call。
static:静态方法只能被类自己和其附加类调用。静态类实例化的对象是只有一个通用的对象,而不是实例化出无数个对象。
Variables and referencing:java中变量是对对象的引用,比如var2=var1不是赋值而是改变var2指向var1的引用。包括一个变量的自带方法的
Scope and garbage collection:对象的作用域只要有变量还在引用这个对象,他就活着。没有人引用的时候就自动垃圾回收。
基本设计原则:设计对象指代目标;对象方法指代其能做的操作;Locality和Modifiability;对象只能通过方法调用交互。
Client-Contractor model:一种思想,一个对象调用其他对象的方法得到返回值来使用它,像客户和承包商一样。现代社会大进步的原因就是大家各专其职,因此项目中各个类各专其职也能在同样的量的前提下发挥更多的作用。
-
客户给承包商的合同就类似定义类和方法的规范specification。代码编写者有义务Obligations拿了钱benefits,就确保软件正确执行,不做破坏的事等。
-
Design by Contract:一种思想。客户端代码应当满足承包商的先决需求pre-conditions;应当对异常做处理(exception);后置条件post-conditions在测试中作为断言assertions合并到程序中。(前置条件:前提;后置条件:方法运行后的状态)
Single Responsibility Principle (SRP)
单一责任原则:每个类有一个职责 responsibility,其所有对象都服务于这个职责。Leads to highly cohesive 内聚性高
Open-Closed Principle (OCP)
开放闭合原则:模块(类和方法)open to extension,close to modification 可以对外扩展,但是不能让外界修改。比如一个类以不同对象形式表现这就是扩展,因为需求改变或者有新需求。
Do not Repeat Yourself principle (DRY)
不要复读机原则(不要重复自己原则)。本模块中如果发现重复内容,建议再抽象为方法和类。
**Liskov Substitution Principle (LSP) **
Liskov替代原理:重写方法,不应该抱着”修改其方法“的想法。不能加强 strengthen 前置条件,不能削弱 weaken 后置条件。
比如父类people方法AddPeople(String name, int age) 子类Student构造方法AddPeople(String name, int age)里先校验一下学生年龄,如果年龄大于25岁则不允许add,这就修改了原来方法。这就是前置条件加强了,要求年龄的限制多了。
后置条件比如原来返回值大于0,现在返回值可以小于0了,那么返回值限制弱了,后置条件削弱了,不满足里氏原则。
参考:设计模式六大原则(二)----里式替换原则 - 盛开的太阳 - 博客园 (cnblogs.com)
Association, Aggregation and Composition:association是两个不同东西的对应,比如家长和孩子。另外两个是子集,aggregation是彼此可以独立存在,比如班级和学生。composition是可以彼此独立存在,比如house和room。
有的时候继承满足不了LSP(比如令正方形为矩形的子类),可以使用聚类来防止破坏LSP。
Interface-Segregation Principle (ISP)
接口隔离原则:首先接口应该尽量分解为小接口。客户端每个类不应该依赖他不使用的方法。就是接口用啥实现啥,别多实现。
Dependency-Inversion Principle (DIP)
依赖倒置原则:
如果高层模块直接调用低层模块提供的服务,那么就是具体耦合关系,这样高层模块依赖于低层模块就不可避免。但是,如果我们使用抽象耦合关系,在高层模块和低层模块之间定义一个抽象接口,高层模块调用抽象接口定义的方法,低层模块实现该接口。这样,就消除了高层模块和低层模块之间的直接依赖关系。现在,高层模块就不依赖于低层模块了,二者都依赖于抽象。同时也实现了“抽象不应该依赖于细节,细节应该依赖于抽象”。
面向对象基础设计原则:4.依赖倒转原则 - 知乎 (zhihu.com)
依赖倒置原则(Dependency Inversion Principle) - sangmado - 博客园 (cnblogs.com)