309
社区成员
发帖
与我相关
我的任务
分享在以往作业中,我更习惯先根据输入输出写代码,遇到需求变化时再临时添加类和方法。这样的方式在需求较简单时效率较高,但当系统规模变大时,代码很容易变成“哪里需要就在哪里补一块逻辑”的状态。
第四单元要求我们先提交一阶类图,再实现代码,最后根据代码修正二阶类图。这个流程迫使我在写代码之前先思考几个问题:
我一开始不太理解为什么要设计两阶类图。设计阶段画一个类图,编程阶段照着类图实现不就行了?有什么问题直接在代码上改,还要连带着更新类图不是多此一举吗?
然而实践过程中我发现了,在类图上进行修改比直接对着代码改更直观、更宏观,能够明确一个地方的改动需要连带着改动其他哪些地方,使得修改更有质量、更有效率。
但既然如此,设计时很难做到尽善尽美,很多问题往往要到实际编程时才会发现,那么一阶类图和二阶类图有一定差距几乎是必然的。然而课程组或许出于作业周期和方便测试的考量,一方面只为一阶类图预留了很短的设计时间,另一方面又要求两阶类图相似性不能太低,这两个限制某种意义上互相排斥。虽然课程组也说明了差距太大时只要提交说明文档也没问题,但从我身边的反应来看,大家大多产生了“相似性太低是有问题的”的误解。希望课程组今后能考虑克服这个问题。
第四单元最终的架构可以概括为“管理器调度 + 仓库维护状态 + 服务类处理流程 + 实体类保存信息”。
LibraryManager 是系统入口,负责读取命令并分发给不同模块。它不应该承担所有业务细节,而是负责组织开馆、闭馆和请求处理流程。
RequestProcessor 负责处理用户在开馆期间发出的请求,包括借书、还书、预约、取书、阅读、归还、评分、续订和查询。这样可以避免 Main 方法中出现大量复杂判断。
BookRepository 负责维护图书副本的当前位置,例如普通书架、精品书架、预约处、借还处、阅览室和用户手中。它是图书状态变化的核心。
ArrangeService 负责整理流程,包括把借还处和阅览室的书放回书架、根据评分移动到精品书架或普通书架、处理预约过期、为预约用户预留图书等。
ReservationManager 负责保存预约信息,包括用户是否已有未完成预约、预约的 ISBN、预约图书是否已经送达预约处、是否过期等。
Student 负责用户自身状态,包括持有的图书、当天正在阅读的书、信用分等。
BookCopy 和 Book 负责图书与图书副本的信息。Book 表达某一 ISBN 的抽象图书,BookCopy 表达具体副本。这个区分在后续需求中比较重要,因为评分是按 ISBN 进行的,而移动轨迹和借还操作是按副本进行的。
第十三次作业的核心是基础流转:书架、预约处、借还处和用户之间的图书移动。此时最关键的是正确维护图书位置和用户持有状态。
第十四次作业加入了精品书架、阅读室和评分机制。这使得“图书在架”不再只表示普通书架,而是普通书架和精品书架的统称。同时,阅读流程引入了阅览室状态,评分流程又决定了开馆整理时图书应该进入普通书架还是精品书架。这要求图书位置不能简单用一个布尔值表示,而应该用状态枚举统一管理。
第十五次作业加入信用分、借阅期限和续订。此时,仅仅知道“用户持有某书”已经不够,还要知道借阅日期、应还日期、是否逾期、是否续订过等信息。因此需要增加借阅记录类或在用户持有信息中维护期限相关数据。信用分也要求系统在不同事件发生时即时更新,比如按时还书加分、逾期扣分、阅读后主动归还加分、阅读不还扣分、预约不取扣分等。
整个迭代过程说明,一个好的架构应该尽量将变化封装在局部。比如评分变化主要影响书架整理,信用分变化主要影响用户状态和权限判断,续订主要影响借阅记录。如果所有逻辑都混在一个方法中,每次迭代都会导致大面积修改。
类图中的类应该能在代码中找到对应实现。例如 LibraryManager、RequestProcessor、BookRepository、ReservationManager、Student、Book、BookCopy 等,都应该有对应的 Java 类。
类图中的属性也应该尽量对应代码中的成员变量。例如 Student 中的 credit 或 creditScore 对应用户信用分,BookCopy 中的 isbn 对应图书副本所属 ISBN,LibraryManager 中关联 Bookshelf、AppointmentOffice、BorrowReturnOffice 等地点对象,体现图书馆管理者对各个部门的组织关系。
类图中的方法则对应系统能力。例如 borrow、returnBook、orderBook、pickBook、read、restore、grade、renew、query 等方法,分别对应题面中的业务请求。这样不仅有利于评测,也有利于自己检查“题目中的每个动词是否在程序中有对应实现。
状态图关注的是一本具体图书副本在系统中的位置变化。第十四、十五次作业中,图书可能处于普通书架、精品书架、预约处、借还处、阅览室和用户手中等状态。
这些状态与代码中的图书位置枚举或状态字段相对应。例如一次成功借阅会触发“普通书架/精品书架 -> 用户”,一次还书会触发“用户 -> 借还处”,一次阅读会触发“普通书架/精品书架 -> 阅览室”,闭馆后整理可能触发“阅览室 -> 普通书架/精品书架”。状态图可以帮助检查是否有遗漏的状态转移,也能帮助理解图书移动轨迹为什么这样输出。
顺序图体现的是一次业务请求中多个对象之间如何协作。
例如用户成功预约一本书的过程中,可以抽象为:用户发出预约请求,LibraryManager 接收命令,RequestProcessor 判断请求是否合法,ReservationManager 创建预约记录,AppointmentOffice 在整理时接收被预留的图书。成功取书时,系统需要从预约处找到为该用户预留的副本,检查数量限制,然后将图书转移到用户手中,最后完成预约。
顺序图的价值在于,它不只是说明“有哪些类”,而是说明“这些类如何共同完成一个场景”。这对理解复杂业务流程很有帮助。
在第四单元中,我使用了大模型辅助分析题意、设计架构、检查 UML 模型和定位 bug。这个过程给我的感受是:大模型在处理复杂需求时很有帮助,但不能完全依赖它直接给出最终答案,尤其是在有严格评测规则和交互逻辑的作业中。
首先,大模型适合帮助梳理需求。第四单元的题面很长,而且需求不断迭代。让大模型把新增功能、修改点和潜在影响列出来,可以帮助我快速明确本次作业相对上次作业变化在哪里。
其次,大模型适合帮助搭建初始架构。比如将图书馆系统拆成管理器、请求处理器、仓库、预约管理器、用户类、图书类、整理服务等模块,这类职责划分大模型通常可以给出较合理的建议。
再次,大模型适合辅助定位错误。例如在第十五次作业中,曾经出现过取书逻辑错误:我把 canPick 直接复用了 canBorrow,导致取书时错误地再次检查信用分是否大于 80。后来重新对照题面发现,取书流程只要求预约有效、ISBN 匹配和借阅数量限制,并没有要求信用分仍然大于 80。大模型擅长分析hack数据的模式,从而结合题面定位错误。
刚拿到第一单元任务时,我的脑海里自然浮现出了字符串处理与递归调用,不理解为什么非得写那么多类,感觉很多类的内容都没什么区别啊。
随着代码的迭代,我逐渐意识到了:
多线程的思想是一个全新的思想,让我们从对于程序的静态视角上升到了动态的视角,更加关注运行时的行为。
我认为第三、四单元实际上是一回事:将我们从实现的角度带到了设计的角度。只不过第三单元作为过渡,让我们练习根据已有的设计进行实现,而第四单元则真正交给我们设计的任务。
相比于实现,设计是一个更高的视角,更有利于优化我们的架构。
大模型时代里,底层任务的价值骤减,我们需要自觉提升自己能力的层次:从实现上升到设计,上升到测试。所以比起埋头苦学编程语言,我们有必要进行设计、测试等能力的学习和练习。
第一单元中,主要靠分析题目得到边界情况和极端情况,来测试代码的正确性、复杂度。
此外,由于任务相对简单,一些奇怪的错误便频繁发生,所以也有必要靠精读代码或评测机来找bug。
多线程编程不太方便用以往的逐步测试,主要依赖黑盒测试。
因为多线程涉及相当多的并发问题和时序问题,所以可以据此有针对性地测试。
另一方面,多线程具有不可复现性,所以测试难度依然很高。
根据规格很容易得到边界情况,这是提前设计的好处。
规格不涉及复杂度,所以需要仔细进行性能测试。
面向对象课程不仅教会了我们“面向对象”的思想,而且为我们提供了相当多的实战考验。
比如,同样是进程与线程,操作系统课主要从信号量的角度帮我们理解同步与互斥,而面向对象课则用有挑战性的电梯任务,在不断的犯错中帮助我们加深理解。
我们最爱又最恨的环节是互测,就像游戏一样惊心动魄,时而欣喜若狂,时而面红耳赤。互测借助竞争的压力,锻炼了我们阅读他人代码、构造测试数据、与同伴共享信息等实际场景中需要的能力。
每单元的三次迭代也让我们切身理解了“敏捷模型”的含义和好的架构的意义。
我认为这门课训练的核心不是某一种语法或某一个框架,而是面对复杂需求时如何进行抽象、建模、实现、验证和迭代的能力。这个能力比某一次作业的分数更重要,也会影响以后完成更大规模软件系统时的设计方式。