#面向对象程序设计PTA作业# 第1、2、3次作业总结(二)

一、前言

本篇承接 #面向对象程序设计PTA作业# 前三次作业(一)继续分析题目集3的第4题


二、设计与分析

题目集3,第4题

(一)原题放送:

7-4日期类设计:
作者 段喜龙
单位 南昌航空大学

参考题目3(题目集3的第三题)和日期相关的程序,设计一个类DateUtil,该类有三个私有属性year、month、day(均为整型数),其中,year∈[1820,2020] ,month∈[1,12] ,day∈[1,31] , 除了创建该类的构造方法、属性的getter及setter方法外,需要编写如下方法:

public boolean checkInputValidity();//检测输入的年、月、日是否合法
public boolean isLeapYear(int year);//判断year是否为闰年
public DateUtil getNextNDays(int n);//取得year-month-day的下n天日期
public DateUtil getPreviousNDays(int n);//取得year-month-day的前n天日期
public boolean compareDates(DateUtil date);//比较当前日期与date的大小(先后)
public boolean equalTwoDates(DateUtil date);//判断两个日期是否相等
public int getDaysofDates(DateUtil date);//求当前日期与date之间相差的天数
public String showDate();//以“year-month-day”格式返回日期值

应用程序共测试三个功能:

  1. 求下n天
  2. 求前n天
  3. 求两个日期相差的天数

注意:严禁使用Java中提供的任何与日期相关的类与方法,并提交完整源码,包括主类及方法(已提供,不需修改)
程序主方法如下:

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int year = 0;
        int month = 0;
        int day = 0;

        int choice = input.nextInt();

        if (choice == 1) { // test getNextNDays method
            int m = 0;
            year = Integer.parseInt(input.next());
            month = Integer.parseInt(input.next());
            day = Integer.parseInt(input.next());

            DateUtil date = new DateUtil(year, month, day);

            if (!date.checkInputValidity()) {
                System.out.println("Wrong Format");
                System.exit(0);
            }

            m = input.nextInt();

            if (m < 0) {
                System.out.println("Wrong Format");
                System.exit(0);
            }

            System.out.print(date.getYear() + "-" + date.getMonth() + "-" + date.getDay() + " next " + m + " days is:");
            System.out.println(date.getNextNDays(m).showDate());
        } else if (choice == 2) { // test getPreviousNDays method
            int n = 0;
            year = Integer.parseInt(input.next());
            month = Integer.parseInt(input.next());
            day = Integer.parseInt(input.next());

            DateUtil date = new DateUtil(year, month, day);

            if (!date.checkInputValidity()) {
                System.out.println("Wrong Format");
                System.exit(0);
            }

            n = input.nextInt();

            if (n < 0) {
                System.out.println("Wrong Format");
                System.exit(0);
            }

            System.out.print(
                    date.getYear() + "-" + date.getMonth() + "-" + date.getDay() + " previous " + n + " days is:");
            System.out.println(date.getPreviousNDays(n).showDate());
        } else if (choice == 3) {    //test getDaysofDates method
            year = Integer.parseInt(input.next());
            month = Integer.parseInt(input.next());
            day = Integer.parseInt(input.next());

            int anotherYear = Integer.parseInt(input.next());
            int anotherMonth = Integer.parseInt(input.next());
            int anotherDay = Integer.parseInt(input.next());

            DateUtil fromDate = new DateUtil(year, month, day);
            DateUtil toDate = new DateUtil(anotherYear, anotherMonth, anotherDay);

            if (fromDate.checkInputValidity() && toDate.checkInputValidity()) {
                System.out.println("The days between " + fromDate.showDate() + 
                        " and " + toDate.showDate() + " are:"
                        + fromDate.getDaysofDates(toDate));
            } else {
                System.out.println("Wrong Format");
                System.exit(0);
            }
        }
        else{
            System.out.println("Wrong Format");
            System.exit(0);
        }        
    }
}

输入格式:
有三种输入方式(以输入的第一个数字划分[1,3]):

  • year month day n //测试输入日期的下n天
  • year month day n //测试输入日期的前n天
  • year1 month1 day1 year2 month2 day2 //测试两个日期之间相差的天数

输出格式:
当输入有误时,输出格式如下:
Wrong Format
当第一个数字为1且输入均有效,输出格式如下:
year1-month1-day1 next n days is:year2-month2-day2
当第一个数字为2且输入均有效,输出格式如下:
year1-month1-day1 previous n days is:year2-month2-day2
当第一个数字为3且输入均有效,输出格式如下:
The days between year1-month1-day1 and year2-month2-day2 are:值
输入样例1:
在这里给出一组输入。例如:
3 2014 2 14 2020 6 14

输出样例1:
在这里给出相应的输出。例如:
The days between 2014-2-14 and 2020-6-14 are:2312

输入样例2:
在这里给出一组输入。例如:
2 1834 2 17 7821

输出样例2:
在这里给出相应的输出。例如:
1834-2-17 previous 7821 days is:1812-9-19

输入样例3:
在这里给出一组输入。例如:
1 1999 3 28 6543

输出样例3:
在这里给出相应的输出。例如:
1999-3-28 next 6543 days is:2017-2-24

输入样例4:
在这里给出一组输入。例如:
0 2000 5 12 30

输出样例4:
在这里给出相应的输出。例如:
Wrong Format

代码长度限制
12 KB
时间限制
10000 ms
内存限制
64MB


(二)题目分析:

这道题是作者第一次在Java中遇到代码量相对其他题目多得多的题目,对Java初学者来说,看上去比较棘手哈,但好在题目还是给出了我们方法,不需要我们自己去设计方法,所以也是一道易懂易上手的题目,但本着学习的态度,我们写完代码后还是来做个设计

程序类图及调用关系:

CLASS
这个调用关系看着很简单,但是对于我这个初学者来说,实现这个类的全部功能还是有困难

我的代码:

My

    import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int year = 0;
        int month = 0;
        int day = 0;
    int choice = input.nextInt();

    if (choice == 1) { // test getNextNDays method
        int m = 0;
        year = Integer.parseInt(input.next());
        month = Integer.parseInt(input.next());
        day = Integer.parseInt(input.next());

        DateUtil date = new DateUtil(year, month, day);

        if (!date.checkInputValidity()) {
            System.out.println("Wrong Format");
            System.exit(0);
        }

        m = input.nextInt();

        if (m < 0) {
            System.out.println("Wrong Format");
            System.exit(0);
        }

        System.out.print(date.getYear() + "-" + date.getMonth() + "-" + date.getDay() + " next " + m + " days is:");
        System.out.println(date.getNextNDays(m).showDate());
    } else if (choice == 2) { // test getPreviousNDays method
        int n = 0;
        year = Integer.parseInt(input.next());
        month = Integer.parseInt(input.next());
        day = Integer.parseInt(input.next());

        DateUtil date = new DateUtil(year, month, day);

        if (!date.checkInputValidity()) {
            System.out.println("Wrong Format");
            System.exit(0);
        }

        n = input.nextInt();

        if (n < 0) {
            System.out.println("Wrong Format");
            System.exit(0);
        }

        System.out.print(
                date.getYear() + "-" + date.getMonth() + "-" + date.getDay() + " previous " + n + " days is:");
        System.out.println(date.getPreviousNDays(n).showDate());
    } else if (choice == 3) {    //test getDaysofDateUtils method
        year = Integer.parseInt(input.next());
        month = Integer.parseInt(input.next());
        day = Integer.parseInt(input.next());

        int anotherYear = Integer.parseInt(input.next());
        int anotherMonth = Integer.parseInt(input.next());
        int anotherDay = Integer.parseInt(input.next());

        DateUtil fromDateUtil = new DateUtil(year, month, day);
        DateUtil toDateUtil = new DateUtil(anotherYear, anotherMonth, anotherDay);

        if (fromDateUtil.checkInputValidity() && toDateUtil.checkInputValidity()) {
            System.out.println("The days between " + fromDateUtil.showDate() +
                    " and " + toDateUtil.showDate() + " are:"
                    + fromDateUtil.getDaysofDates(toDateUtil));
        } else {
            System.out.println("Wrong Format");
            System.exit(0);
        }
    }
    else{
        System.out.println("Wrong Format");
        System.exit(0);
    }
}

}

class DateUtil {

private int year;
private int month;
private int day;
private int[] mon_maxnum = new int[]{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

public DateUtil() {

}

public DateUtil(int year, int month, int day) {
    this.year = year;
    this.month = month;
    this.day = day;
}

public int getYear() {
    return year;
}

public int getMonth() {
    return month;
}

public int getDay() {
    return day;
}

public void setYear(int year) {
    this.year = year;
}

public void setMonth(int month) {
    this.month = month;
}

public void setDay(int day) {
    this.day = day;
}

public boolean isLeapYear(int year) {
    boolean flag = false;
    if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
        flag = true;
    }
    return flag;
}

public boolean checkInputValidity() {
    boolean flag = true;
    if (this.year < 1820 || this.year > 2020 || this.month < 1 || this.month > 12) {
        flag = false;
    } else if (isLeapYear(this.year)) {
        if (this.month != 2) {
            if (this.day < 0 || this.day > mon_maxnum[this.month]) {
                flag = false;
            }
        } else {
            if (this.day > 29 || this.day < 1) {
                flag = false;
            }
        }
    } else {
        if (this.day < 1 || this.day > mon_maxnum[this.month]) {
            flag = false;
        }
    }
    return flag;
}

public DateUtil getNextNDays(int n) {// 下n天
    int N = n;
        for(int i = 0;N/365 != 0; i++){
            if(isLeapYear(this.year+1)){
                this.year++;
                N = N - 366;
            }else{
                this.year++;
                N = N - 365;
            }
        }

    for(int i = 0; i<N; i++) {
        if (this.month == 12 && this.day == 31) {// 最后一天的下一天
            this.year++;
            this.month = 1;
            this.day = 1;
        } else if (!isLeapYear(this.year)) {// 非最后一天的平年的下一天
            if (this.day + 1 > mon_maxnum[this.month]) {
                day = 1;
                this.month++;
            } else {
                this.day++;
            }
        } else if (isLeapYear(this.year)) {// 非最后一天闰年的下一天
            if (this.month == 2) {
                if (this.day + 1 > 29) {
                    day = 1;
                    this.month++;
                } else {
                    this.day++;
                }
            } else {
                if (this.day + 1 > mon_maxnum[this.month]) {
                    day = 1;
                    this.month++;
                } else {
                    this.day++;
                }
            }
        }
    }
    return DateUtil.this;
}
public String showDate(){// 以“year-month-day”格式返回日期值
    String time = year+"-"+month+"-"+day;
return time;
}
public boolean equalTwoDates(DateUtil date){// 判断日期相等
    boolean result = false;
    if (date.year == this.year && date.month == this.month && date.day == this.day){
        result = true;
    }
 return result;
}

public boolean compareDates(DateUtil date){// 当前日期比date大返回true
    boolean flag = false;
    if(this.year > date.year){
        flag = true;
    }else if(this.year == date.year && this.month > date.month){
        flag = true;
    }else if(this.year == date.year && this.month == date.month && this.day > date.day){
        flag = true;
    }
    return flag;
}

public int getDaysofDates(DateUtil date){
    int n = 0;
    if(!compareDates(date)){
        for(n = 0; !equalTwoDates(date); n++){
            getNextNDays(1);
        }
    }else{
        for(n = 0; !equalTwoDates(date); n++){
            getPreviousNDays(1);
        }
    }
    return n;
}

public DateUtil getPreviousNDays(int n){// 取得year-month-day的前n天日期
    int N = n;
        for(int i = 0; N/365 != 0; i++){
            if(isLeapYear(this.year-1)){
                this.year--;
                N = N - 366;
            }else{
                this.year--;
                N = N - 365;
            }
        }

    for(int i = 0;i < N; i++) {
        if (this.month == 1 && this.day == 1) {
            this.month = 12;
            this.day = 31;
            this.year--;
        } else if (!isLeapYear(this.year)) {
            if (this.day == 1) {
                this.day = mon_maxnum[this.month - 1];
                this.month--;
            } else {
                this.day--;
            }
        } else if (isLeapYear(this.year)) {
            if (this.month == 3 && this.day == 1) {
                this.day = 29;
                this.month--;
            } else if (this.day == 1) {
                this.day = mon_maxnum[this.month - 1];
                this.month--;
            }else{
                this.day--;
            }
        }
    }
 return DateUtil.this;
}

}

// 救命 为什么这俩测试点就不给过?????????????????????????????????????????????????????????????????????

测试结果:

result

看到最后一行注释,应该能想象到我当时的心境真的很难受,有两个测试点死活过不了(折腾了两个半小时),其实一开始大家基本上都是这两个测试点不通过,但是他们的问题很快就通过测试用例找到了,很不巧,我的问题藏得很深,没有一个我想得到的用例是异常的,当然包也括别人容易出问题的用例

我自己的测试用例:

用例1输入:1 1999 3 28 2147483647
用例1输出:1999-3-28 next 2147483647 days is:5881609-10-5
对应图中测试点5


用例2输入:2 1834 2 17 2147483647
用例2输出:1834-2-17 previous 2147483647 days is:-5877777-8-10
对应图中测试点8

这个两个日期看起来很奇怪ah,但是这就是正确的答案
我甚至怀疑这两个测试点在误导我

后续又进行了其他用例测试:
输入n=0,期望输出为当天的日期。
输入n=1,期望输出为明天的日期。
输入n=365,期望输出为一年后的日期。
输入n=366,期望输出为一年零一天后的日期(考虑闰年)。
输入n=-1,期望输出为昨天的日期。
输入n=30,期望输出为一个月后的日期(考虑不同月份的天数)。
输入n=32,期望输出为一个月零两天后的日期。
输入n=3650,期望输出为十年后的日期。
输入n=10000,期望输出为二十七年零三百六十五天后的日期。
输入n=999999,期望输出为二千七百三十一年零一百二十九天后的日期(考虑跨越多个世纪)

结果都是正确的输出,由于这些测试点已经通过平台的测试,不再一一展示
感兴趣的话,或者你有认为可能出现漏洞但我没有考虑到的测试方案,可以复制我的代码在自己的编译器上测试,如果方便请留言告诉我,这两个测试点前前后后真的折磨了我很长时间


(三)代码不足分析:

1. 圈复杂度:


左边的Kiviat图中可以明显看到 Max Complexity 指标已经远超合理的标准

下面看一下Date类中的方法具体的圈复杂度:

可以看到有两个方法的圈复杂度达到了15,这是一个明显需要改进的地方

方法 圈复杂度
DateUtil.checkInputValidity() 15
DateUtil.getNextNDays() 15

三、改进建议

以DateUtil.getNextNDays()方法为例:

public DateUtil getNextNDays(int n) {// 下n天

int N = n;
int yearDays = 365;
if(isLeapYear(this.year)){ // 如果是闰年,天数变为366
yearDays = 366;
}
int yearDiff = N / yearDays; // 计算需要增加的年份
this.year += yearDiff;
N = N % yearDays; // 剩余天数

while(N > 0){ // 循环增加天数,直到达到N天
    int monthDays = getMonthDays(this.year, this.month);
    int diff = monthDays - this.day + 1;
    if(diff <= N){ // 如果剩余天数小于等于一个月的天数,月份不变,天数加上剩余天数
        this.day += diff;
        N -= diff;
    }else{ // 否则,月份加1,天数变为1
        this.month += 1;
        this.day = 1;
        N -= diff;
    }
    if(this.month > 12){ // 如果月份超过12,年份加1,月份变为1
        this.year += 1;
        this.month = 1;
    }
}
return this.getDate();
}

// 判断是否是闰年
private boolean isLeapYear(int year){
if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0){
return true;
}
return false;
}

// 获取指定年份月份的天数
private int getMonthDays(int year, int month){
int[] monthDays = {31,28,31,30,31,30,31,31,30,31,30,31};
if(isLeapYear(year) && month == 2){ // 如果是闰年的2月,天数为29
return 29;
}
return monthDays[month-1];
}

限于我的编码水平,难以避免会使用很多if语句,最容易想到也最容易实现的是进一步向符合SRP原则靠拢,将职责进一步细化,将功能进一步模块化。这样,这个模块的最大圈复杂度从15降到了3,出错的概率也会降低。

另外,这个类里面有一个方法我从头到尾是没有用到的,是为满足题目规定而写,这也是一个缺陷。


四、踩坑心得

  1. 这道题目如果没有任何设计拿起来就开始写,一定是相当折磨,另外这道题目的测试点开始接近真实软件工程的测试情况。例如整型数最大值这种平时不容易考虑的点,因为作为用户一般不会输入这样的数据,但是作为软件工程师来说这是需要特别注意的地方。

  2. 写注释!写注释!写注释!但凡有一定代码量的代码,不加注释的结果就是几天之后再拿起来,要看半天才能看懂,这个改进后的代码我不只是做了进一步的模块化,还写了更多注释。

    我最讨厌的事:
    ①别人写代码的时候不写注释
    ②让我写代码的时候写注释


  3. 如果时间允许,尽可能的模块化,并且边写边调试

  4. 要去看编译器抛出的异常,看不懂也要去查一下,不要哪行一报错就开始debug哪行


五、总结

(一)对代码:

在写代码之前,应该对问题有一个大体的分析,作者这几次作业总是看完题目立马着手开始写代码,总觉得写出来一点代码就离解决问题更近一步,但最后一提交代码(逻辑、运行时)错误百出。这样的代码可读性、可维护性、可扩展性都非常差。从这三次作业中,我开始真正体会到软件工程这个专业存在的意义,学一学语法,会写一些代码等等简单的东西,是非软件工程专业的人抽出几天时间自学一下就能学会的,真正有价值的东西在于那些别人也需要下很大功夫才能掌握的设计和算法,在于别人也要花很长时间才能积累出的开发经验,软件工程专业应该是为了去写出各方面数据都很可观的代码,而不是面向结果编程。

(二)对自己:

Java本身就是一个很庞大的东西,第一不能指望老师教语法、规范这些能通过自学掌握的东西,第二在自学的过程中要注意全面系统学习,用到什么学什么的方法不太合适这个阶段学习Java。时刻提醒自己这门专业本身就不是以学习语法为重点。处理问题时一头扎进问题中,忽略其他细节,在错误的方向上疯狂进发,然后写出来一堆屎山代码,为了避免这种情况发生,老师讲到的东西一定要注意

(三)对教师:

我的面向对象程序设计老师是段老师,很爱放狠话,用美剧《光辉岁月》里丹泽尔·华盛顿饰演的主教练那句话来说就是:+"I am the law."(我就是独裁者),我只能说在他班里上课感觉像回到高中,很有内味。

(四)对课程形式:

我觉得这个课上课下组织形式很不错,课下既有自己单干摸索的过程,也有同学之间交流协作的过程;课上只会讲难以自学学会的干货。当然这样的结果就是学生很累,吃得苦中苦方为人上人吧。我比较欣赏的另一点就是这门课的考核方式,不是拿纸和笔在卷子上写++i、i++,而是真正去考查学生编码能力,毕竟这门专业的目的就是培养有优秀编码能力的人,而不是为了培养懂语法的人。


  1. 一般来说圈复杂度大于10的方法存在很大的出错风险。圈复杂度和缺陷个数有高度的正相关:圈复杂度最高的模块和方法,其缺陷个数也可能最多 ↩︎

posted @   一杯大丞汁  阅读(108)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示