BUAA OO 2024 Unit 4 & Final

BUAA OO 2024 Unit 4 & Final

正向建模与开发

UML 愚见

何为 UML?按照官方的说法,这个叫做统一建模语言(Unified Modeling Language),能够帮助我们理清不同类型的 功能特征和组织结构(类图)、某个实体的 状态变化(状态图)、不同实体的 行为交互(时序图)。如果说,上个单元的的 JML (Java Modeling Language) 是对代码的 直接描述,那么 UML 则是在此基础上的 进一步抽象,展示了代码的 内在逻辑。一方面,它集中体现了开发者的设计思路,便于不同开发者进行架构设计层面的交流合作;另一方面,它具有相对良好的可读性,有助于在客户的特定需求和开发者的具体实现之间实现有机统一。

Class Diagram

类图主要展示的是类的属性、方法,包括他们的类型、参数、可见性。此外,类图还能体现类与类之间的关系,诸如泛化、实现、依赖、关联、组合、聚合等等。类图通过忠实地展示代码中类的 原始信息,能够帮助我们从 宏观上 把握项目的层次结构,从而进一步理解架构设计。

State Diagram

状态图主要展示的某个实体存在的几种状态,包括初始状态、中间状态、终止状态。此外,状态图还能展示状态转化所需的触发条件和前置要求。状态图通过 横向 地梳理某个实体的存在状态,能够帮助我们具体理解某些关键对象的 业务面,从而更好地进行需求对接和程序设计。

Sequence Diagram

时序图主要展示的是不同实体之间的行为交互,包括消息传递、生命周期等信息。此外,时序图还能再此基础上进一步细化消息的类型、发送接收的时机、创建与消亡的行为。时序图通过 纵向 地描述不同实体的行为交互,能够帮助我们理解某些业务的 处理流程, 从而检验功能的实现是否合理。

开发实践

罗马城不是一天建成的,UML 也绝非一下就能写好的。

在开发的流程中,往往是根据需求产生了一些大概的想法(譬如为了实现预约功能,我们需要一个预约处的类;为了协调不同部门的工作,我们需要一个调度器类来统一调度……),然后在 类图 中画出来对我们的代码实现进行 方向性 的指导。当我们在写代码的过程中,往往会因为具体的实现而发现许多新的问题,就需要在类图中进行补充、修改,然后再进一步在代码中完善设计。如此反复,实现类图的丰满、代码的实现。

当我们对某一个对象的业务面不清晰的时候,就需要通过 状态图 来整理这个对象的状态,列出可行的状态转移路径(譬如图书可以在书架、借还处、预约处、漂流角、用户的手中,借还处到书架是可行的而借还处到预约处则不可行),然后与代码一一对应确保对应的属性和方法不重不漏。

当我们对某一功能的业务流程不清晰的时候,就需要通过 时序图 来梳理这个业务的处理流程,画出涉及的对象,把握他们的交互(譬如预约图书需要调度器从书架中取书送到预约处,然后再交付用户),然后确保代码能够完整地实现这条功能主线。

从上面的分析可以看到,UML 并非起到顶层设计的作用,它实际上与代码相辅相成。用一个不恰当的比喻来形容,UML 就像游戏里 引导我们的 NPC,没有他们,我们照样可以完成任务;但有了他们,我们就知道自己每一步应该干什么,明确了方向、少走些弯路。不论是要做好架构设计还是敏捷开发,UML 都是很好的辅助工具。所谓正向建模与开发,大抵如此。

架构设计

类的设计

本单元要完成的任务比较简单,我的架构也相对简洁,主要是围绕着 数据 进行设计。

Main 作为主类,仅负责接收输入的工作。

Dispatcher 作为核心功能类,采用了单例模式的设计,负责对主类发送的请求进行分类(实际上由官方包完成)、解析、处理。这个类作为 数据的管理者,完成了对不同类型数据的整体调度,其中的每个 deal* 方法都对应一个具体的业务功能(比如开门、预约、取书、查询信誉积分等)。

采取这样的设计,好处是便于集中处理、调用关系简单;与上一单元类似的是,这个类只用做最顶层的管理工作,具体的细节都下方到各个底层的类,层次分明、权责清晰、易于实现。但坏处也比较明显,核心代码都集中在这个类中,使得代码量直逼 500 行的警戒线。

BookShelfBorrowReturnOfficeAppointmentOfficeBookDriftCorner 类作为 数据的组织者 则具体适应每个部门的业务要求,采取各自的方式存储对应的数据,并根据顶层类的需要提供了查询、删除等方法。

WarnStudentOrder 三个类则是根据业务要求封装的底层数据类型,分别对应记录逾期的警告信息、用户的信息和预约信息。
类图

代码设计和UML模型设计

从代码和 UML 模型的对应关系来说,二者保持了高度的一致性。不论是在类的基本构造上还是业务流程的方法设计上,代码和 UML 都可互相追踪。对于一个合格的软件工程师来说,这应该是基本的要求(当然,也是为了通过课程组的评测)。具体的设计开发流程已在前文提到,这里不在赘述。

四个单元中架构设计思维的演进

Unit 1 表达式化简

这一单元强调 层次化设计。通过 递归下降 的方式,我们可以把一个复杂的任务按照固定的格式进行分解,每个层次只需负责当前层次的基本工作,而不用一口吃个大胖子,显著降低了任务的难度;由于分工明确,在出现 Bug 时我们也很容易快速定位错误。此外,这一单元的嵌套括号对于我们认识递归也有很大的帮助。之前学习汉诺塔时虽然每次都能够理解,但每次也都难以自己写出来;而在这一单元完成了括号展开之后,递归出口和递推关系这种抽象的概念也都具象化地了然于心了。

Unit 2 电梯调度

这一单元强调 多线程设计。我们主要采取了 生产者消费者 模型来协调不同线程之间的工作,通过在类内部确保线程安全的方式来尽可能地规避线程安全问题,通过同步互斥关系的把握来全面地解决线程安全问题。在初学这块内容时,可能只是涉及到皮毛,学了一点多线程设计的方法,并未把握其实质。但在操作系统课程学习了进程调度,特别是在实验课中手搓了时间片轮转的系统代码之后,就对多线程的底层原理有了更全面的认知。再回过头来看 Java 库函数层面的线程问题时,就多了一分从容不迫的底气。

Unit 3 社交网络

这一单元强调 规格化设计。在 JML 的帮助下,我们摒弃了之前 粗放型 的代码生产方式,转而进行 精耕细作,使得每个方法的功能都空前明确,前置条件、后置条件、副作用格外清晰,每个类的职责都合理划分,架构设计更加妥当,每一步都有迹可循、有据可依。尽管这一单元我们的主要工作是根据 JML 去实现自己的代码,但通过 JML 组织起来的结构无疑给我们展现出了良好的程序设计范例。这大大降低了我们编写代码的负担,标准化了我们的程序设计,使得空指针之类的低级错误近乎绝迹。

Unit 4 图书管理

这一单元强调 模型设计。与其他单元相比,这一单元的架构设计并无特别值得称道的地方,整体难度也相对较低。虽然 starUML 并不好用,但其在帮助我们梳理逻辑、整理思路方面的作用不容小觑。在这一单元中,我们品味到的更多是阅读需求、理解需求、实现需求、更改需求这一开发流程的完整闭环,也算是具体而微,为日后的大型项目开发积累了有益的经验。

经过一个学期的学习实践,在面对具体的任务时,我已经能够相对驾轻就熟地从 数据和功能 的角度进行架构设计。总体而言,对于 SOLID 六大软件设计原则中的单一职责和开闭原则有相对丰富的体会。从之前的一 main 到底,到现在的高度封装;从之前的高度耦合,到现在的分工明确;从之前头痛医头、脚痛医脚的代码实现,到现在提纲挈领、纲举目张的顶层设计……可以说,这四个单元的架构设计令我受益匪浅、收获颇丰吧!当然,这也有可能是后两单元作业较为简单带给我的错觉。

测试思维的演进

测试思维

本学期四个单元的实践,也是我软件测试思维逐渐形成的过程。

在第一单元,我第一次真正意义上实现了自己的评测机,直观体会到了其中的一些具体细节,初步学到了怎么生成数据、怎么检验 正确性 、怎么实现自动化,是从零到一的一次经验积累。尽管后续都没再写评测机,但倘若实现起来,想必也有了一定的方向。这一单元的测试也比较简单,只需采取相减为零的方式判断表达式化简结果正确与否即可。

在第二单元,我们深刻地意识到,多线程设计给测试带来了很大的困难。程序运行的结果只是某一时空和特定运行环境下的结果,不具有稳定性;我们的测试也如同薛定谔的猫一样,不具有确定性。这启示我们,程序的正确与否仅用覆盖率有限的大量的数据点去检验是 不完备的 ,我们还需要新的方式方法去形式化地确定程序的正确性。

在第三单元,我们学习了规格化设计和单元测试。我们意识到,很多错误是可以通过良好的设计避免的,一些没有规避掉的错误,也可以通过 单元测试 的手段有针对性地测试出来。在第四单元,我们在 UML 的帮助下更好地进行了程序设计,避免了一些潜在的错误;在具体的业务场景,我们也逐渐明白,测试不只是程序正确性的检验,更要 与需求对接

Bug 复盘

可能是因为急于求成,没有做过多的检查;也可能是因为心浮气躁,很多细节没处理好。尽管我自以为写起来相对得心应手,但在四个单元中出现的 Bug 并不少,既有逻辑上的疏漏,也有指下误的问题。最难的多线程设计单元并没有出问题,反倒是按部就班就能写好的后两单元小错不断。我想把这些问题列举出来,引以为戒

作业号Bug错因举例
Unit 1-2函数代入错误忽略了同名变量重复代入的问题f(x, y)=x*y 则 f(y, 1)=y 而不应等于1
Unit 1-3最大公因数错误在获取一组数据的最大公因数时,倘若仅有一个数,
则应该返回这个数的绝对值,而不应直接返回这个数
abs(-15) = 15 而不应等于 -15
Unit 3-1DFS 超时错误在用栈模拟深度优先遍历时,
应当把入过栈的元素标记为访问过,
而非把出过栈的元素标记为访问过
-
Unit 3-2int 溢出错误在重写比较函数,升序排列的时候,
前者在前返回负,后者在前返回正,
则应当调用原生的 Integer.compare 函数,
而不应直接返回二者的差值
2,147,483,647-(-1) =
-2,147,483,648
会造成排序错误
Unit 3-3qvs 计算错误在修改某对关系的值时,
包含这对关系的 Tag 中的 valueSum 要同步修改,
倘若分别从这对关系的两个人遍历 Tag ,
则会对有效 Tag 遍历两次,
则 valueSum 每次应该增减一半。
Unit 4-3预约处理错误倘若有正在生效的某一用户对某一书号的预约,
则应当拒绝,
判断时要同时满足用户和书号相同,
而非仅仅满足书号相同。
读者 1 预约了某一书号的书,
则读者 2 仍可预约该书,
不应因书号相同而拒绝

课程收获

在 OO 课程中,仍然延续了 CO 课程中不看一篇博客,全程独立设计的“优良传统”。我不好说这样是否会过犹不及,导致花费时间变长、认知宽度变窄、整体效果变差,但我向来不甚在意结果,很留恋那间让我憋出了处理表达式嵌套括号方法的教室,很喜欢那顿自己搓的评测机问世后的午餐,很怀念那个让我删删改改后顿悟出多线程奥秘的夜晚……每每念及于此,总有一种“把酒酹滔滔,心潮逐浪高”的豪情萦绕心间。虽然我不过一介凡夫俗子、志大才疏之辈,常有庸人自扰之举、难行慎终如始之事,但好在遇到有些事情也会激荡起一番愚者的孤勇,不求一蹴而就,但愿持之以恒,踏遍山重水复,自有柳暗花明!

多年以后,也许 Hry 正在某互联网公司的角落里,当着一名不起眼的码农;亦或是被人工智能所取代,不得不回家种地。但当他想起自己曾经用自己写的表达式化简程序,去完成离散数学生成函数相关的作业的时候;当他想起自己年少时打 MC 卡出 Bug,意识到这是一个线程安全问题的时候……他会会心一笑,继续接下来的旅程。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值