前言
第四单元作业的主题是图书管理系统的构建,主要学习内容涉及到程序架构设计以及UML
图的阅读与绘制。第一次作业主要实现基本的图书管理系统,服务单元主要是书架、借阅处、预约处,仅含有正式书籍,顾客可以获得borrow
、return
、query
、order
和pick
等服务,同时绘制程序的UML
类图;第二次作业增加了图书漂流角服务单元(CornerOffice
漂流角类),含有正式与非正式书籍(非正式书籍可以转正),顾客可以获得donate
、renew
等服务,同时绘制程序的UML
类图以及书籍借阅流程的UML
状态图;第三次作业增加了信誉积分,顾客可以查询自身信誉积分,借书还书的过程中各种情况会导致客户信誉积分变化,同时绘制程序的UML
类图以及书籍预约获取流程的UML
顺序图。第四单元并未明确规定代码的架构以及书籍的移动策略,自由度较高,主要练习了程序代码架构高内聚低耦合以及逻辑合理清晰的设计。各种UML
图的绘制较为繁琐,反而是本单元中耗时最长的部分。本篇总结主要是记录一下三次作业的具体实现、各种优缺点分析,以及自身一点心得体会。
一、作业架构分析
Homework1
题目重点重述
第一次作业主要是实现简单的图书管理系统,包含borrow
、return
、query
、order
和pick
等顾客操作,以及图书馆在开馆闭馆时自行进行的调整操作。图书馆分区可分为三区,书架区、借阅处、预约处。当顾客进行借阅操作时,携带书籍前往借阅处,借阅处审批顾客是否可以借阅,若可以,则借与顾客,如不可以,则存入借阅处。归还书籍时同理,归还的书籍存入借阅处。当顾客进行查询操作时,书架区需要返回特定书籍编号的库存数量。当顾客进行预约操作时,预约处审批顾客是否可以预约,并在停止营业后进行书籍调整。当顾客进行取预约书籍操作时,预约处需要查询是否有预约信息,若存在,则进行借出。开馆闭馆停止营业时可以进行书籍在图书馆内调整操作,在三个分区转运书籍。书籍分为ABC三类,其中,A类书籍不可以借阅或预约(不可以移动),B类书籍同时只能拥有一本,C类书籍同一书号同时只能拥有一本。
程序UML图
本次作业中使用RequestDealer
类读入并处理需求LibraryRequest
,主要功能实现类是Library
类,主要交互是在Library
和Customer
类之间进行。在Library
中存入客户名单,以及Shelf
、BorrowOffice
、AppointOffice
三个服务分区,在Library
类中进行上层的统一操作。同时设计了BookBuffer
类统一存储当前对象的书籍,并配有查询特定书籍数量、是否拥有特定书籍和增删书籍的基本操作。对于预约处的处理,使用预约信息AppointInf
作为最小操作单元,每条预约信息均有bookId
、customer
、date
等属性,以判断预约信息的有效性。
复杂度
类复杂度表格如图所示:其中OCavg
指代平均操作复杂度,OCmax
指代最大操作复杂度,WMC
指代加权方法复杂度
Class | OCavg | OCmax | WMC |
---|---|---|---|
AppointInf | 1 | 1 | 5 |
AppointOffice | 2 | 4 | 14 |
BookBuffer | 1.88 | 3 | 15 |
BorrowOffice | 1 | 1 | 3 |
Customer | 1.6 | 4 | 8 |
Library | 2.1 | 5 | 21 |
Main | 1 | 1 | 1 |
RequestDealer | 2.8 | 6 | 14 |
Shelf | 1 | 1 | 6 |
可以观察到,本次作业设计架构还是较为合理的,整体复杂度水平较低。复杂度最高的是RequestDeal
和Library
类。这两个类中含有多种方法,且存在多项的分支选择,因此复杂度较高。同时,由于各项客户操作以及书籍转运主要在Library
中实现,因此会出现Library
复杂度远超其他类的现象。Customer
、Shelf
、BorrowOffice
中方法均是对bookBuffer
操作,主体部分在BookBuffer
类中实现,因此复杂度较低。
重构
在重新思考后,发现不将书籍副本实例化为具体对象,将会遗失很多信息(将书本实例化后会更好存储),同时会造成三种Office
较难抽象,因此选择进行重构。
Class | OCavg | OCmax | WMC |
---|---|---|---|
AppointInf | 1 | 1 | 4 |
AppointOffice | 1 | 1 | 4 |
Book | 1 | 1 | 6 |
BookBuffer | 2.64 | 6 | 29 |
BorrowOffice | 1 | 1 | 2 |
Customer | 1.5 | 4 | 9 |
Library | 1.9 | 4 | 19 |
Main | 1 | 1 | 1 |
Office | 1 | 1 | 6 |
RequestDealer | 2.8 | 6 | 14 |
ShelfOffice | 1 | 1 | 4 |
可以观察到,类复杂度略有上升。但是由于Book
类中可以存储是否预约以及预约信息,因此可以将图书馆中appointOffice
、borrowOffice
、shelfOffice
抽象出一个Office
的父类,使得代码复用性更高,操作也更加顺遂。
Homework2
题目重点重述
本次作业在第一次作业基础上,新增了客户捐献、图书漂流角、正式书籍续借等功能。首先定义了正式书籍与非正式书籍,正式书籍可从初始化配置(存储于书架中)以及非正式书籍升级获得(非正式书籍被借阅两次,需要升级为正式书籍),仅存在于书架、预约处、借阅处、客户;非正式书籍可从捐献获得(存储于图书漂流角中),仅存在于图书漂流角、借阅处、客户。书籍的转移路径为书架到借阅处(正式书籍借阅失败)、书架到客户(正式书籍借阅成功)、书架到预约处(正式书籍预约整理)、借阅处到书架(正式书籍返还及非正式书籍升级)、借阅处到图书漂流角(非正式书籍返还)、预约处到书架(预约书籍超时未取返还)、预约处到客户(预约书籍取走)、图书漂流角到借阅处(非正式书籍借阅失败)、图书漂流角到客户(非正式书籍借阅成功)、客户到借阅处(客户归还借阅书籍)。在客户借阅到期的前五天内,通过图书馆预约信息判定后,客户可以续借30天。
程序UML图
相比于第一次作业,本次作业整体架构并无较大变化。可以观察到,经过重构抽象后,非正式书籍相关操作实现较为简单。如图书漂流角可直接继承Office
类,其全部功能均在父类中已经实现,因此可以直接使用。本次作业中,最大改变是将图书馆系统的操作全部提取出来建立Service
接口,实例化各种服务类。在System
类中建立Service[]
数组储存各种服务,在系统使用各项功能时调用服务类即可。
本次作业还要求制作书籍的UML
状态图。本次画制较为简单,仅做出了初始状态InitState
、在图书馆中状态inLibrary
、在客户手中状态inCustomer
。三种状态的转换方式如图所示,不再过多赘述。
复杂度
类复杂度表格如图所示:其中OCavg
指代平均操作复杂度,OCmax
指代最大操作复杂度,WMC
指代加权方法复杂度
Class | OCavg | OCmax | WMC |
---|---|---|---|
Main | 1 | 1 | 1 |
books.Book | 1.73 | 9 | 19 |
books.BookBuffer | 2.73 | 4 | 41 |
informations.Inf | 1 | 1 | 8 |
libraries.Library | 1.87 | 3 | 28 |
libraries.System | 3.17 | 9 | 19 |
offices.AppointOffice | 1.4 | 3 | 7 |
offices.BorrowOffice | 1 | 1 | 4 |
offices.CornerOffice | 1 | 1 | 2 |
offices.Office | 1 | 1 | 9 |
offices.ShelfOffice | 1 | 1 | 2 |
services.BorrowService | 2 | 3 | 4 |
services.ContinueService | 1.5 | 2 | 3 |
services.DonateService | 1 | 1 | 2 |
services.OrderService | 2 | 3 | 4 |
services.PickService | 2 | 3 | 4 |
services.QueryService | 1.5 | 2 | 3 |
services.ReturnService | 1.5 | 2 | 3 |
users.Customer | 1.67 | 5 | 10 |
可以观察到,在增加功能后,本次作业复杂度有较高提升,其中System
、BookBuffer
类复杂度最高。对于System
类,其中的run()
方法进行command
轮询,requestDeal()
方法有较多选项的switch
语句,选择不同的服务进行调用,因此整体复杂度较高。其余类复杂度正常,得益于程序的模块化分离。
Homework3
题目重点重述
本次作业在第二次作业的基础上,新增了用户的信誉积分功能。用户初始分数为10分,最高为20分,最低无限制。当用户出现按时归还借阅书籍、捐赠书籍、捐赠书籍转正时获得相应加分;出现逾期还书、预约书籍逾期未取等情况时减去相应减分。当用户的信誉积分小于0时,将限制其借阅、预约、续借等服务。同时新增用户信誉积分查询功能,查询单个客户的信誉积分。
程序UML图
UML类图与上一次作业变化不大,主要新增了CreditService
服务类。在Book
类中新增state
属性,表示书籍当前状态,主要是NONE
(代表处于图书馆内,没有被预约借阅)、APPOINTED
(代表处于预约处,被某位客户预约)、BORROWED
(代表被某位客户借阅,处于客户手中),通过新增方法changeState()
变换状态。这种改变既可以去除isAppointed
、isBorrowed
两个属性,整体更加抽象简洁,也可以在未来增加更多书籍状态时更好的拓展。
本次作业还要求制作书籍的UML
顺序图,主要模拟客户预定并获取书籍收发消息的流程。本次画图较为简略,仅制出了两个lifeline
作为收发消息的对象,分别是:System
和:Customer
(:
后代表类)。当客户发出预约请求时,系统向客户发送消息orderNewBook()
表示已接收到请求,并进行各项操作,将书籍变为可获取的状态;客户获取到书籍后,向系统发送消息getOrderedBook()
表示已获得书籍。整体流程较为简单,是一个反馈机制。
复杂度
类复杂度表格如图所示:其中OCavg
指代平均操作复杂度,OCmax
指代最大操作复杂度,WMC
指代加权方法复杂度
Class | OCavg | OCmax | WMC |
---|---|---|---|
Main | 1 | 1 | 1 |
books.Book | 1.82 | 10 | 20 |
books.BookBuffer | 2.73 | 4 | 41 |
informations.Inf | 1 | 1 | 8 |
libraries.Library | 1.87 | 3 | 28 |
libraries.System | 2.88 | 9 | 23 |
offices.AppointOffice | 2.33 | 7 | 14 |
offices.BorrowOffice | 1 | 1 | 4 |
offices.CornerOffice | 1 | 1 | 2 |
offices.Office | 1 | 1 | 9 |
offices.ShelfOffice | 1 | 1 | 2 |
services.BorrowService | 2 | 3 | 4 |
services.ContinueService | 1.5 | 2 | 3 |
services.CreditService | 1 | 1 | 2 |
services.DonateService | 1 | 1 | 2 |
services.OrderService | 2 | 3 | 4 |
services.PickService | 2 | 3 | 4 |
services.QueryService | 1.5 | 2 | 3 |
services.ReturnService | 1.5 | 2 | 3 |
users.Customer | 2 | 5 | 20 |
可以观察到,相比于第二次作业,本次作业复杂度无显著变化。本次作业改动较少,并未产生过多新方法,同时新增部分中基本没有多项判断和循环,因此并未对复杂度造成太大影响。
二、架构重点实现分析
Homework1
第一次作业程序中主要有三个实现的重点:
-
借阅流程的实现: 当顾客进行借阅操作时,首先应该判断书籍是否可以移动,若可以移动则客户携带书籍前往借阅处,并进行进一步审批。借阅处审批顾客是否可以借阅,若可以借阅,则该书籍从
shelf
删除,并加入客户的books
中;如不可以借阅,则该书籍从shelf
删除,并加入借阅处的books
中。借阅处的操作较为简单,关键在于对客户借阅请求的审批。设计中将该判断分为两部分,分别是书籍是否可以移动以及顾客是否可以借阅。书籍是否移动主要判断shelf
中是否有对应剩余的副本,顾客是否可以借阅主要判断customer
现在拥有的书籍和借阅请求是否矛盾。//判断书籍是否可移动(有余本且不是A类书籍),在Shelf类中进行 public boolean canMove(LibraryBookId bookId) { return books.haveBook(bookId) && !bookId.isTypeA(); } //判断顾客是否可以借阅或预约书籍,在Customer类中进行 public boolean canBorrowOrAppoint(LibraryBookId bookId) { if (bookId.isTypeB()) { return !books.haveBTypeBooks(); } else if (bookId.isTypeC()) { return !books.haveBook(bookId); } else { return false; } }
-
预约流程的实现: 预约流程主要涉及预约操作、取书操作以及停止营业时书籍的内部调整。设计时为预约处设计了两个属性
requests
和appoints
。requests
主要存储审批通过的预约请求,appoints
主要存储经过内部调整后运送至预约处的书籍的预约信息。在预约环节,仅审核客户的预约资格,如上所示,所有拥有预约资格的请求都应加入请求池中,等待处理;在转运环节,遍历requests
,判断书籍是否可以移动以及当前该客户是否可以预约借阅(当客户在预约后进行其他操作并失去拥有书籍的资格时,如此可以保证当其再获得拥有书籍的资格时,预约请求仍能生效,提高客户picked
的成功率),对于书籍可移动且当前该客户可预约借阅的请求,转运书籍,并以当前转运的bookId
、customer
、date
创建新的预约信息,存入appoints
中,等待客户取书。当客户取书时,遍历appoints
找到为其准备的书籍并删除对应的预约信息。 -
停止营业阶段的书籍转运: 书籍转运主要有两部分组成,即借阅处与书架区、预约处与书架区的转运。请注意,转运操作无强制逻辑要求,可以自行设计转运方案。但最佳方案应有转运至书架区的操作在转运至预约处的操作前进行,以提高对应书籍转运的成功率以及客户获得书籍的成功率。
- 借阅处与书架区间转运:主要是用户借阅失败以及归还的书籍返回书架区,操作较为简单,将借阅处的
books
清空,并将返回的集合转运添加至书架区的books
中即可。该部分操作放在闭馆时进行,这样的好处是每天借阅处的书籍都能及时返回书架区。 - 预约处与书架区间转运:主要是用户未取走因到时间而失效的预约书籍返回书架区以及因预约从书架区转运至预约处的书籍。对于预约处返回书架区的书籍,遍历
appoints
,根据预约信息的date
,借助LocalDate
类提供的isBefore()
方法,判断超时需要返回书架区的书籍。对于因预约从书架区转运至预约处的书籍,遍历requests
,判断当前需求对应书籍是否可以移动以及客户是否具备取书资质,符合的进行转运。该部分操作放在开馆时进行,这样的好处是当出现进行预约后数天不开馆的情况时,能空除这段时间延长预约有效周期,提高客户picked
的成功率。
- 借阅处与书架区间转运:主要是用户借阅失败以及归还的书籍返回书架区,操作较为简单,将借阅处的
Homework2
第二次作业程序中主要有三个实现重点:
-
时间判断: 本次作业中,续借请求审核、还书是否超期等判断均需要进行时间判断。在程序中,主要借助于
LocalDate
类存储操作的时间日期,在时间判断中,主要借助该类中的plusDays()
方法来获得截止日期,借助该类中的isBefore()
、isAfter()
方法来比较日期大小(需注意isBefore()
、isAfter()
方法不包含当天,即[2024-10-01].isBefore([2024-10-01])
是不成立的)。在Inf
类中,实现了不在操作时限内方法overOperationLimit()
和不在续借时限内方法overContinueLimit()
,如下所示。//不在操作时限内,主要用于归还是否超时判定和预约是否超时判定 public boolean overOperationLimit(LocalDate now) { //从opDate后一天开始计算,limit天后无效(不包含opDate+limit当天) return opDate.plusDays(limit).isBefore(now); } //不在续借时限内,主要用于是否处于续借时间判定 public boolean overContinueLimit(LocalDate now) { //从opDate后一天开始计算,limit-4天内无效(不包含opDate+limit-4当天) //从opDate后一天开始计算,limit天后无效(不包含opDate+limit当天) return opDate.plusDays(limit - 4).isAfter(now) || opDate.plusDays(limit).isBefore(now); }
-
正式书籍与非正式书籍: 本次作业中,建立了正式书籍和非正式书籍两种书籍类型,具有不同操作,存在于不同位置。同时在借阅处归还后进行书籍返还整理时,应对非正式书籍进行升级,转变为正式书籍,仅改变书籍的类型,不改变书籍的序号。在官方包中,给出了
LibraryBookId
的toFormal()
方法。在该方法中,非正式书籍转为正式书籍时,会返回一个新的LibraryBookId
对象。这时候会产生两本原本书籍类型序号均相同的非正式书籍(使用同一个LibraryBookId
对象)经过转换后不相同(类型序号均相等,但使用两个不同的LibraryBookId
对象)的问题,会对后续书籍存储、书籍类型判断产生影响,因此需要重写LibraryBookId
类的equals()
、hashCode()
方法。//bookId的toFormal()方法,会产生新对象 public LibraryBookId toFormal() { switch (this.type) { case AU: return new LibraryBookId(LibraryBookId.Type.A, this.uid); case BU: return new LibraryBookId(LibraryBookId.Type.B, this.uid); case CU: return new LibraryBookId(LibraryBookId.Type.C, this.uid); default: return this; } } //重写后的equals()方法 public boolean equals(Object var1) { if (this == var1) { return true; } else if (var1 != null && this.getClass() == var1.getClass()) { LibraryBookId var2 = (LibraryBookId)var1; return this.type == var2.type && Objects.equals(this.uid, var2.uid); } else { return false; } } //重写后的hashCode()方法 public int hashCode() { return Objects.hash(new Object[]{this.type, this.uid}); }
-
Service
接口的实现: 本次作业中,实现了Service
的接口,实例化出各种服务,并将其储存在System
类中进行调用。Service
接口主要实现work()
方法,每个实例化的Service
内含library
属性,并在work()
方法中使用。
Homework3
第三次作业程序中主要有两个实现重点:
- 借阅超期的扣分机制: 用户每次成功获得书籍,在该书应归还日期的当日闭馆后,若用户仍未归还图书,该用户信用积分减2。请注意,应该在借阅期限当天扣除积分,而不是归还书籍当天扣除。因此在
Customer
类中新建属性returnBooks
表示客户拥有的尚未扣除积分的书籍,在每日结束时对该属性遍历扣除积分,已扣除积分的书籍移除。 - 整理流程的顺序安排: 按照要求,在闭馆后,应先对借阅超期和预约超期的客户进行分数扣除,再对有捐赠非正式书籍转正的客户进行加分。因此,闭关后整理流程的顺序安排应是:遍历客户,对客户进行借阅逾期积分扣除 → \rightarrow →整理预约处书籍,对客户预约逾期未取积分扣除,并将书籍返还书架 → \rightarrow →整理借阅处书籍,对有捐赠非正式书籍转正的客户加分,并将书籍返换书籍和图书漂流角 → \rightarrow →整理书架,按照预约请求将书籍运送至预约处。
三、Bug分析及程序优化
Homework1
Bug分析
本次作业暂无bug。
优化策略
本次作业对性能要求较低,暂无优化策略。
Homework2
Bug分析
本次作业暂无bug。
优化策略
本次作业对性能要求较低,暂无优化策略。
Homework3
Bug分析
本次作业暂无bug。
优化策略
本次作业对性能要求较低,暂无优化策略。
四、心得体会
在学习过前三个单元后,第四个单元迎来了完全自由的代码框架设计。不得不说,经过前三个单元的魔鬼训练,笔者学到了许多理念与思想,并运用在了本单元中,比如说本单元中的核心类BookBuffer
类,就是参考第二单元中的RequestQueue
类进行设计的。同时,按照相像性设计了Office
类和Service
接口,将一些类进行抽象,并将其存储在System
的数组中,可以实现更进一步的方法抽象。
那么心得体会部分就主要谈谈这单元完成之后的一些收获和经验吧:
- 架构设计的重要性。 好的,这已经是第四遍了。相较于第三单元,本单元实现难度大幅降低,设计自由度大幅提高。好的设计逻辑更加鲜明,实现更加抽象,复用性更高。
- 各类语言的阅读能力。 第三单元作业出现了
JML
语言,本单元作业中出现了UML
语言。UML
语言可视化程度高,阅读更加清晰明白。但是starUML
从头构建UML
图确实是一件繁琐的事情,耗费大量时间。同时,正常来说,UML
图的设计应在程序设计最开始进行,但是在真正实现时,会发现很多细节都有瑕疵,需要不停修改,较为难办。 - 测试的重要性。 在第三单元中,最重要的是程序测试,本单元中同样重要。本单元中,由于服务种类众多,组合出很多种情况,又存在如信誉积分这种影响全局的属性,因此需要进行详尽的测试。
至于对课程的建议,就留到课程总结中说吧。(其实就在下方)
最后,还是要感谢助教wwr学长的帮助。也希望在之后的学习中能再接再厉,继续进步。
课程总结
架构设计思维演进
经过整个课程四个单元迭代式的学习演进,笔者在架构设计方面学到了很多,最重要的应该是分离式、模块化、层次化、安全性设计。现进行一定的记录:
第一单元 字符串解析的简单计算器实现
- 递归下降法设计: 递归下降法是将原对象分为多个层次,每个层次相互独立处理,将本层次划分为多个下一层次的元素,并进入下一个层次进行处理。在第一单元中表现为原式分为多项式、单项式、因子,按照
+/-
将多项式拆分为单项式,按照*
将单项式分为因子,在因子层面,如果因子带有()
则按照多项式处理(这就会出现多项式类继承因子类的行为)。 - 分离式设计: 分离式设计应该是面向对象设计思想中极为重要的一点。这种思想有多种含义,包括程序功能模块化设计(如本单元中字符串解析为各个元素与字符串中元素计算功能分离)、层次化设计(如本单元中分为多项式、单项式、因子,后面可能还有更多层次,各个层次相互分离)等。
第二单元 多线程对应的电梯系统实现
- 线程控制安全: 主要有两种方式,普通的锁
synchronized
和读写锁,区别主要在于读写锁可以支持多个读线程同时对临界资源进行访问而synchronized
方法则不行,两者会带来一定程度上的性能区别。线程安全是多线程设计中最需要考虑的问题,对于临界资源的访问都应确保通过锁的使用是安全的。 - 线程休眠唤醒: 为保证在不进行任何动作时空余计算资源,在线程没有请求时进行休眠操作,并在出现请求时将其唤醒。具体操作主要是使用
wait()
和notifyAll()
函数(请注意,这两个函数都是对临界资源进行操作的,即当前线程休眠和共享临界资源的全部线程唤醒)。休眠唤醒中需要关注程序的结束问题,比如部分线程休眠后无法被唤醒(如部分线程休眠而进行中的线程结束)就会导致程序无法结束。 - 临界资源共享: 我们知道,进程包含资源和执行单元,而这种执行单元就是线程。也就是说线程只包含少量资源,通过函数调用执行操作,因此需要为其包含的资源提供共享的临界资源,并通过这些临界资源控制线程。这从本质上说也是一种分离式设计。
第三单元 JML架构对应的社交网络实现
- 上层接口实现: 在最上层类中建立多个函数接口,降低访问时间消耗与逻辑复杂度。
- 特定属性动态维护: 对于某些特定的属性,可以进行动态维护,降低访问时间消耗。
- 规格/设计和实现分离: 本单元主要学习
JML
规格,将架构设计与代码实现分离,由架构设计师设计出程序架构,并书写JML
架构语言,由程序员按照JML
语言完成具体代码。
第四单元 自由设计的图书馆系统实现
- 抽象思想的实现: 在设计中,应注意方法的复用性。可以将程序中的部分对象进行进一步抽象,以提高程序复用性。如本单元设计中,将
Shelf
、Appoint
、Borrow
、Corner
等具体的类继承Office
类,在上层存储时存储为Office
数组,在调用时按照序号进行使用。服务类也是如此,将其实现为Service
接口,在上层存储时存储为Service
数组,在调用时按照序号进行使用。 - 对于信息的解析存储: 对于一些对象的关键信息,应进行存储。如本单元的书籍对象,其含有借阅信息、预约信息、和捐赠信息等关键属性,需要建立关键类并存储这些信息以便后续使用。
- 分离式设计与多层次设计: 本单元中,分离式设计以及信息可见性是很关键的问题。将功能实现与查询接口相分离,将图书馆中不同部分的功能实现相分离,将
System
、Library
、Office
、BookBuffer
等层次分离,对于这些分离的模块设计接口,仅接口信息可见,保护信息的可见性和程序的安全性。
测试思维演进
四个单元中,对自身程序进行了多次测试,以满足程序正确性的要求。
- 数据构造器: 通过编写
python
程序,实现自动随机生成大量数据,并放入程序中进行验证。 - 多情况全面评判: 测试最核心的点在于数据的覆盖性,因此数据不在于数据量大,而是在于数据的覆盖面广。构造数据前,全面考虑可能出现的情况,并进行相应的特殊数据构造。
- 强数据测试: 程序除去正确性,还应注意运行时间等性能问题,不应出现大数据量时程序无法在限制时间内获得正确结果的情况。
JUnit
单元测试:JUnit
主要是对特定方法进行测试,通过书写测试程序,检查方法使用时是否会出现结果以及对象变化的问题。其主要是从特定的方法层面测试,而非全面的程序逻辑测试。同时也进行了参数化数据构造,实现自动化生成数据测试。- 多种测试方法并行: 黑箱测试、白箱测试,单元测试、集成测试,功能测试,压力测试,回归测试等测试方法,对程序的整体功能实现和代码实现逻辑与限制进行了测试。详细部分已经在第三单元中阐释,不过多赘述。
课程收获
对比CO和OS课,OO课可以说是我最上心的课了,因为面向对象这种思想不论是在工程实践还是生活中都有极高的实用价值。包括说本学期在日常中自己写一些具体问题的程序,都在刻意的训练自己这种思考问题的方式,以期更能适应未来的生产生活。
现对自己在面向对象中学到的东西做一些总结:
- 架构设计: 面向对象课程中最大的收获应该就是架构设计了,在这之中学习到了许多构建架构的思想,如分离式模块化设计、分层式迭代设计、出于程序安全性考虑的特殊设计。总结下来就是一种低内聚高耦合的思想,设置多种闭合模块,再暴露出特定接口,以模块为单位实现元功能和组合成为更复杂的函数,提高了程序安全性、修改的简便性和函数的复用性。这种思想在各个领域都有表现,就像局域网等。同时,面向对象是对于类进行操作,而架构设计本身也是一种分类的过程,分成多层次或依赖或独立的各种类。分类标准也是一项大收获,过去更多按照从属关系分类,类中方法也更多以具体实现为导向;而现在也会通过一些接口实现和类继承,从业务角度出发,不仅以操作者分,还以相似动作分。
- 性能优化: 除去第四单元,前三个单元都对程序性能有具体要求。如第一单元中,对于计算式的展开、同类项的合并具有很高的要求;第二单元中,对于电梯调度(单个轿厢与多电梯调配)策略有很高要求(影子电梯或多目标规划);第三单元中,对于动态维护、大顶堆等排序方法、
DPS/BPS
等图论方法的实践。在整个学习中,既有架构设计等整体性学习,又有性能优化等局部性练习。 - 工程开发流程: 各单元的迭代练习比较有工程开发的味道,从前期架构设计到后期具体实现,都有很大难度的训练。整体学习中,对各部分都有所锻炼,相信在未来真正面对实际问题时,不会那么无头苍蝇乱撞,或者设计出需要不停重构的架构了。
- 程序测试: 在几个单元中,学习并使用了多种测试方法。通过黑白箱测试,实现数据测试和逻辑测试的双重验证;通过单元测试、集成测试,实现局部与整体的双重验证;通过构造极限数据点的压力测试,实现对于程序的边界测试。这些测试方法,或者说测试思想,对于未来书写代码具有重要意义。
还有很多更加细节的知识点,如多线程的设计与并行控制、JML
构造语言的阅读书写、UML
图的阅读绘制,等等等等。甚至可以说,这些思想也可以应用到生活中的做人做事中,比方说遇到各种事情时都可以将其划分为层次鲜明、互相独立的小问题,通过实现小功能去组合解决大问题。
那么,依旧是致谢环节。感谢各位风趣幽默又不缺乏细节的讲解架构设计和功能实现的老师,感谢每一位帮助我找出bug共度难关的学长学姐和同学,也感谢那个虽不完美但仍对一切充满信心充满热情的自己。衷心希望OO课程未来更进一步,祝好!