当前位置: 首页 > news >正文

25202214-软件工程凌云版三次作业集总结 - CR

一、 前言与度量基础

  1. 总体回顾
    本单元的核心任务是模拟航空业中极其重要的“载重平衡”环节。
    • 第一次作业:聚焦于基础的单航班、单货舱的货物装载与总重统计,难点在于基础类的划分和Scanner类控制台输入的特殊处理。
    • 第二次作业:引入了多货舱(前舱、后舱)概念以及装载位置网格,要求对货物按重量降序排列后进行贪心装载,难点在于理解组合(Composition)与聚合(Aggregation)关系的区别。
    • 第三次作业:难度呈现陡坡式上升。引入了旅客、行李实体,并且加入了真实的航空物理力学公式(力臂、力矩、平均空气动力弦MAC百分比的计算)。最严苛的是,本次作业严禁使用继承、多态、接口以及内置排序方法(Collections.sort),这极大考验了纯基础语法的运用能力和类的精细化拆分。
  2. Complexity Metrics(复杂度分析概念说明)
    在下文的设计与分析中,将使用SourceMonitor生成的复杂度报表,主要参考以下几个软件度量指标:
    • ev(G) (Essential Complexity):基本复杂度,用来表示一个方法的结构化程度。范围在$[1, v(G)]$之间,值越大意味着程序的结构越缺乏规范,包含了大量非结构化的跳转(如过多的break、return等),其计算过程和控制流图的“缩点”有关。
    • iv(G) (Design Complexity):设计复杂度,用来表示一个方法与其调用的其他方法之间的紧密程度(耦合度)。范围在$[1, v(G)]$之间,值越大表示模块间的依赖关系越复杂。
    • v(G) (Cyclomatic Complexity):圈复杂度/循环复杂度。可以直观理解为穷尽程序流程每一条独立路径所需要的测试用例数量。过多的if-else、for、while会导致此数值飙升。
    • 类复杂度:包含类的平均方法复杂度等,反映类是否承担了过多的上帝职责(God Object)。
    二、 设计与分析
    (一)第一次作业:基础货运配载
  3. 作业要求与领域模型
    第一次作业要求设计一个基础的航班货运模块。系统记录航班的基本信息(航班号、最大起飞重量),地勤人员循环输入货物信息(名称、重量)。系统需实时计算已装载的总重量,并与最大起飞重量比对,输出是否“严重超载”。指导书给出了严格的单一职责原则(SRP)限制。
  4. 架构设计与类图
    在架构设计上,我严格参考了指导书的UML参考模型,设立了三个核心实体类:
    • Flight(航班类):负责维护航班的基础属性,并将具体的装载逻辑委托给内部的装载单。
    • LoadManifest(装载单类):这是核心业务类,内部维护一个ArrayList,负责执行添加货物、统计总重量(countTotal())的职责。
    • Cargo(货物体类):纯粹的实体类(POJO),仅包含货物的名称和重量。
    356abf5c8eb6f09ecfd7b295830624aa
    be65e5a741ad1d09c35efeac7631f56a

分析与心得:
通过SourceMonitor数据可以看出,本次代码整体规模极小(百行左右)。由于功能单一,所有方法的
v(G)均保持在1~2之间。唯一出现轻微复杂度提升的是主类Main中的main方法。因为所有的控制台输入解析、循环读取以及最终的格式化输出判断均堆砌在main方法中。这虽然在第一次作业中没有引发问题,但已经暴露出“主控逻辑过重”的端倪,为后续的重构埋下了伏笔。
(二)第二次作业:多货舱管理与降序装载

  1. 作业要求与领域模型
    迭代二要求飞机划分为多个货舱(如前舱、后舱),每个货舱有独立的最大载重和具体的行列位置。货物装载不再是简单的累加,而是要求先
    按货物重量从高到低排序,依次尝试装入指定货舱。如果某货舱容量不足,则当前货物装载失败。同时要判断整体航班是否超过“最大起飞重量”和“最大业载重量”。
  2. 架构设计与类图
    本次架构的核心难点在于精准表达对象间的关系:
    • 组合关系(实心菱形):CargoCompartment(货舱类)与Position(位置类)。货舱被创建时,其内部的网格位置即固定存在,货舱销毁,位置随之销毁。因此,Position集合是在CargoCompartment的构造器中直接实例化的。
    • 聚合关系(空心菱形):CargoCompartment与Cargo。货物是独立存在的实体,只是被“放置”进货舱,因此通过addCargo(Cargo cargo)方法从外部传入。
    • 引入了算法调度类 LoadDispatcher,负责剥离出排序(sortCargos)和货舱查找(findCompartment)的职责,保证了实体类的纯洁性。
    [此处插入图片:第二次作业类图]
    (注:必须清晰展示出CargoCompartment指向Position的实心菱形箭头,以及指向Cargo的空心菱形箭头)
  3. 复杂度与代码规模分析
    b1246fb86004350fb0f79c86c347c70c

092b066141c2a228cecc5e3dc1ae6e5f

分析与心得:
从监控数据可以看到,本次作业的复杂度出现了显著的跃升。
首先,
LoadDispatcher在处理货物降序时,由于涉及对象属性的比对,其逻辑分支增加,iv(G)
有所上升。
其次,最严重的问题爆发在
Main类中。main方法的v(G)可能突破了10。这是因为装载完成后,系统需要执行多重状态判定:既要遍历输出每个货舱的正常/超载状态,又要进行复杂的逻辑判断:
Java
下载代码
复制代码
boolean
overTakeoff = totalFlightWeight > flight.getMaxTakeoffWeight();
boolean
overPayload = totalFlightWeight > flight.getMaxPayloadWeight();
if
(overTakeoff && overPayload) { ... }
else if
(overTakeoff) { ... }
else if
(overPayload) { ... }
这种多重条件分支是导致圈复杂度上升的直接元凶。
(三)第三次作业:航空器载重平衡核心计算

  1. 作业要求与领域模型
    本次迭代是最贴近真实航空业务的硬核业务。不仅需要管理货舱货物,还需要引入旅客(
    Passenger)及行李(Luggage
    )。最致命的约束是:必须通过物理公式计算重心位置。
    公式核心要求:
    image

  2. 架构设计与类图
    面对严苛的约束,我设计了近10个类来分摊庞大的业务逻辑:
    • 细粒度拆分:将旅客拆分为Passenger和Luggage。按照业务规定,旅客带行李属于组合关系,因此Luggage在Passenger内部被强行绑定创建。
    • 静态工具类:设计了WeightBalanceCalculator纯计算工具类。该类没有成员变量,不持有航班状态,而是暴露generateLoadSheet(Flight flight)静态方法。所有的基准常量(空机40000kg,空机力臂16.25m等)均作为public static final常量维护在其中。
    • 防腐层设计:为了解决前一次作业主方法复杂度爆炸的问题,设计了InputValidator静态类,全面接管控制台读取、异常捕获、负数过滤和范围校验。
    54216ad01be8193a5ba075916ab318ed

  3. 复杂度与代码规模分析
    a5381a6875708ec080ce118b04f9575e

分析与心得:
通过SourceMonitor图表可见,代码总行数逼近600行。
WeightBalanceCalculator.generateLoadSheet()方法成为了新的“复杂度洼地”。在这个方法中,既有对旅客列表的for循环遍历统计,也有对前后货舱的for循环遍历统计,紧接着是冗长的数学四则运算,最后是一大块if-else
判断MAC百分比是否在$[25.0, 38.0]$安全区间。
尽管我抽象出了工具类,但这个方法本身承担了“计算数据”和“格式化控制台输出打印”两项职责,这在某种程度上依然违反了单一职责原则,导致其设计复杂度
iv(G)偏高。
三、 采坑心得与深度反思
在这三次源码的提交和PTA自动化测试系统的对抗中,我踩了无数的坑,这些血泪教训远比最终的正确代码更有价值。

  1. Scanner缓冲区陷阱(第一/二次作业)
    问题再现:在连续输入数字后紧接字符串读取时,程序经常抛出InputMismatchException
    或直接读取到空字符串。
    深度剖析:例如执行int n = sc.nextInt();后,用户在控制台输入3并按下回车。nextInt()仅仅读取了数字3,而将回车符\n留在了输入缓冲区中。随后执行的String name = sc.nextLine();
    会立刻捕获到这个残留的回车符,导致真正的货物名称被跳过。
    解决方案:在每次读取数值类型且后续需要读取整行字符串时,必须显式地插入一行sc.nextLine();来专门吞噬缓冲区中的废弃回车符。这是一个极易被新手忽略的基础机制问题。
  2. 自定义排序算法的“不稳定性”灾难(第三次作业)
    问题再现
    :第三次作业禁止调用JDK自带的排序API,必须手写。我最初写了一个基础的冒泡排序。但在PTA测试中,出现了“同等重量的货物,输出顺序与输入顺序不一致”的格式错误。
    深度剖析
    :指导书明确要求“同等重量的货物按输入的先后顺序排列”。如果冒泡排序的交换条件写成了:
    if (cargos.get(j).getWeight() >= cargos.get(j+1).getWeight())
    这会导致原本重量相同的相邻元素发生位置交换,破坏了排序算法的
    稳定性(Stability)

    解决方案:修改判断条件为严格的<
    (因为是降序排列,只有后面的严格大于前面的才交换):
    if (cargos.get(j).getWeight() < cargos.get(j+1).getWeight()) { swap(...) }
    仅仅一个等号的区别,体现了对基础算法特性理解的深度要求。
  3. 浮点数精度截断与输出格式(贯穿三次作业)
    问题再现:直接使用System.out.println输出double类型的总重量时,遇到类似1478.000000000002
    的结果,导致Wrong Answer。
    深度剖析
    :由于计算机底层的IEEE 754浮点数表示规范,十进制的小数在转换为二进制时往往产生精度丢失。
    解决方案:全面废弃println拼接字符串的做法,一律采用System.out.printf("...%.1fkg", weight)进行格式化输出控制。这不仅解决了精度显示问题,也使得长文本的拼接代码变得更加优雅可读。
  4. 输入鲁棒性的架构痛点(第三次作业)
    问题再现:第三次作业要求:如果发现数值为负,立即打印特定错误并停止程序。初期我将这段逻辑写在Main的解析流中,导致Main充斥着无数的if (val < 0) { System.exit(0); }
    ,代码丑陋不堪。
    解决方案:提取出了InputValidator类。将校验逻辑封装:
    Java
    下载代码
    复制代码
    public static double readDouble(Scanner sc)
    {
    double
    val = sc.nextDouble();
    if (val < 0
    ) {
    System.out.println(
    "数值不能为负数!"
    );
    System.exit(
    0
    );
    }
    return val;
    通过这种防腐层(Anti-corruption Layer)设计,主控逻辑无需再关心肮脏的边界判定,圈复杂度被成功压降。

四、 可持续改进建议
尽管系统通过了所有测试点,但站在工程的角度,目前的架构仍有明显的重构空间:

  1. 方法复杂度的进一步拆解(Extract Method)
    正如SourceMonitor所揭示的,WeightBalanceCalculator.generateLoadSheet
    方法过于臃肿。
    改进策略:应该利用提取方法(Extract Method)重构手法,将该静态方法拆分为:
    • private static double calculatePaxTotalMoment(List paxes)
    • private static double calculateCargoMoment(CargoCompartment comp, double arm)
    • private static void printFormattedSheet(...)
    让顶层方法只负责流程调度,将业务细节下沉到私有方法,从而大幅降低单个方法的
    v(G)。
  2. 实体类的防御性编程与封装边界
    当前架构中,许多实体类的集合是通过Getter直接暴露给外部的。例如flight.getCompartments()直接返回了底层的ArrayList
    引用。
    危险在于:外部拿到这个引用后,可以随意调用.clear()或.remove()
    ,这破坏了对象状态的完整性。
    改进策略:应该返回集合的只读副本(虽然本次禁用了高级特性,但在未来的开发中应采用Collections.unmodifiableList),或者在Flight类内部提供迭代器或具体的查询方法,坚守迪米特法则(Law of Demeter)。
  3. 应对未来变化的开闭原则(OCP)
    假设未来的“迭代四”要求增加“机翼油箱”的重心计算,当前的generateLoadSheet
    方法必须被打开修改,添加新的硬编码逻辑。
    改进策略:在解除了“禁止使用接口和多态”的限制后,应该为所有能产生力矩的实体(客舱、前舱、后舱、油箱)提取一个公共接口MomentCalculable,定义getWeight()和getMoment()方法。这样,WeightBalanceCalculator只需要遍历所有的MomentCalculable接口集合,无论新增什么类型的载体,计算类都不需要修改任何一行代码,真正实现对扩展开放,对修改封闭。
    五、 总结与展望
    经历了第一单元的折磨与蜕变,我深切感受到了面向对象编程的魅力。在以往的C语言项目中,我总是习惯以过程的视角去解构问题(第一步读数据,第二步计算,第三步输出);而这三次作业,强行扭转了我的视角,让我学会以实体和职责的视角去建模这个世界。
    我学到了什么?
  4. SRP(单一职责原则)的巨大威力。它让代码的Bug定位变得极其精确。例如计算出错,我只需去查WeightBalanceCalculator;排序不对,我只需看LoadDispatcher。
  5. 组合与聚合的语意区别。不仅是UML图上菱形实心与空心的区别,更是生命周期管理的区别(同生共死 vs 弱引用)。
  6. 利用SourceMonitor等度量工具驱动重构的意识。不再是“代码能跑就行”,而是追求代码结构的健康。
    对课程与教学的建议:
  7. 关于测试方法:目前我们重度依赖PTA平台的黑盒测试(Wrong Answer / 格式错误)。由于看不到具体的错误测试用例,很多时候是在盲目猜Bug(例如边界值是25.0还是25.1)。建议在后续课程中,早日引入JUnit单元测试框架的教学。如果在第三次复杂的重心计算中,我们能编写单元测试对特定的物理公式进行断言测试,将会极大提高代码的质量和开发效率。
  8. 关于代码规范:建议引入类似CheckStyle的自动化工具,强制规范变量命名、方法长度等,培养企业级开发的代码素养。
    回首这三次“航空器配载”的迭代之旅,从单舱累加到复杂物理力矩计算,从混沌的主函数到各司其职的类群,虽然过程中踩坑无数,但看着SourceMonitor中越来越清晰的代码结构,这本身就是对工程师思维最大的奖赏。未来的多线程与复杂架构单元,我已准备好迎接新的挑战!
http://www.zskr.cn/news/1311913.html

相关文章:

  • Go泛型实战:从类型安全到代码复用的设计跃迁
  • 全网最详细的数据库基础指南
  • 打破生态壁垒:在Windows上无缝安装Android应用的创新方案
  • 如何3步彻底修复Windows游戏兼容性问题:DirectDraw兼容性终极解决方案
  • 嵌入式Linux嵌入式Linux驱动开发:板级DTS实操与完整实战演练——从修改设备树到点亮LED的完整闭环
  • NotebookLM提示词工程白皮书(社会科学专属版):含17个经IRB审核通过的田野访谈摘要模板
  • 通过 Python 脚本快速接入 Taotoken 并调用多模型完成内容生成任务
  • 面向对象设计与总结(航空配载系列)
  • Vue Vant Cascader异步加载数据实战:从事件困惑到精准控制的省市区街道选择方案
  • pta第一至三次作业总结
  • 【MySQL基础教程】DQL语句详细介绍
  • DeepSeek 强势赋能 OpenClaw 智能能力全面升级
  • NCM解密工具终极指南:简单三步解锁网易云音乐加密文件
  • 5分钟上手Waifu2x-Extension-GUI:AI超分辨率让你的图片视频焕然一新
  • GPTs商店避坑指南:3类97%用户踩过的“伪高星”GPT陷阱,附官方API调用验证法
  • 2026年内蒙古化妆/彩妆/美容美发/美甲美睫学校指南:为何“丽妍”成为行业首选? - 深度智识库
  • 【YOLO目标检测全栈实战】39 多模型流水线:当YOLO遇上OCR和语音合成,如何让四个模型“共线生产”?
  • 学生党福音:一个信用卡搞定AWS Deepracer无限免费训练时长,附CCF比赛实战代码
  • 高校实验室项目如何利用Taotoken的Token Plan套餐控制科研实验成本
  • 2026交调设备十大主流品牌排行榜 广州聚杰芯科占据市场重要席位 - 品牌速递
  • LLVM 16深度赋能Arm生态:从指令集、安全模型到工具链的全面革新
  • 深度解析7-Zip-zstd压缩算法:6种现代压缩技术性能对比与选型指南
  • 10分钟掌握R3nzSkin国服特供版:英雄联盟免费换肤完全指南
  • 强化学习算法:近端策略优化(PPO)
  • 告别臃肿软件!OmenSuperHub:惠普暗影精灵的纯净硬件控制神器
  • 超大规模内容生成技能引擎:模块化架构与工作流实践
  • Windows和Office激活难题?3分钟永久激活的智能方案
  • 使用taotoken后ubuntu服务器上的api调用延迟与稳定性体感观察
  • 终极指南:用D2DX让《暗黑破坏神2》在现代电脑上完美运行
  • React Server Components实战:解锁服务端渲染新能力