2018年系统分析与设计—个人项目作业
题目来自于 :https://edu.cnblogs.com/campus/xnsy/2018Systemanalysisanddesign/homework/2120
本次作业Gitee项目地址 :https://gitee.com/moxxxmo/WordCount
一、项目简介
1. 本项目的需求:
通过程序设计,编写一个可执行文件exe能够对源程序文件进行统计字符数、单词数、行数,统计结果可以以指定的格式输出到默认文件中。
2.参数及其约定如下:
基本功能:
扩展功能:
wc.exe -s //递归处理目录下符合条件的文件
wc.exe -a file.c //返回代码行 / 空行 / 注释行
wc.exe -e stopList.txt //停用词表,统计文件单词总数时,不统计该表中的单词
高级功能:
wc.exe -x //该参数单独使用,如果存在参数,则显示图形界面,用户通过这个界面选取单个文件,程序就会显示文件的字符数、单词数、行数等。
二、完成作业过程:
1. 编写PSP
PSP要在项目开始之前和项目结束时进行编写,要根据自己的能力和任务量编写。
PSP2.1 | PSP阶段 | 预估耗时(分钟) | 实际耗时(分钟) |
Planning | 计划 | 60 | 40 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 40 |
Development | 开发 | 450 | 700 |
· Analysis | · 需求分析 (包括学习新技术) | 90 | 120 |
· Design Spec | · 生成设计文档 | 40 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 15 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 10 |
· Design | · 具体设计 | 60 | 60 |
· Coding | · 具体编码 | 130 | 350 |
· Code Review | · 代码复审 | 30 | 25 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 90 |
Reporting | 报告 | 45 | 80 |
· Test Report | · 测试报告 | 30 | 65 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | .事后总结并提出改进计划 | 5 | 5 |
合计 | 555 | 820 |
分析与总结:我的前后时间出现差别我觉得存在的原因在于我不能正确的预估我学习一个新知识和如何将这个知识快速地运用进编码的时间,所以主要的时间差别在需求分析和编码上。
2. 解题思路
在我拿到这个项目之后,我先对题目的要求进行了分析。然后决定了要使用java语言来实现,此项目中需要运用到java中对文件的操作,因为我没有学习过这部分的内容,所以我知道我的第一步要做的就是先学习java文件操作的知识。第二步就是分析整个项目如何进行设计,从类方面划分的话,我认为可以分为两个类,一个是主函数类(MainCount),一个是功能函数类(WordCount)。然后在功能函数类中将基本功能中要求的四个命令实现,以四个函数的方式进行实现,分别为统计字符数函数、统计行数函数、统计单词数函数、文件输出函数。第三步、思考如何进行单元测试,单元测试的设计可以在写具体功能代码实现之前,也可以在写完具体功能之后。在这里项目里,我想的是采用在写完一个具体功能之后再写单元测试。第四步,学习如何将java project 打包成exe文件,这部分的知识对我来说也是一个新的知识,所以也需要时间进行学习。
所有资料: https://blog.csdn.net/qq_39615545/article/details/77244134 (Java文件与字节流操作)
https://blog.csdn.net/java958199586/article/details/6943033(Java流与文件操作)
http://www.cnblogs.com/yichenglongblog/p/8608408.html (题目参考资料)
https://blog.csdn.net/sunkun2013/article/details/13167099 (文件打包参考资料)
3. 程序设计实现
(1)项目的总体结构
描述:整个项目是在eclipse下建立了一个java project项目然后创建了两个java类,分别为主函数类(MainCount)和功能类(WordCount),主函数类通过调用功能函数类,从控制台接受 了用户输入的指令后,将指令进行解析,然后再对解析的指令调用相对应的WordCount类中的方法进行处理,从而实现程序的功能。
(2) 类的详细设计:
WordCount类:
描述:charCount、lineCount、wordCount这三个整型变量分别用来存储最后统计出的总字符数、总行数和总单词数。
outResult这个字符型变量用来存储最后的字符串结果。
LineCommand统计总行数方法,传入参数文件路径集合,得到统计出的总行数。
CharCommand统计字符方法,传入参数文件路径集合,得到统计出的字符数。
wordCommand统计单词方法,传入参数文件路径集合,得到统计出的单词数。
OutputFile输出文件方法,传入参数输出的文本文件和查询结果。
MainCount类:
描述:main()函数是怎么程序执行的主函数,里面含有一个args[],在该数组里包含了用户在cmd命令行里输入的命令,各个命令以空格为斩断的形式,存入数组。该函数也是通过遍历命令数组,得到指令从而去调用WordCount里面所对应的方法。
(3) 类间关系图:
4. 代码实现
首先介绍一下四个功能的具体实现:
- CharCommand方法
统计字符数方法,运用java对文件操作的技术,将指定的文件读出来然后一行一行的读取字符并计数。
//统计总字符 public String CharCommand(Set<String> file_paths) { outResult = ""; int bytes = 0; byte[] temp = new byte[20*1024];//用于存储所读取的数据的字节数组 FileInputStream in = null; //文件字符输入流对象 try { for(String file_path:file_paths)//遍历文件集合 { in = new FileInputStream(file_path);//实例化文件字符输入流对象 while((bytes = in.read(temp, 0, temp.length))!=-1)//得到文件中的所有字符 { charCount += bytes;//统计累计的字符数 } outResult += file_path+", 字符数:"+charCount+" ";//拼接字符串用于得到最后的输出结果 charCount = 0; } }catch(FileNotFoundException e) { e.printStackTrace(); }catch(IOException e) { e.printStackTrace(); }finally { try { in.close(); }catch(IOException e) { e.printStackTrace(); } } return outResult; }
- LineCommand方法
统计行数方法,此方法的实现比较简单,也是运用java对文件操作的技术,将指定的文件读出来然后读一行就计数。
//统计总行数 public String LineCommand(Set<String> file_paths) { outResult = ""; //用于输出最后的统计结果 FileInputStream in = null; //声明字符输入流 InputStreamReader isr = null; //声明字节输入流 BufferedReader bdr = null; //声明缓存文本读取 try { for(String file_path:file_paths)//遍历路径集合中的每一项 { in = new FileInputStream(file_path);//实例化文件字符输入流对象 isr = new InputStreamReader(in);//实例化字节输入流对象 bdr = new BufferedReader(isr);//实例化缓存输入流对象 while(bdr.readLine()!=null) //一次读取一行的字符 { lineCount++; //统计累计的行数 } outResult += file_path+", 行数:"+lineCount+" ";//拼接字符串用于得到最后的输出结果 lineCount = 0;//可能多次调用,所以需要初始化 } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }catch(IOException e) { e.printStackTrace(); } finally { try { in.close(); isr.close(); bdr.close();//关闭流操作 }catch(IOException e) { e.printStackTrace(); } } return outResult; }
- wordCommand方法
统计单词数方法,在统计单词数的时候,有一个明确的限制条件就是只能以空格和逗号作为判断单词的标准,所以这里还需要用到正则表达式 同时与需要运用java对文件操作的技术,大体的思路与实现字符计数差不多,就在每一行读出后用split方法分割字符串 。
//统计有多少个单词 public String wordCommand(Set<String> file_paths) { outResult = ""; StringBuffer sb = new StringBuffer();//用StringBuffer来进行读取的添加 String temp = "";//缓存字符串 FileInputStream in = null; //声明字符输入流 InputStreamReader isr = null; //声明字节输入流 BufferedReader bdr = null; //声明缓存文本读取 try { for(String file_path:file_paths) { in = new FileInputStream(file_path); isr = new InputStreamReader(in); bdr = new BufferedReader(isr); while((temp = bdr.readLine())!= null)//readLine()会返回字节数 { sb.append(temp);//将读出的数据接在已经保存的数据后面 } temp = sb.toString(); String[] totalWord = temp.split("[\\s+,\\.\n]");//用正则表达式将字符串按单词格式分开,空格和逗号 wordCount = totalWord.length;//所获取的数组长度就是单词个数 outResult += file_path+", 单词数:"+wordCount+" ";//拼接字符串用于得到最后的输出结果 wordCount = 0; } }catch(FileNotFoundException e) { e.printStackTrace(); }catch(IOException e) { e.printStackTrace(); }finally { try { in.close(); isr.close(); bdr.close(); }catch(IOException e) { e.printStackTrace(); } } return outResult; }
- OutputFile方法
文件输出方法,将文件的输出路径和文件所要存储的内容作为函数的参数。创建相应的文件输出流对象和将content内容转换为byte字节
//输出到文件 public boolean OutputFile(String File_path,String Content) { File outputFile = new File(File_path);//创建一个输出文件的对象 FileOutputStream os = null; //声明文件输出流 byte[] test = null; //存储content转化的byte字节数组 if(!outputFile.exists()) { try { outputFile.createNewFile(); //如果输出文件不存在,则需要创建一个新文件 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } try { os = new FileOutputStream(outputFile); test = Content.getBytes(); os.write(test); System.out.println("\n"); } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }catch(IOException e) { e.printStackTrace(); }finally { try { os.close(); }catch(IOException e) { e.printStackTrace(); } } return true; }
再来介绍一下MainCount类里的主函数:
- main()方法
首先我们需要知道的是,用户输入的命令是会存储在main方法里面的一个参数String[] args数组里的,所以我们要做的事情,就是解析这个数组。看用户放了什么东西在里面,自然就会涉及到遍历的操作,遍历怎么数组。还需要知道的一点是,当用户没有指定将输出文件的时候,我们需要将输出的内容放在放在result.txt里面。
public static void main(String[] args) { WordCount WC = new WordCount();//实例化所需要的功能类 Set<String> file_paths = new HashSet<String>();//存储输入文件的集合 String outputFile = "result.txt"; //在默认的情况下输出的文本文件 String Result = ""; //显示查询的结果 int len = args.length; //命令长度 boolean isChar = false;//用于判断是否有统计字符数命令 boolean isLine = false;//用于判断是否有统计行数命令 boolean isWord = false;//用于判断是否有统计单词数命令 for(int i = 0;i < len;i++) { if(args[i].startsWith("-"))//判断命令中出现了那些指令 { switch(args[i]) { case "-c": isChar = true; break; case "-l": isLine = true; break; case "-w": isWord =true; break; case "-o": if(!args[i+1].startsWith("-"))//用于判断后面是否指定了具体的输出文件 { outputFile = args[i+1]; i++; } else { System.out.println("error!"); System.exit(0); } break; default: System.out.println("error !"); break; } } else file_paths.add(args[i]);//将不属于命令的字符串存储在集合里 } if (isWord) { Result += WC.wordCommand(file_paths)+"\r\n";//调用统计单词数方法 } if (isLine) { Result += WC.LineCommand(file_paths)+"\r\n";//调用统计行数方法 } if (isChar) { Result += WC.CharCommand(file_paths)+"\r\n";//调用统计字符数方法 } System.out.println(Result); WC.OutputFile(outputFile, Result); }
5. 测试设计过程
首先,我在设计的时候,想到了在上《构建之法》这门课时,学到的单元测试和回归测试。在每完成一个功能之后,都应该编写一个对应的测试方法,对功能进行测试。所有我在这里新建了一个test包,里面新建了一个UnitTest类用来进行单元测试。我在设计过程中的思路是:首先当用户的所输入的文件存在时是否可以运行,不存在时会不会运行。然后就是可能存在高风险的地方是在字符、行数、单词的计数上面。
下面是我的测试代码的设计:
public class UnitTest { public static void testCharCommand()//测试统计字符数函数 { Set<String> file_paths = new HashSet<String>(); String file1 = "E://file.c";//存在文件 String file2 = "E://file2.c";//不存在文件 file_paths.add(file1); file_paths.add(file2); System.out.println(new WordCount().CharCommand(file_paths)); } public static void testLineCommand()//测试统计行数函数 { Set<String> file_paths = new HashSet<String>(); String file1 = "E://file.c";//存在文件 String file2 = "E://file2.c";//不存在文件 file_paths.add(file1); file_paths.add(file2); System.out.println(new WordCount().LineCommand(file_paths)); } public static void testwordCommand()//测试统计总单词数函数 { Set<String> file_paths = new HashSet<String>(); String file1 = "E://file.c";//存在文件 String file2 = "E://file2.c";//不存在文件 file_paths.add(file1); file_paths.add(file2); System.out.println(new WordCount().wordCommand(file_paths)); } public static void testOutputFile() { String file_path = "result.txt"; String content = "OutputFileTESR"; System.out.println(new WordCount().OutputFile(file_path, content)); } public static void main(String[] args) { // TODO Auto-generated method stub testCharCommand(); testLineCommand(); testwordCommand(); testOutputFile(); }
}
最后就是我的wc.exe 文件的测试过程,但是我只设置了一个测试用例。
- -c命令测试:
默认生成的result.txt文件:
- -l命令测试:
默认生成的result.txt文件:
- -w命令测试:
默认生成的result.txt文件:
- 多命令查询:
生成的指定文件filetest.txt:
6. 参考文献说明
1.关于PSP文件的书写:http://www.cnblogs.com/xinz/archive/2011/10/22/2220872.html
2.源代码管理:http://www.cnblogs.com/xinz/p/5044037.html
3.作业相关知识参考博客:http://www.cnblogs.com/oldBook/p/8592748.html
http://www.cnblogs.com/yichenglongblog/p/8608408.html
https://www.cnblogs.com/Who-Named-Cody/p/8609924.html
三、项目总结:
对于此项目,我只实现了项目所要求的基本功能。在分析过项目的需求后,我发现这些功能虽然看似很简单,但是做起来的工作其实也是很大的,我总结了以下几个原因:一、因为我之前没有深入地学习过java语言,只学习过java语言的基础语法,平时使用java语言做项目也很少。所以在这次项目中,对文件的操作也不是很熟悉,在开始编码之前,我需要对java语言对文件操作这一部分知识进行学习。二、此项目除了对编码有要求以外,还要结合《构建之法》这本书中所讲到关于在软件开发过程中,需要涉及到的软件工程的知识。另外还要撰写PSP表格、需求分析、测试用例、撰写博客。因此整个项目也不是单纯地实现功能那么简单了。并且我知道我的项目也存在了一些不足的地方,例如没有合理的安排我的时间,使得最后做项目的时候有点赶时间,不细致!一个合格软件开发人员,在此次作业中psp表内的内容应该都要做到,所以怎么安排自己时间是件很重要的事。除此之外,我认为还有我自己项目经验不足的原因。
通过本次项目,我收获颇丰。首先,学会了如何使用git管理自己的代码,以及如何通过git将自己的代码上传到码云上面。其次,学会了将自己的源代码程序打包成exe可执行文件,还体会了自己编写单元测试。然后,学习了撰写博客的经验,并实践到自己的博客中。最后,希望自己能够通过学习,使得自己的专业知识能力获得提升,并将自己的思路清晰的展现到博客中去。