结对项目:单词计数
一、项目要求
1. 实现统计文件的字符数、单词数、行数。
2. 实现递归处理目录下符合条件的文件,返回更加复杂的数据。
3. 实现图形界面功能。
二、github链接
链接:https://github.com/LLFKirito/WordCount-BIT1120161918
三、结对同学
结对同学:马伯乐(1120161922)
四、项目计划PSP表格
计划安排表
PSP | Personal Software Process Stages | 预估耗时(分钟) |
Planning | 计划 | |
Estimate | 估计这个任务需要多少时间 | 30 |
Development | 开发 | |
Analysis | 需求分析 (包括学习新技术) | 150 |
Design Spec | 生成设计文档 | 90 |
Design Review | 设计复审 | 40 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 |
Design | 具体设计 | 90 |
Coding | 具体编码 | 1000 |
Code Review | 代码复审 | 360 |
Test | 测试(自我测试,修改代码,提交修改) | 240 |
Reporting | 报告 | |
Test Report | 测试报告 | 120 |
Size Measurement | 计算工作量 | 60 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 90 |
合计 | 2300 |
五、设计实现
总体设计
程序流程图如下
程序分为main、define、fundamental、extended、Interface五个文件,分别实现对外接口,基本输入输出操作、基础功能、扩展功能、高级功能。
函数关系图
代码规范
参考教材上的注释示例:
/// <summary> </summary>
/// <param name="?"> </param>
/// <return> </return>
根据种方案写注释。
函数命名采用下划线命名法。
六、解题思路与代码说明
首先对主函数传来的参数进行探测,本项目对错误的检查能力很强,对不同的输入错误有不同的返回报错信息。
check_file_name用于检查文件是否存在并可以读取,返回-1边表示文件不存在,返回-2表示文件存在但没有读取权限,返回1表示文件有访问权限 。
int check_file_name(char filename[])
{
if (access(filename, 0) != 0) { return -1; } // 文件不存在返回-1
if (access(filename, 4) != 0) { return -2; } // 文件不能读取返回-2
return 1;
}
对命令的检查也很严格,有任何错误就能输出错误信息。
int check_order(char order[])
{
int len = strlen(order);
if (len != 2 || order[0] != '-') return -1;
if (order[1] == 'c' || order[1] == 'w' || order[1] == 'l' || order[1] == 'a') {
return 1;
}
else return -1;
}
为了处理有些文件名有空格,在参数上文件名会占用多个参数,特别处理。
int num_para;
for (num_para = argc - 1; num_para > 1; num_para--) {
if ( strcmp(argv[num_para], "-s") == 0 || strcmp(argv[num_para], "-x") == 0
|| strcmp(argv[num_para], "-c") == 0 || strcmp(argv[num_para], "-w") == 0
|| strcmp(argv[num_para], "-l") == 0 || strcmp(argv[num_para], "-a") == 0 )
{break;}
}
char filename[500] = {0};
strcat(filename, argv[num_para+1]);
for (int i = num_para+2;i < argc;i++) {
strcat(filename, " ");
strcat(filename, argv[i]);
}
基本功能处理比较简单:
统计字数:读到正确字符就+1。
int count_character(char file[])
{
int ch_num = 0;
char ch;
freopen(file, "r", stdin);
while ((ch = getchar()) != EOF) {
if (ch != ' '&&ch != '\n'&&ch != '\t')
ch_num++;
}
fclose(stdin);
return ch_num;
}
统计单词:逐个读取,字母被分隔就+1.
int count_word(char file[])
{
int w_num = 0, is_word = 0;
char ch;
freopen(file, "r", stdin);
while ((ch = getchar()) != EOF) {
if ((ch >= 'a'&&ch <= 'z') || (ch >= 'A'&&ch <= 'Z') || ch == '_')
is_word = 1;
else {
if (is_word) {
w_num++;
is_word = 0;
}
}
}
fclose(stdin);
return w_num;
}
统计行数:直接读取'\n'。
int count_line(char file[])
{
int l_num = 0;
char ch;
freopen(file, "r", stdin);
while ((ch = getchar()) != EOF) {
if (ch == '\n')
l_num++;
}
fclose(stdin);
return l_num;
}
递归处理部分,每次搜索当前文件的匹配文件和找到子文件并依次进入。
void search_file(string path, int idx)
{
struct _finddata_t filefind;
string cur = path + "*.*";
int done = 0, handle;
if ((handle = _findfirst(cur.c_str(), &filefind)) != -1) {
while (!(done = _findnext(handle, &filefind))) {
if (strcmp(filefind.name, "..") == 0)
continue;
if ((_A_SUBDIR == filefind.attrib)) { //目录
cur = path + filefind.name + '\\';
search_file(cur, idx); //递归处理
}
else {
int len = strlen(filefind.name);
for (int i = 0; i < len; i++) {
if (filefind.name[i] == '.') {
len = i;
break;
}
}
if (strcmp(filefind.name + len, para[idx] + 1) == 0) {
cur = path + filefind.name;
printf("%s:\n", filefind.name);
for (int i = 1; i < idx; i++)
basic_command(para[i], &cur[0]);
}
}
}
_findclose(handle);
}
}
高级功能通过调用界面程序来执行。
ShellExecuteA(NULL, "open", "WordCount.exe", NULL, NULL, SW_SHOW);
界面部分利用C#搭建,界面实际效果图如下(背景是本人相当喜欢的Sword Art Online):
点击浏览会弹出资源管理器,在此选择文件,点统计输出统计信息。
统计功能直接调用拓展功能的函数,文件资源管理器调用如下:
private void view_Click(object sender, EventArgs e)
{
OpenFileDialog openFileDialog1 = new OpenFileDialog(); //显示选择文件对话框
openFileDialog1.InitialDirectory = "c:\\";
openFileDialog1.Filter = "All files (*.*)|*.*";
openFileDialog1.FilterIndex = 2;
openFileDialog1.RestoreDirectory = true;
if (openFileDialog1.ShowDialog() == DialogResult.OK)
{
this.txtFilepath.Text = openFileDialog1.FileName; //显示文件路径
}
}
调用函数通过直接启动wc.exe实现:
private void count_Click(object sender, EventArgs e)
{
if (string.IsNullOrEmpty(this.txtFilepath.Text.Trim()))
{
MessageBox.Show("请选择文件");
}
string[] arg = new string[10];
arg[0] = "-c"; arg[1] = "-w"; arg[2] = "-l"; arg[3] = "-a";
arg[4] = this.txtFilepath.Text.Trim();
StartProcess(@"wc.exe", arg);
}
七、测试与分析
单元测试
一共设计了11个单元测试:
#include "stdafx.h"
#include "CppUnitTest.h"
#include "..\wc\define.h"
#include "..\wc\define.cpp"
#include "..\wc\fundamental.h"
#include "..\wc\fundamental.cpp"
#include "..\wc\extended.h"
#include "..\wc\extended.cpp"
using namespace Microsoft::VisualStudio::CppUnitTestFramework;
namespace UnitTest1
{
TEST_CLASS(UnitTest1)
{
public:
TEST_METHOD(TestMethod1)
{
int examine_1 = check_file_name("..\\UnitTest1\\TestCase\\test3.c");
int examine_2 = check_file_name("..\\UnitTest1\\TestCase\\test4.cpp");
int examine_3 = check_file_name("..\\UnitTest1\\TestCase\\test8.py");
int examine_4 = check_file_name("..\\UnitTest1\\TestCase\\1120161918.txt");
int examine_5 = check_file_name("..\\UnitTest1\\TestCase\\1120161922.cpp");
Assert::AreEqual(examine_1 == 1, true);
Assert::AreEqual(examine_2 == 1, true);
Assert::AreEqual(examine_3 == 1, true);
Assert::AreEqual(examine_4 == -1, true);
Assert::AreEqual(examine_5 == -1, true);
}
TEST_METHOD(TestMethod2)
{
int examine_1 = check_order("-c");
int examine_2 = check_order("-w");
int examine_3 = check_order("-l");
int examine_4 = check_order("-a");
int examine_5 = check_order("-cxxcsc");
int examine_6 = check_order("c");
Assert::AreEqual(examine_1 == 1, true);
Assert::AreEqual(examine_2 == 1, true);
Assert::AreEqual(examine_3 == 1, true);
Assert::AreEqual(examine_4 == 1, true);
Assert::AreEqual(examine_5 == -1, true);
Assert::AreEqual(examine_6 == -1, true);
}
TEST_METHOD(TestMethod3)
{
char file[] = "..\\UnitTest1\\TestCase\\test1.c";
int num_ch = count_character(file);
Assert::AreEqual(num_ch == 36, true);
}
TEST_METHOD(TestMethod4)
{
char file[] = "..\\UnitTest1\\TestCase\\test1.c";
int num_w = count_word(file);
Assert::AreEqual(num_w == 6, true);
}
TEST_METHOD(TestMethod5)
{
char file[] = "..\\UnitTest1\\TestCase\\test1.c";
int num_l = count_line(file);
Assert::AreEqual(num_l == 5, true);
}
TEST_METHOD(TestMethod6)
{
char file[] = "..\\UnitTest1\\TestCase\\test2.c";
int num_ch = count_character(file);
int num_w = count_word(file);
int num_l = count_line(file);
Assert::AreEqual(num_ch == 44, true);
Assert::AreEqual(num_w == 7, true);
Assert::AreEqual(num_l == 5, true);
}
TEST_METHOD(TestMethod7)
{
char file[] = "..\\UnitTest1\\TestCase\\test3.c";
int num_bl = count_blankline(file);
int num_nl = count_noteline(file);
int num_cl = count_codeline(file);
Assert::AreEqual(num_bl == 2, true);
Assert::AreEqual(num_nl == 0, true);
Assert::AreEqual(num_cl == 3, true);
}
TEST_METHOD(TestMethod8)
{
char file[] = "..\\UnitTest1\\TestCase\\test4.cpp";
int num_ch = count_character(file);
int num_w = count_word(file);
int num_l = count_line(file);
int num_bl = count_blankline(file);
int num_nl = count_noteline(file);
int num_cl = count_codeline(file);
Assert::AreEqual(num_ch == 224, true);
Assert::AreEqual(num_w == 52, true);
Assert::AreEqual(num_l == 15, true);
Assert::AreEqual(num_bl == 2, true);
Assert::AreEqual(num_nl == 0, true);
Assert::AreEqual(num_cl == 13, true);
}
TEST_METHOD(TestMethod9)
{
char file[] = "..\\UnitTest1\\TestCase\\test5.cpp";
int num_ch = count_character(file);
int num_w = count_word(file);
int num_l = count_line(file);
int num_bl = count_blankline(file);
int num_nl = count_noteline(file);
int num_cl = count_codeline(file);
Assert::AreEqual(num_ch == 338, true);
Assert::AreEqual(num_w == 81, true);
Assert::AreEqual(num_l == 37, true);
Assert::AreEqual(num_bl == 15, true);
Assert::AreEqual(num_nl == 0, true);
Assert::AreEqual(num_cl == 22, true);
}
TEST_METHOD(TestMethod10)
{
char file[] = "..\\UnitTest1\\TestCase\\test6.cpp";
int num_ch = count_character(file);
int num_w = count_word(file);
int num_l = count_line(file);
int num_bl = count_blankline(file);
int num_nl = count_noteline(file);
int num_cl = count_codeline(file);
Assert::AreEqual(num_ch == 210, true);
Assert::AreEqual(num_w == 47, true);
Assert::AreEqual(num_l == 21, true);
Assert::AreEqual(num_bl == 7, true);
Assert::AreEqual(num_nl == 0, true);
Assert::AreEqual(num_cl == 14, true);
}
TEST_METHOD(TestMethod11)
{
char file[] = "..\\UnitTest1\\TestCase\\test9.py";
int num_ch = count_character(file);
int num_w = count_word(file);
int num_l = count_line(file);
int num_bl = count_blankline(file);
int num_nl = count_noteline(file);
int num_cl = count_codeline(file);
Assert::AreEqual(num_ch == 202, true);
Assert::AreEqual(num_w == 25, true);
Assert::AreEqual(num_l == 19, true);
Assert::AreEqual(num_bl == 2, true);
Assert::AreEqual(num_nl == 0, true);
Assert::AreEqual(num_cl == 17, true);
}
};
}
测试文件夹:
测试结果:
代码覆盖率如下:
八、PSP表格总结
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
Planning | 计划 | ||
Estimate | 估计这个任务需要多少时间 | 30 | 20 |
Development | 开发 | ||
Analysis | 需求分析 (包括学习新技术) | 150 | 180 |
Design Spec | 生成设计文档 | 90 | 60 |
Design Review | 设计复审 | 40 | 35 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
Design | 具体设计 | 90 | 120 |
Coding | 具体编码 | 1000 | 800 |
Code Review | 代码复审 | 360 | 220 |
Test | 测试(自我测试,修改代码,提交修改) | 240 | 240 |
Reporting | 报告 | ||
Test Report | 测试报告 | 120 | 100 |
Size Measurement | 计算工作量 | 60 | 50 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 90 | 120 |
合计 | 2300 | 1975 |
九、总结与反思
这一次是做结对项目,俩人合作完成单词计数。难点主要在需求分析上,因为项目需求写的不是特别明白,很多细节需要反复讨论以及再次咨询老师才能够完全确定。
这一次需求对界面要求特别高,尤其是需要像调用文件资源管理器一样来处理单个文件。难度其实挺大的。
代码编写是两个人编写,所以需要先约定代码风格,比如采用下划线命名法,分文件写的时候,先规划每个函数实现功能,精确到参数个数及类型,返回值类型,然后分开写的时候就算不知道对方写的部分也能调用函数。结对项目与个人项目不同,它更加考察我们对项目的分工与合作,如何把一个大的项目拆分成多个小项目,如何多人合作,程序连接时如何能正确执行,各个模块间接口如何,这些是本次项目所带给我们最多值得学习的地方。
简而言之,结对项目的完成提升了我们对完成大型项目的能力,规划,分工,合作,这些都是很重要的经验。