软件工程第一次个人编程作业

这个作业属于哪个课程构建之法-2021秋-福州大学软件工程
这个作业要求在哪里2021秋软工实践第一次个人编程作业
这个作业的目标实现一个程序功能,它可以对读入的C或C++代码文件进行不同等级的关键字提取
学号031902126

PSP表格

PSPPersonal Software Process Stages预估耗时(分钟)实际耗时(分钟)
Planning计划60120
Estimate估计这个任务需要多少时间11302000
Development开发--
Analysis需求分析 (包括学习新技术)6060
Design Spec生成设计文档--
Design Review设计复审 (和同事审核设计文档)--
Coding Standard代码规范 (为目前的开发制定合适的规范6060
Design具体设计60120
Coding具体编码7201440
Code Review代码复审--
Test测试(自我测试,修改代码,提交修改)8080
Reporting报告--
Test Report测试报告--
Size Measurement计算工作量3030
Postmortem & Process Improvement Plan事后总结, 并提出过程改进计划6090
Total合计11302000

代码规范制定

传送门

解题思路描述(任务分解)

看到题目的时候,首先想到的是逐行读取文件,利用正则表达式把关键词分割开,然后前两个要求就按顺序查找并计数。后面查找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的其他功能,为接下来的作业做铺垫。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值