这个作业属于哪个课程 | 构建之法-2021秋-福州大学软件工程 |
---|---|
这个作业要求在哪里 | 2021秋软工实践第一次个人编程作业 |
这个作业的目标 | 实现一个程序功能,它可以对读入的C或C++代码文件进行不同等级的关键字提取 |
学号 | 031902126 |
PSP表格
PSP | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 120 |
Estimate | 估计这个任务需要多少时间 | 1130 | 2000 |
Development | 开发 | - | - |
Analysis | 需求分析 (包括学习新技术) | 60 | 60 |
Design Spec | 生成设计文档 | - | - |
Design Review | 设计复审 (和同事审核设计文档) | - | - |
Coding Standard | 代码规范 (为目前的开发制定合适的规范 | 60 | 60 |
Design | 具体设计 | 60 | 120 |
Coding | 具体编码 | 720 | 1440 |
Code Review | 代码复审 | - | - |
Test | 测试(自我测试,修改代码,提交修改) | 80 | 80 |
Reporting | 报告 | - | - |
Test Report | 测试报告 | - | - |
Size Measurement | 计算工作量 | 30 | 30 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 60 | 90 |
Total | 合计 | 1130 | 2000 |
代码规范制定
解题思路描述(任务分解)
看到题目的时候,首先想到的是逐行读取文件,利用正则表达式把关键词分割开,然后前两个要求就按顺序查找并计数。后面查找if else结构和if else-if else结构时,利用栈来解决。
具体任务分解:
1.读文件,并删除注释。
2.利用正则表达式分割关键词并计数。
3.使用栈保存switch、case、{、}用于判断switch结构。
4.使用栈保存if、else if、if、用于判断if else和if else-if else结构。
设计实现过程
作业实现总流程图如下:
上图中重要的函数包括switch case计数和if else计数的函数,这两个函数实现较为接近,下面以前者为例:
代码说明
- all_key函数,这个函数的作用是将文件中的字符串分割为关键字或变量名之类的元素放在列表里,同时也是完成基本要求的函数。该函数的关键部分是
re.split(r'[^a-zA-Z0-9_{}]', line)
使用正则分割了字符串,然后删去其中的空字符串,再循环计数。
# all_ke_count.py
def all_key(word):
count = 0
all_word = []
for line in word:
words = re.split(r'[^a-zA-Z0-9_{}]', line) # 使用正则表达式分割字符串
words = [item for item in filter(lambda t:t != '', words)] # 删去空字符串
if not words: # 计数
continue
all_word.append(words)
for x in words:
if x:
for key in keyWord:
if key == x:
count += 1
break
# print(all_word)
print("total num: ", count)
return all_word
- judge函数,使用循环判断的方式去除注释,如果遇到//就将前面部分切片,后半部分丢弃;如果遇到/* 就做个标记,直到遇到 * /,然后把他们之间的字符全部剔除,即可消去注释。
# keyCount.py
def judge(word_list): # 删去每一行注释的函数,
word_list = addBlank(word_list)
global flag
len_list = len(word_list)
if flag == 0: # 这部分不是注释内部
for x in range(len_list):
if word_list[x] == '/':
if x < len_list - 1:
if word_list[x + 1] == '/':
return word_list[0: x]
elif word_list[x + 1] == '*':
flag = 1
return word_list[0: x]
return word_list
else:
for x in range(len_list):
if word_list[x] == '*':
if x < len_list - 1 and word_list[x + 1] == '/':
flag = 0
if x == len_list - 2:
return
else:
return word_list[x + 2: -1]
return
- sc_count函数,计算switch和case数量的函数,详见“设计实现过程”中第二个设计图。
# switch_case_count.py
def sc_count(word):
all_word = all_key_count.all_key(word)
new_word = keyWord.saveKey(all_word, ['switch', 'case', '{', '}'])
# 最后存储的结果
switch_count = 0
case_count = []
key_stack = [] # 堆栈
switch_pos = [] # 堆栈执行过程中switch在堆栈中的位置
switch_left = [] # switch结构中左括号的数量
out_left = 0 # switch外左括号数量
# switch = 1, case = 2, { = 3, } = 4.
'''for line in new_word:
print(line)'''
for line in new_word:
for x in line:
if x == '{':
if not switch_pos: # 属于外括号
out_left += 1
else: # 属于内括号
key_stack.append(3) # 加入堆栈
switch_left[-1] += 1 # 对应左括号加1
elif x == 'switch':
key_stack.append(1) # switch入栈
switch_pos.append(len(key_stack)) # switch位置入栈
switch_left.append(0) # 这个switch对应的左括号列表可以开始添加元素了
case_count.append(0) # switch对应的case列表可以开始添加了
switch_count += 1
elif x == 'case':
case_count[switch_count-1] += 1 # case + 1
elif x == '}':
if not switch_pos: # 属于外括号
out_left -= 1
else: # 属于内括号
switch_left[-1] -= 1 # 对应左括号-1
if switch_left[-1] == 0: # 当前switch结束
key_stack = key_stack[0:switch_pos[-1]] # 出栈
switch_pos.pop()
- elif_count函数,用于完成拔高要求和终极要求。具体思路与上一个sc_count函数很相似。先把所给列表中关键字’if’, ‘else’, ‘{’, '}‘保留下来,其他删去。之后循环判断:如果是‘if’,直接入栈;如果是‘else’,则判断下一个是不是‘if’,若是则’else if‘入栈,反之’else‘入栈;如果是’{‘,入栈,并保存在栈中位置;如果是’}‘,将最后一个’{‘到栈顶的数据切片,只保留其他部分,并利用正则判断有几个相应结构。最后根据等级判断输出结果。
def elif_count(word, flag):
all_word = switch_case_count.sc_count(word)
new_word = keyWord.saveKey(all_word, ['if', 'else', '{', '}'])
# 存放结果
if_else_num = 0 # 所有if else 的个数
elif_num = 0 # 所有if else if else 的个数
key_stack = [] # 堆栈
# if_left = [] # if结构里面左括号数量
left_pos = [] # 所有左括号在堆栈的位置
# if = 1, else = 2, else-if =3, '{' = 4, '}' = 5.
for line in new_word:
len_line = len(line)
i = 0
while i < len_line:
if line[i] == 'if':
key_stack.append(1) # if入栈
elif line[i] == '{':
left_pos.append(len(key_stack)) # 保存{在栈中的位置
key_stack.append(4) # 入栈
elif line[i] == 'else':
if i < len_line - 1 and line[i + 1] == 'if': # else-if 结构
key_stack.append(3)
i += 1 # 跳过if
else:
key_stack.append(2)
elif line[i] == '}':
temp = key_stack[left_pos[-1]+1:] # 将大括号之间的部分切片
key_stack = key_stack[:left_pos[-1]] # 剩下的部分
left_pos.pop()
for t in range(len(temp)):
temp[t] = str(temp[t])
str_tmp = ''.join(temp) # 转为字符串
# print(temp, key_stack)
pat_1 = re.compile(r'12') # 12结构查找if else结构
pat_2 = re.compile(r'13+2') # 根据13+2结构查找 if else-if else结构
if_else_num += len(pat_1.findall(str_tmp))
elif_num += len(pat_2.findall(str_tmp))
i += 1
print("if-else num: ", if_else_num)
if flag == 4:
print("if-elseif-else num: ", elif_num)
迭代过程描述
v1.0 :最开始只实现了简单的功能,但还有很多地方没有完善,例如类似于int1,int_2被判断为关键字。
v2.0 :增强了对关键字的判断。
v2.1 :增加了删除注释的功能。
v2.2 :可以实现嵌套switch case的判断。
v2.3 :可以实现不完整if else结构的判断。
v3.0 :添加了部分注释,增加了输入判断以及处理文件异常功能。
遇到的困难及解决方法
1.在使用for i in range(x)的语句中,修改i的值并不能达到理想的效果。
解决方法:这个语句的本质是i从0~x-1中选出一个值,即使在循环中修改了i的值,但到执行该语句的时候i还是会按顺序取对应的值。改为使用while循环就可以了。
2.判断嵌套的switch case结构和缺失else的if else结构时,容易判断出错。
解决方法:分割关键词时保留大括号,后面根据括号匹配判断case属于哪个switch或者else属于哪个if。同时可能会出现’else{‘的无法分开的结构,因此需要在’{‘和’}‘前后添加空格。
3.使用str_tmp = ‘’.join(temp)把数字列表转为字符串时报错。
解决方法:join函数只能将字符串类型的列表转为字符串,而temp里面是数字。所以我们要先用str()函数把temp里面的数字转为字符串,再使用join拼接为大字符串。
单元测试截图和描述
单元测试代码如下:
import unittest
from unittest import main
from unittest.mock import patch
import keyCount
class Test_Key(unittest.TestCase):
def test_readFile(self):
with patch('builtins.print') as mocked_print:
keyCount.readFile('test.c', 1)
mocked_print.assert_called_with("total num: ", 48)
keyCount.readFile('test.c', 4)
mocked_print.assert_called_with("if-elseif-else num: ", 1)
if __name__ == '__main__':
main()
直接调用传入参数readFile文件,然后判断输出是否正确(这里不知道如何获取更多的print,所以写的很简陋)结果如下:
覆盖率优化和性能测试
- 覆盖率
使用coverage工具进行覆盖率检测,结果如下:
可以看出all_key_count.py和keyCount.py的覆盖率较低,查看细节如下:
all_key_count.py
可以看出主要是continue的执行次数比较低,对应的应该是空字符数比较少,拿一个用其他方法来消去空字符,提高覆盖率。
keyCount.py
这个部分我主要是在单元测试里输入的路径和等级,所以这部分代码没有完全执行。实际运行时,这部分代码也会被覆盖。还有一部分是出错之后才会用到的代码。
- 性能测试
使用pycharm自带的profile功能进行性能测试,结果如下
总结
这次作业的难度中等,主要考察的是基础语法的运用以及github和git的运用。在完成这次作业的过程中,我对python的语法更加熟练了,也学会了git和GitHub的使用。可能目前的代码还存在一些bug 特性,但我也会在以后的学习中不断完善它,使之成为一个实用的功能。当然,在这次作业中我也发现了自己的一些不足,例如python的掌握还不够熟练,许多地方都需要去百度。那么接下来我会积极学习python的其他功能,为接下来的作业做铺垫。