第二次blog作业

一、前言:
题目集八、九相较于之前的电梯调度问题在难度上要小了不少,在算法上的要求不算很高,但对结构的设计要求更高,往往有了构筑好了结构便能够轻松的解决问题,但新概念的引入,还是为我带来了一些困难,接下来我将展示我在这两次题目集中的历程。
二、问题分析:
题目集八的作业 1、2:
作业1让我做一个点、线、面的图形系统,主要学的是抽象类和继承。老师给了个Element抽象类,要求我们写Point、Line、Plane三个子类,分别实现显示坐标、计算线段长度和展示颜色的功能。输代码大概80行,不算多,但输出格式必须严格按题目要求,一个小点不对都会错,调了好久才搞定。作业2难度明显上来了,要做一个调速系统,得用接口设计两种方法。比如控制杆和旋钮的位置变化会触发不同速度计算,这里用了很多switch嵌套,写的时候容易漏case,特别是高级策略多两个档位,测试的时候得一个个试。写代码要求,边写边理清类之间的关系,比如Agent类怎么协调策略和组件状态,一开始没想通就卡住了好久。两次作业从基础类设计到策略切换,感觉对抽象的理解更深了,但作业2的switch确实有点重复,另外测试用例没覆盖到边界情况,下次注意这些细节。
题目集九的作业 1、2:
第四次作业是做几何体计算,比如立方体和正四棱锥的面积体积,再搞一个魔方类组合这些几何体,根据层数放大边长。一开始被数学公式搞懵了,比如正四棱锥体积公式里的根号2,照着公式硬写出来结果总是不对,后来发现是括号没打全。魔方类的设计也不容易,得把几何体对象传进去,还要算放大后的总边长,写的时候老忘记乘层数。第五次作业是动态管理几何对象系统,可以随时添加点、线、面,还能删指定位置的元素。这次用了集合存所有图形对象,输入指令1到4对应不同操作,比如输1加一个点,输4删第几个元素。最头疼的是输入处理,操作指令和参数得按固定顺序输,少一个就报错,删元素的时候下标从1开始,但代码里集合从0开始,调试时忘记减1直接越界崩溃。两次业都用了继承和多态,但第四次数学公式容易写错,第五次要边读输入边改集合,稍不留神就出bug,尤其是删元素后遍历集合可能会漏掉后面的项,后来用了临时列表才解决。
第一次航空货运管理系统:

点击查看代码
import java.util.*;
class Person{
    private String id;
    private String name;
    private String number;
    private String dizhi;
    public Person(String id, String name, String number, String dizhi){
        this.id=id;
        this.name=name;
        this.number=number;
        this.dizhi= dizhi;
    }
    public String getName(){
        return name;
    }
    public String getNumber(){
        return number;
    }
    public void setId(String id){
        this.id=id;
    }
    public void setName(String name){
        this.name=name; 
    }
    public void setNumber(String number){
        this.number=number;
    }
    public void setDizhi(String dizhi){
        this.dizhi =dizhi;
    }
}
class Huowu{
    private String id;
    private String name;
    private double length;
    private double width;
    private double height;
    private double grossweight; 
    public Huowu(String id,String name,double length,double width,double height, double grossweight) {
        this.id=id;
        this.name=name;
        this.length=length;
        this.width=width;
        this.height=height;
        this.grossweight=grossweight;
    }
    private double getVolumeWeight(){
        return (length*width*height)/6000;
    }
    public double getMweight(){
        return Math.max(getVolumeWeight(),grossweight);
    }
    public double getRate(){
        return getMweight()>=50?25:30;
    }
    public double getMoney(){
        return getMweight()*getRate();
    }
    public String getName(){
        return name;
    }
    public void setId(String id){
        this.id=id;
    }
    public void setName(String name){
        this.name=name;
    }
    public void setLength(double length){
        this.length=length;
    }
    public void setWidth(double width){
        this.width=width;
    }
    public void setHeight(double height){
        this.height=height; 
    }
    public void setGrossweight(double grossweight){
        this.grossweight=grossweight;
    }
}
class Fly {
    private String fNumber;
    private double maxWeight;

    public Fly(String fNumber,double maxWeight){
        this.fNumber =fNumber;
        this.maxWeight=maxWeight;
    }
    public String getFNumber(){
        return fNumber;
    }
    public double getMaxWeight(){
        return maxWeight;
    }
    public void setFNumber(String fNumber){
        this.fNumber=fNumber;
    }
    public void setMaxWeight(double maxWeight){
        this.maxWeight=maxWeight;
    }
}
public class Main{
    public static void main(String[] args){
        Scanner scanner=new Scanner(System.in);
        String Id=scanner.nextLine();
        String Name=scanner.nextLine();
        String Number=scanner.nextLine();
        String Dizhi=scanner.nextLine();
        Person person=new Person(Id, Name, Number, Dizhi);
        int HuowuCount=Integer.parseInt(scanner.nextLine());
        List<Huowu> huowus=new ArrayList<>();
        for (int i=0;i<HuowuCount;i++){
            String HuoId=scanner.nextLine();
            String name=scanner.nextLine();
            double length=Double.parseDouble(scanner.nextLine());
            double width=Double.parseDouble(scanner.nextLine());
            double height=Double.parseDouble(scanner.nextLine());
            double Grossweight=Double.parseDouble(scanner.nextLine());
            huowus.add(new Huowu(HuoId,name,length,width,height,Grossweight));
        }
        String flNumber=scanner.nextLine();
        scanner.nextLine();//起飞
        scanner.nextLine();//降落
        scanner.nextLine();//日期
        double maxWeight=Double.parseDouble(scanner.nextLine());
        Fly fly=new Fly(flNumber,maxWeight);
        String FaId=scanner.nextLine();
        String FaDate=scanner.nextLine();
        String FaDizhi=scanner.nextLine();
        String FaName=scanner.nextLine();
        String FaNumber=scanner.nextLine();
        String ShouDizhi=scanner.nextLine();
        String ShouName= scanner.nextLine();
        String ShouNumber=scanner.nextLine();
        double totalWeight=0;
        for (Huowu huowu:huowus){
            totalWeight+=huowu.getMweight();
        }
        if (totalWeight>fly.getMaxWeight()){
            System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.\n", fly.getFNumber());
            return;
        }
        System.out.printf("客户:%s(%s)订单信息如下:\n",person.getName(), person.getNumber());
        System.out.println("-----------------------------------------");
        System.out.printf("航班号:%s\n",fly.getFNumber());
        System.out.printf("订单号:%s\n",FaId);
        System.out.printf("订单日期:%s\n",FaDate);
        System.out.printf("发件人姓名:%s\n",FaName);
        System.out.printf("发件人电话:%s\n",FaNumber);
        System.out.printf("发件人地址:%s\n",FaDizhi);
        System.out.printf("收件人姓名:%s\n",ShouName);
        System.out.printf("收件人电话:%s\n",ShouNumber);
        System.out.printf("收件人地址:%s\n",ShouDizhi);
        System.out.printf("订单总重量(kg):%.1f\n",totalWeight);
        double totalMoney=0;
        for (Huowu huowu:huowus){
            totalMoney+=huowu.getMoney();
        }
        System.out.printf("微信支付金额:%.1f\n\n",totalMoney);
        System.out.println("货物明细如下:");
        System.out.println("-----------------------------------------");
        System.out.println("明细编号\t货物名称\t计费重量\t计费费率\t应交运费");
        int index=1;
        for (Huowu huowu : huowus){
            System.out.printf("%d\t%s\t%.1f\t%.1f\t%.1f\n",index++,huowu.getName(),huowu.getMweight(),huowu.getRate(),huowu.getMoney());
        }
    }
}

类图设计:

写题历程:
一、输入处理不够好,中间有一行输错了顺序,我之前把货物数量填到客户地址的位置,一直无法通过测试。忘记检查输入合法性,导致输入程序时,会生成一堆负数重量的货物。以致于我的代码拿不全分数。
二、运费费率的分段条件写反了。题目要求超过50公斤的货物用更便宜的费率,但我一开始代码里写成“大于等于50”就降价,后来测试发现50公斤的货物运费算错了,赶紧改成“严格大于50”,但改的时候漏了一处地方,有个三元运算符还是用了>=,导致部分50公斤货物还是按低价算,最后无法拿到群发。这种条件判断的细节一不留神就出错,尤其是代码里多个地方用到费率计算的时候。
解决这些问题的时候,一开始到处补漏洞。比如输入问题,最开始只是在读数据后加了一行if判断货物数量不能为负,但后来发现用户可能输字母,又手忙脚乱地加try-catch包住所有数值解析。后来学聪明了,把输入校验抽成一个方法,专门处理正数检查和格式转换,这才让代码稍微能看。不过时间不够,运费计算部分还是留着double,只能祈祷测试用例别出极端小数。
分析SourceMonitor报表

优点:
代码行数为 170,相对简洁。
总行数 170(行业参考值 <=200),规模可控,无明显冗余代码,整体结构紧凑。
方法平均语句数为 6.25,方法长度较为适中。
方法平均语句数 6.25(参考值 <=8),逻辑块拆分合理,单个方法代码量少,符合简洁性原则,便于维护和调试。
分支语句占比低,逻辑清晰。
分支语句占比 3.2%(参考值 <=15%),条件判断少,代码结构简单,减少嵌套复杂度,提升可读性,降低维护成本。
最大复杂度低,执行路径清晰。
最大复杂度为 2(参考值<=5),逻辑简单,无多层嵌套循环或条件,代码执行路径明确,调试和理解成本低。
缺点:
注释覆盖率极低,可读性差。
仅 2.4% 的代码行含注释(参考值>=20%),关键业务逻辑(如货物重量计算、运费累加规则、订单输出格式)缺乏说明后续维护者难以快速理解代码意图,尤其是复杂业务场景(如多货物明细的循环输出)未标注,增加理解成本。
第二次航空货运管理系统:

点击查看代码
import java.util.*;
abstract class User {
    protected Person person;
    public User(Person person) {
        this.person = person;
    }
    public abstract double getDiscountRate();
    
    public String getCustomerInfo() {
        return person.getName() + "(" + person.getNumber() + ")";
    }
}
class PersonalUser extends User {
    public PersonalUser(Person person) {
        super(person);
    }
    @Override
    public double getDiscountRate() {
        return 0.9;
    }
}
class CorporateUser extends User {
    public CorporateUser(Person person) {
        super(person);
    }
    @Override
    public double getDiscountRate() {
        return 0.8;
    }
}
abstract class Cargo {
    protected String id;
    protected String name;
    protected double length;
    protected double width;
    protected double height;
    protected double grossWeight;
    public Cargo(String id, String name, double length, double width, 
                double height, double grossWeight) {
        this.id = id;
        this.name = name;
        this.length = length;
        this.width = width;
        this.height = height;
        this.grossWeight = grossWeight;
    }
    public double getVolumeWeight() {
        return (length * width * height) / 6000;
    }
    public double getChargeableWeight() {
        return Math.max(getVolumeWeight(), grossWeight);
    }
    public abstract double getRate();
    public double getFreight() {
        return getChargeableWeight() * getRate();
    }
    public String getName() {
        return name;
    }
}
class GeneralCargo extends Cargo {
    public GeneralCargo(String id, String name, double length, double width,
                        double height, double grossWeight) {
        super(id, name, length, width, height, grossWeight);
    }
    @Override
    public double getRate() {
        double weight = getChargeableWeight();
        if (weight < 20) return 35;
        if (weight < 50) return 30;
        if (weight < 100) return 25;
        return 15;
    }
}
class DangerousCargo extends Cargo {
    public DangerousCargo(String id, String name, double length, double width,
                          double height, double grossWeight) {
        super(id, name, length, width, height, grossWeight);
    }
    @Override
    public double getRate() {
        double weight = getChargeableWeight();
        if (weight < 20) return 80;
        if (weight < 50) return 50;
        if (weight < 100) return 30;
        return 20;
    }
}
class ExpressCargo extends Cargo {
    public ExpressCargo(String id, String name, double length, double width,
                        double height, double grossWeight) {
        super(id, name, length, width, height, grossWeight);
    }
    @Override
    public double getRate() {
        double weight = getChargeableWeight();
        if (weight < 20) return 60;
        if (weight < 50) return 50;
        if (weight < 100) return 40;
        return 30;
    }
}
class Flight {
    private String number;
    private double maxLoad;
    public Flight(String number, double maxLoad) {
        this.number = number;
        this.maxLoad = maxLoad;
    }
    public String getNumber() {
        return number;
    }
    public double getMaxLoad() {
        return maxLoad;
    }
}
public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        Person customer = new Person(
            sc.nextLine(), sc.nextLine(), 
            sc.nextLine(), sc.nextLine()
        );
        User user = createUser(sc.nextLine(), customer);
        List<Cargo> cargos = new ArrayList<>();
        int cargoCount = Integer.parseInt(sc.nextLine());
        for (int i = 0; i < cargoCount; i++) {
            String type = sc.nextLine();
            cargos.add(createCargo(type, sc));
        }
        Flight flight = new Flight(
            sc.nextLine(), 
            Double.parseDouble(sc.nextLine())
        );
        sc.nextLine(); sc.nextLine(); sc.nextLine();
        double totalWeight = cargos.stream()
            .mapToDouble(Cargo::getChargeableWeight)
            .sum();
        if (totalWeight > flight.getMaxLoad()) {
            System.out.printf("The flight with flight number:%s has exceeded its load capacity and cannot carry the order.\n",
                             flight.getNumber());
            return;
        }
        String[] orderInfo = new String[8];
        for (int i = 0; i < 8; i++) {
            orderInfo[i] = sc.nextLine();
        }
        double totalFreight = cargos.stream()
            .mapToDouble(Cargo::getFreight)
            .sum();
        totalFreight *= user.getDiscountRate();
        printOrder(user, cargos, flight, orderInfo, totalWeight, totalFreight);
    }
    private static User createUser(String type, Person person) {
        switch (type) {
            case "个人": return new PersonalUser(person);
            case "集团": return new CorporateUser(person);
            default: throw new IllegalArgumentException("无效的用户类型");
        }
    }
    private static Cargo createCargo(String type, Scanner sc) {
        String[] params = new String[6];
        for (int i = 0; i < 6; i++) {
            params[i] = sc.nextLine();
        }
        switch (type) {
            case "普通货物":
                return new GeneralCargo(params[0], params[1], 
                    Double.parseDouble(params[2]), 
                    Double.parseDouble(params[3]),
                    Double.parseDouble(params[4]), 
                    Double.parseDouble(params[5]));
            case "危险货物":
                return new DangerousCargo(params[0], params[1], 
                    Double.parseDouble(params[2]), 
                    Double.parseDouble(params[3]),
                    Double.parseDouble(params[4]), 
                    Double.parseDouble(params[5]));
            case "加急货物":
                return new ExpressCargo(params[0], params[1], 
                    Double.parseDouble(params[2]), 
                    Double.parseDouble(params[3]),
                    Double.parseDouble(params[4]), 
                    Double.parseDouble(params[5]));
            default: throw new IllegalArgumentException("无效的货物类型");
        }
    }
    
    private static void printOrder(User user, List<Cargo> cargos, Flight flight,
                                  String[] orderInfo, double totalWeight, double totalFreight) {
        System.out.println("客户:" + user.getCustomerInfo() + "订单信息如下:");
        System.out.println("-----------------------------------------");
        System.out.println("航班号:" + flight.getNumber());
        System.out.println("订单号:" + orderInfo[0]);
        System.out.println("订单日期:" + orderInfo[1]);
        System.out.println("发件人姓名:" + orderInfo[3]);
        System.out.println("发件人电话:" + orderInfo[4]);
        System.out.println("发件人地址:" + orderInfo[2]);
        System.out.println("收件人姓名:" + orderInfo[5]);
        System.out.println("收件人电话:" + orderInfo[6]);
        System.out.println("收件人地址:" + orderInfo[7]);
        System.out.printf("订单总重量(kg):%.1f\n", totalWeight);
        System.out.printf("微信支付金额:%.1f\n\n", totalFreight);
        
        System.out.println("货物明细如下:");
        System.out.println("-----------------------------------------");
        System.out.println("明细编号\t货物名称\t计费重量\t计费费率\t应交运费");
        int index = 1;
        for (Cargo cargo : cargos) {
            System.out.printf("%d\t%s\t%.1f\t%.1f\t%.1f\n",
                             index++,
                             cargo.getName(),
                             cargo.getChargeableWeight(),
                             cargo.getRate(),
                             cargo.getFreight());
        }
    }
}

类图设计
因为没有写出这次题目,老是过不去测试案例,所以就没有绘画完整的类图,只展示类图的思路:

点击查看代码
abstract class User {
    - person: Person
    + User(person: Person)
    + abstract getDiscountRate(): double
    + getCustomerInfo(): String
}

abstract class Cargo {
    - id: String
    - name: String
    - length: double
    - width: double
    - height: double
    - grossWeight: double
    + Cargo(id: String, name: String, length: double, width: double, height: double, grossWeight: double)
    + getVolumeWeight(): double
    + getChargeableWeight(): double
    + abstract getRate(): double
    + getFreight(): double
    + getName(): String
}

class Flight {
    - number: String
    - maxLoad: double
    + Flight(number: String, maxLoad: double)
    + getNumber(): String
    + getMaxLoad(): double
}

class Person {
    - name: String
    - phone: String
    - address: String
    - number: String
    + Person(name: String, phone: String, address: String, number: String)
    + getName(): String
    + getPhone(): String
    + getAddress(): String
    + getNumber(): String
}

' 定义具体类
class PersonalUser {
    + PersonalUser(person: Person)
    + getDiscountRate(): double
}

class CorporateUser {
    + CorporateUser(person: Person)
    + getDiscountRate(): double
}

class GeneralCargo {
    + GeneralCargo(id: String, name: String, length: double, width: double, height: double, grossWeight: double)
    + getRate(): double
}

class DangerousCargo {
    + DangerousCargo(id: String, name: String, length: double, width: double, height: double, grossWeight: double)
    + getRate(): double
}

class ExpressCargo {
    + ExpressCargo(id: String, name: String, length: double, width: double, height: double, grossWeight: double)
    + getRate(): double
}

class Main {
    + main(args: String[]): void
    - createUser(type: String, person: Person): User
    - createCargo(type: String, sc: Scanner): Cargo
    - printOrder(user: User, cargos: List<Cargo>, flight: Flight, orderInfo: String[], totalWeight: double, totalFreight: double): void
}

' 定义继承关系
User <|-- PersonalUser
User <|-- CorporateUser
Cargo <|-- GeneralCargo
Cargo <|-- DangerousCargo
Cargo <|-- ExpressCargo

写题历程:
这次作业真是让我栽了大跟头,最后代码都没能跑通,只能眼睁睁看着截止时间过去。一开始信心满满,觉得前几次作业都搞定了,这次肯定没问题,结果从用户类型处理开始就踩坑。题目要求区分个人和集团用户,折扣率不同,我照着模板写了抽象类和子类,但测试的时候发现,用户类型输入“个人”或者“集团”之外的词,导致一直无法通过测试用例。
一、货物类型的设计更是困难。题目要求用策略模式实现不同货物的运费计算,我写了普通、危险、加急三个子类,每个类里硬编码了费率判断。结果测试时发现,危险货物在50kg到100kg之间的费率应该是30,但我代码里写成25,和题目要求完全不符。想改的时候发现这些费率数字散落在三个类里,得一个个翻,一着急改漏了一个地方,导致部分重量区间的运费还是错的。
二、最让我烦恼的是航班载重校验。代码里累加所有货物的计费重量时,用了stream的sum()方法,但和预期结果对不上。最后交上去的版本连一半用例都没过,甚至有个别案例输出格式混乱,在IDE里显示正常,但在pta上数字对不齐,直接被判格式错误。
这次作业让我意识到,代码的鲁棒性真的很重要。“理论上不会输错”的输入,测试时一定会出错;“差不多就行”的浮点计算,最后一定会差一点点。
分析SourceMonitor报表

优点:
代码规模可控:
总行数 170(行业参考值≤200),结构紧凑,无冗余,符合中小型项目代码量标准,便于整体管理与维护。
方法粒度合理:
方法平均语句数 6.25(参考值≤8),单个方法逻辑清晰,拆分合理(如createUser、createCargo、printOrder等独立方法),降低维护复杂度,符合高内聚原则。
逻辑复杂度低:
分支语句占比 3.2%(参考值≤15%),条件判断少(仅航班载重检查),代码结构扁平,无嵌套冗余,提升可读性。
最大复杂度 2(参考值≤5),执行路径单一(无多层循环 / 条件嵌套),调试时可快速定位问题,理解成本极低。
缺点:
注释严重缺失:
仅 2.4% 代码含注释(参考值≥20%),关键逻辑无说明
三、踩坑心得​​:
​​1.输入处理是万恶之源​​:前几次作业总以为用户会“按规矩输入”,结果一到测试全是负数、字母、乱序,程序秒崩。尤其是第三次作业,输入顺序错一位就全盘皆输,还因为没校验货物数量,差点让“-3件货物”成功下单。
​​浮点数的精度陷阱​​:从第三次到第六次,每次用double算钱或重量,总有一两个测试用例卡在0.0001的误差上。最惨的是第六次作业,30.3kg的航班载重因为精度误差判成超重,被迫熬夜改BigDecimal,结果发现除法舍入规则写反了。
​​2.设计模式“一看就会,一写就废”​​:第二次作业的策略模式以为懂了,结果第六次作业的运费策略还是硬编码在类里,想加新类型得改一堆代码,完全违背开闭原则。
​​边界条件总能给你惊喜​​:电梯作业里最高层有人按“上行”,航班载重刚好等于总重量,50kg的运费分段点……这些边界情况测试时必现原形,而我总是写完才想起来补。
​​四、自我反思​​
​​1.总想先把功能跑通再优化,结果核心逻辑和输入处理、异常校验混在一起。
2.​​以为double能应付所有计算,直到作业三的运费始终不正确才懂BigDecimal的重要性;以为“策略模式=接口+实现类”,直到第六次作业想扩展货物类型时,才发现自己根本没吃透设计模式。
​​3.每次写完代码随便输两三个“理想数据”就交。第六次作业拖到截止前两夜才开始写,发现输入处理和策略模式的设计漏洞时,马上改代码,最后连基础功能都没跑通。
​​五、总结​​
​​输入校验的必要性​​:多次作业因未严格检查输入合法性导致程序崩溃或逻辑错误,今后需优先实现输入过滤模块,对所有用户输入进行格式和范围校验。
​​浮点计算的精确性​​:三次作业因使用double类型累加出现精度问题,最终需改用BigDecimal并统一舍入规则,避免因微小误差影响业务逻辑。
​​设计模式的实际应用​​:初期对策略模式、工厂模式的理解停留在理论层面,实际编码时仍硬编码逻辑,后续需通过实践案例(如动态扩展货物类型)深入掌握设计模式的核心思想。
​​边界条件的全覆盖测试​​:未充分考虑极端情况,导致部分用例失败,今后需提前列出边界条件清单并针对性测试。
​​代码结构与时间管理​​:核心逻辑与输入/输出代码混杂,维护困难。后续应分层开发:先设计数据结构和接口,再实现输入解析与业务逻辑,最后处理输出格式化。
​​改进方向​​:
​​输入标准化​​:封装工具类统一处理输入校验与转换,减少重复代码。
​​设计文档先行​​:编码前绘制类图或流程图,明确模块职责与交互关系。
​​测试驱动开发​​:针对关键逻辑编写单元测试,确保核心功能稳定。
​​分阶段提交​​:将复杂作业拆分为输入模块、核心逻辑、输出模块分阶段实现,降低后期调试压力。
​​通过六次作业实践,我认识到代码健壮性、可维护性与精确性需从设计阶段开始重视,逐步养成严谨的编码习惯。
建议:
希望老师能多给一些测试数据,这样我就能知道代码到底错在哪,也有修改的方向。
每次作业提交结束后,希望老师能告诉我们代码主要的问题在哪。
​​

posted @ 2025-05-25 17:54  夏宇航  阅读(14)  评论(0)    收藏  举报