软工实践寒假作业(2/2)
这个作业属于哪个课程 | https://edu.cnblogs.com/campus/fzu/2020SPRINGS |
---|---|
这个作业的要求在哪里 | https://edu.cnblogs.com/campus/fzu/2020SPRINGS/homework/10287 |
这个作业的目标 | 设计并开发一个疫情统计软件,学习GitHub的使用,PSP(个人开发流程)的学习使用 |
作业正文 | https://www.cnblogs.com/exusiai-blog/p/12334823.html |
其他参考文献 | .gitignore,github简单教程,菜鸟教程 |
GitHub仓库地址
https://github.com/Exusia-i/InfectStatistic-main
构建之法阅读笔记
第一章 概论
在这一章中,作者为我们介绍了一些关于软件工程的基本知识。
-
软件=程序+软件工程:正是因为对软件开发活动(构建管理、源代码管理、软件设计、软件测试、项目管理)相关的内容的完成,才能完成把整个程序转化成为一个可用的软件的过程。
-
扩展的推论:软件企业=软件+商业模式
-
软件开发的不同阶段:玩具阶段→业余爱好阶段→探索阶段→成熟的产业阶段
-
软件所具有的特殊性:复杂性、不可见性、易变性、服从性、非连续性(由软件的本质所决定的)
软件还有其他特性:
-
有许多不同的程序设计语言、软件工具和软件开发平台
-
存在许多不同的软件开发流程
-
软件团队中存在许多不同的角色
-
软件通常既可以存储在磁带上,也可以存储在CD/DVD上
作者邹欣总结的自己做过的项目的各自特点:
-
Build To Learn:开发软件,构建系统的目的是做进一步的试验,试图发现客观规律或某个试验方法的优点与缺点。这些项目经常是科研论文的基础工作。
-
Build To Show:为了突出地展现某个技术的作用,开发一些演示为目的的软件,这些项目很吸引眼球,经常获得新闻报道,但是功能未必全面。
-
Build To Serve:为了服务一定范围的目标用户而构建的工具等,有时以公开的SDK形式发布。
-
Build To Win:以在市场上赢得用户为目标而构建的软件。这也是种种科学发现,技术突破最好的试金石。这是我在研究院之外的十余年中做的最多的项目类型,也是这本书的英文名字。
第二章 个人技术和流程
2.1 单元测试
重要的单元测试:有效解决程序员对模块功能的误解、疏忽或不了解模块的变化之类的问题,使自己负责的模块功能定义尽量明确,模块的质量得到稳定的、量化的保证。
好的单元测试的标准:
-
在最基本的功能/参数上验证程序的正确性
-
单元测试必须由最熟悉代码的人(程序的作者来写)
-
单元测试过后,机器的状态保持不变
-
单元测试要快(一个测试的运行时间是几秒钟,而不是几分钟)
-
单元测试应该产生可重复、一致的结果
-
独立性——单元测试的运行/通过/失败不依赖于别的测试,可以人为构造数据,以保持单元测试的独立性
-
单元测试应该覆盖所有代码路径
-
单元测试应该集成到自动测试的框架中
-
单元测试必须和产品代码一起保存和维护
单元测试的基础上能够建立关于这一模块的回归测试,目的是:
-
验证新的代码的确改正了缺陷
-
同时验证新的代码有没有破坏模块的现有功能,有没有Regression
2.2 效能分析工具
效能分析方法:抽样和代码注入
2.3 个人开发流程
个人开发流程PSP(Personal Software Process)
特点:
-
不局限于某一种软件技术,而是着眼于软件开发的流程,这样,开发不同应用的软件工程师可以互相比较。
-
不依赖于考试,而主要靠工程师自己收集数据,然后分析、提高。
-
在小型、初创的团队中,很难找到高质量的项目需求,这意味着给程序员的输入质量不高。在这种情况下,程序员的输出(程序/软件)往往质量也不高,然而这并不能全部由程序员负责。
-
PSP依赖于数据(工程师输入数据的时间代价、数据可能遗失或者不准确的风险、可能会出现一些数据不利于工程师本人的情况)
-
PSP目的是记录工程师如何实现需求的效率,而不是记录顾客对产品的满意度,工程师有可能很高效地开发出一个顾客不喜欢的软件。
第三章 软件工程师的成长
3.1 个人能力的衡量与发展
软件工程包括了开发、运用、维护软件的过程中的很多技术、做法、习惯和思想。软件工程把这些相关的技术和过程统一到一个体系中,叫“软件开发流程”,软件开发流程的目的是为了提高软件开发、运营和维护的效率,以及提升用户满意度、软件的可靠性和可维护性。
初级软件工程师的成长包括以下几种:
-
积累软件开发相关的知识,提升技术技能(如对具体技术的掌握,动手能力)。例如:对JAVA、C/C++、C#的掌握,诊断/提高效能的技术,对设备驱动程序、内核调试器的掌握,对于某一开发平台的掌握
-
积累问题领域的知识和经验(例如对医疗或金融行业的了解)
-
对通用的软件设计思想和软件工程思想的理解
-
提升职业技能(区别于技术技能),包括:自我管理的能力、表达交流的能力、与人合作的能力、按质按量完成任务的执行力
-
实际成果——最重要的评价标准
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(min) | 实际耗时(min) |
---|---|---|---|
Planning | 计划 | 60 | 90 |
Estimate | 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 780 | 900 |
Analysis | 需求分析 (包括学习新技术) | 180 | 180 |
Design Spec | 生成设计文档 | 30 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 60 | 60 |
Design | 具体设计 | 60 | 60 |
Coding | 具体编码 | 300 | 360 |
Code Review | 代码复审 | 30 | 30 |
Test | 测试(自我测试,修改代码,提交修改) | 120 | 180 |
Reporting | 报告 | 90 | 90 |
Test Report | 测试报告 | 30 | 30 |
Size Measurement | 计算工作量 | 30 | 35 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 25 |
total | 合计 | 1030 | 1180 |
解题思路描述
刚开始拿到题目时的思考:
- 如何获取并解析命令行参数:
Python中命令行输入的参数将会以列表(list)的形式存储在sys.argv中,使用函数遍历sys.argv列表并做出相应操作即可 - 如何读取日志并将数据输出到指定的文件:
由命令行输入得到日志文件的目录及指定的日期(默认为日志最新的一天),特定格式的日期字符串(如:2020-02-20)的结尾加上".log.txt"就是日志文件的文件名,有了路径和文件名就可以读取日志。输出则直接输出到命令指定的路径的文件。 - 如何处理日志中的数据:
我发现日志的格式固定,因此使用split()
将日志中的一行数据按空格分割为列表,然后使用if/else语句就可以进行数据的处理 - 如何存储处理后的各省各项数据:
使用Python的字典可以很直观地存储数据,如下是我在程序中使用的字典:
data_dict = { '省份名':{'感染患者': 人数, '疑似患者': 人数, '治愈': 人数, '死亡': 人数}}
- 如何按顺序输出各类型的数据:
以行为单位输出,如下是输出福建省感染患者和治愈人数的语句:
f.writelines('福建' + ' ' + '感染患者 ' + data_dict['福建']['感染患者'] + '人 ' + '治愈 ' + data_dict['福建']['治愈'] + '人')
设计实现过程
大致流程
代码结构
关键函数流程图
- file_date(input_list)函数:
- read_file()函数:
关键代码
数据处理
def file_data(self, input_list):
# 处理文档中的数据
province = input_list[0]
if province not in self.data_dict:
self.dict_add_items(province)
# 若省份不在字典中,则以省份名为键新建元素并添加到字典中
if input_list[1] == '新增' or input_list[1] == '排除':
type = input_list[2]
num = int(str(input_list[3]).replace('人', ''))
if input_list[1] == '新增':
self.data_dict[province][type] += num
self.data_dict['全国'][type] += num
else:
self.data_dict[province][type] -= num
self.data_dict['全国'][type] -= num
elif input_list[1] == '死亡' or input_list[1] == '治愈':
num = int(str(input_list[2]).replace('人', ''))
self.data_dict[province][input_list[1]] += num
self.data_dict[province]['感染患者'] -= num
self.data_dict['全国'][input_list[1]] += num
self.data_dict['全国']['感染患者'] -= num
elif input_list[2] == '流入':
num = int(str(input_list[4]).replace('人', ''))
type = input_list[1]
self.data_dict[province][type] -= num
self.data_dict[input_list[3]][type] += num
elif input_list[2] == '确诊感染':
num = int(str(input_list[3]).replace('人', ''))
self.data_dict[province]['感染患者'] += num
self.data_dict[province]['疑似患者'] -= num
self.data_dict['全国']['感染患者'] += num
self.data_dict['全国']['疑似患者'] -= num
解释思路:
日志中的数据只有这几种固定格式:
- 1.
xx省
死亡/治愈
n人
- 2.
xx省
新增/排除
xx患者
n人
- 3.
xx省
xx患者
流入
yy省
n人
- 4.
xx省
疑似患者
确诊感染 n人
将一行数据分割后存入列表中
列表第一项为省名
通过判断第二项可确认第1、2种格式的数据的操作,接下来再判断第三项可确认第3、4种格式的数据的操作
命令行处理
def paras(self):
# 处理命令行参数
index = 0
for arg in self.args:
if arg == '-log':
self.input_path = self.args[index + 1]
if arg == '-out':
self.output_path = self.args[index + 1]
if arg == '-date':
self.input_date = self.args[index + 1]
if arg == '-type':
self.set_type_list(index)
if arg == '-province':
self.set_province_list(index)
index += 1
def set_type_list(self, index):
for i in range(index + 1, len(self.args)):
if self.args[i][0] != '-':
if self.args[i] not in ['ip', 'sp', 'cure', 'dead']:
print('错误:输入的类型不存在!')
return '错误:输入的类型不存在!'
self.type_list.append(self.args[i])
else:
break
def set_province_list(self, index):
for i in range(index + 1, len(self.args)):
if self.args[i][0] != '-':
try:
sort_by_pinyin(self.args[i])
except KeyError as identifier:
print('错误:输入的省份不存在!')
return '错误:输入的省份不存在!'
self.province_list.append(self.args[i])
else:
break
解释思路:
Python的命令行参数会被保存在列表sys.argv
中
定义一个列表args = sys.argv[2:]
,即sys.argv
去掉前两项(文件名、-list
)的列表
遍历该列表,若某项为-log/-out/-date
,则它的后一项为他的参数
若某项为-type/-province
,则它后面直到以-
开头的项之前的项都是他的参数,将它的参数存入对应列表
单元测试截图和描述
测试用例1
不输入-log
def test_1(self):
test_list = ['-out', 'D:/ListOut']
test_str = '未指定日志目录的位置'
self.assertEqual(test_str, check_command(test_list))
测试用例2
不输入-out
def test_2(self):
test_list = ['-log', 'D:/log/']
test_str = '未指定输出文件的目录和文件名'
self.assertEqual(test_str, check_command(test_list))
测试用例3
只有-log
和-out
,无其他参数
def test_3(self):
test_list = ['-log', '../log/', '-out', '../result/out.txt']
test_str = '全国 感染患者147人 疑似患者317人 治愈27人 死亡21人\n福建 感染患者22人 疑似患者38人 治愈3人 死亡0人\n湖北 感染患者125人 疑似患者279人 治愈24人 死亡21人\n'
cl = CommandHandler(test_list)
cl.paras()
self.assertEqual('../log/', cl.input_path)
self.assertEqual('../result/out.txt', cl.output_path)
self.assertEqual('', cl.input_date)
self.assertEqual('[]', str(cl.type_list))
self.assertEqual('[]', str(cl.province_list))
fi = DataHandler(cl.input_path, cl.output_path, '', [], [])
self.assertEqual(test_str, fi.read_file())
测试用例4
-date
的参数为2020-01-22
def test_4(self):
test_list = ['-log', '../log/', '-out', '../result/out.txt', '-date', '2020-01-22']
test_str = '全国 感染患者15人 疑似患者22人 治愈2人 死亡1人\n福建 感染患者5人 疑似患者7人 治愈0人 死亡0人\n湖北 感染患者10人 疑似患者15人 治愈2人 死亡1人\n'
cl = CommandHandler(test_list)
cl.paras()
self.assertEqual('../log/', cl.input_path)
self.assertEqual('../result/out.txt', cl.output_path)
self.assertEqual('2020-01-22', cl.input_date)
self.assertEqual('[]', str(cl.type_list))
self.assertEqual('[]', str(cl.province_list))
fi = DataHandler(cl.input_path, cl.output_path, cl.input_date, cl.type_list, cl.province_list)
self.assertEqual(test_str, fi.read_file())
测试用例5
-date
的参数为2020-01-23
def test_5(self):
test_list = ['-log', '../log/', '-out', '../result/out.txt', '-date', '2020-01-23']
test_str = '全国 感染患者42人 疑似患者62人 治愈4人 死亡3人\n福建 感染患者9人 疑似患者16人 治愈1人 死亡0人\n湖北 感染患者33人 疑似患者46人 治愈3人 死亡3人\n'
cl = CommandHandler(test_list)
cl.paras()
self.assertEqual('../log/', cl.input_path)
self.assertEqual('../result/out.txt', cl.output_path)
self.assertEqual('2020-01-23', cl.input_date)
self.assertEqual('[]', str(cl.type_list))
self.assertEqual('[]', str(cl.province_list))
fi = DataHandler(cl.input_path, cl.output_path, cl.input_date, cl.type_list, cl.province_list)
self.assertEqual(test_str, fi.read_file())
测试用例6
-date
的参数为2020-01-23
-type
的参数为ip,cure
def test_6(self):
test_list = ['-log', '../log/', '-out', '../result/out.txt', '-date', '2020-01-23', '-type', 'ip', 'cure']
test_str = '全国 感染患者42人 治愈4人\n福建 感染患者9人 治愈1人\n湖北 感染患者33人 治愈3人\n'
cl = CommandHandler(test_list)
cl.paras()
self.assertEqual('../log/', cl.input_path)
self.assertEqual('../result/out.txt', cl.output_path)
self.assertEqual('2020-01-23', cl.input_date)
self.assertEqual("['ip', 'cure']", str(cl.type_list))
self.assertEqual('[]', str(cl.province_list))
fi = DataHandler(cl.input_path, cl.output_path, cl.input_date, cl.type_list, cl.province_list)
self.assertEqual(test_str, fi.read_file())
测试用例7
-date
的参数为2020-01-23
-type
的参数为ip,cure
-province
的参数为福建,全国
def test_7(self):
test_list = ['-log', '../log/', '-out', '../result/out.txt', '-date', '2020-01-23', '-type', 'ip', 'cure', '-province', '福建', '全国']
test_str = '全国 感染患者42人 治愈4人\n福建 感染患者9人 治愈1人\n'
cl = CommandHandler(test_list)
cl.paras()
self.assertEqual('../log/', cl.input_path)
self.assertEqual('../result/out.txt', cl.output_path)
self.assertEqual('2020-01-23', cl.input_date)
self.assertEqual("['ip', 'cure']", str(cl.type_list))
self.assertEqual("['福建', '全国']", str(cl.province_list))
fi = DataHandler(cl.input_path, cl.output_path, cl.input_date, cl.type_list, cl.province_list)
self.assertEqual(test_str, fi.read_file())
测试用例8
-date
的参数为2020-01-27
-type
的参数为ip,cure,dead
-province
的参数为福建
def test_8(self):
test_list = ['-log', '../log/', '-out', '../result/out.txt', '-date', '2020-01-27', '-type', 'ip', 'cure', 'dead', '-province', '福建']
test_str = '福建 感染患者22人 治愈3人 死亡0人\n'
cl = CommandHandler(test_list)
cl.paras()
self.assertEqual('../log/', cl.input_path)
self.assertEqual('../result/out.txt', cl.output_path)
self.assertEqual('2020-01-27', cl.input_date)
self.assertEqual("['ip', 'cure', 'dead']", str(cl.type_list))
self.assertEqual("['福建']", str(cl.province_list))
fi = DataHandler(cl.input_path, cl.output_path, cl.input_date, cl.type_list, cl.province_list)
self.assertEqual(test_str, fi.read_file())
测试用例9
-date
的参数为2020-01-27
-type
的参数为ip,cure,dead,sp
-province
的参数为湖北,福建
def test_9(self):
test_list = ['-log', '../log/', '-out', '../result/out.txt', '-date', '2020-01-27', '-type', 'ip', 'cure', 'dead', 'sp', '-province', '湖北', '福建']
test_str = '福建 感染患者22人 治愈3人 死亡0人 疑似患者38人\n湖北 感染患者125人 治愈24人 死亡21人 疑似患者279人\n'
cl = CommandHandler(test_list)
cl.paras()
self.assertEqual('../log/', cl.input_path)
self.assertEqual('../result/out.txt', cl.output_path)
self.assertEqual('2020-01-27', cl.input_date)
self.assertEqual("['ip', 'cure', 'dead', 'sp']", str(cl.type_list))
self.assertEqual("['湖北', '福建']", str(cl.province_list))
fi = DataHandler(cl.input_path, cl.output_path, cl.input_date, cl.type_list, cl.province_list)
self.assertEqual(test_str, fi.read_file())
测试用例10
-date
的参数为2020-01-27
-type
的参数为ip,cure,dead,sp
-province
的参数为湖北,福建,浙江
(其中浙江
为日志中没有记录)
def test_10(self):
test_list = ['-log', '../log/', '-out', '../result/out.txt', '-date', '2020-01-27', '-type', 'ip', 'cure', 'dead', 'sp', '-province', '湖北', '福建', '浙江']
test_str = '福建 感染患者22人 治愈3人 死亡0人 疑似患者38人\n湖北 感染患者125人 治愈24人 死亡21人 疑似患者279人\n浙江 感染患者0人 治愈0人 死亡0人 疑似患者0人\n'
cl = CommandHandler(test_list)
cl.paras()
self.assertEqual('../log/', cl.input_path)
self.assertEqual('../result/out.txt', cl.output_path)
self.assertEqual('2020-01-27', cl.input_date)
self.assertEqual("['ip', 'cure', 'dead', 'sp']", str(cl.type_list))
self.assertEqual("['湖北', '福建', '浙江']", str(cl.province_list))
fi = DataHandler(cl.input_path, cl.output_path, cl.input_date, cl.type_list, cl.province_list)
self.assertEqual(test_str, fi.read_file())
单元测试覆盖率优化和性能测试,性能优化截图和描述
覆盖率:
性能测试:
没有找到可以检测Python程序运行时内存占用的软件...
暂时没法进行性能测试
代码规范链接
https://github.com/Exusia-i/InfectStatistic-main/blob/master/091700410/codestyle.md
心路历程和收获
完成这次作业对我来说还是比较吃力的,特别是还有一些之前没有接触过的东西。比如gtihub的使用、PSP表格、以及单元测试、性能检测等。
而且工作量也比较大,需要学习很多新知识。之所以使用Python而不是更熟悉的Java,是因为最近自己在自学Python,想把这次作业作为一次练手的机会。但也出现了一些问题,一是对于Python不熟悉导致开发进度比较缓慢,二是关于性能检测的问题,比如Java的性能检测方便得多,但是我现在依然没有找到能够对python程序进行详细的性能检测的方法(Pycharm的检测只能检测函数调用次数和运行时间)
在完成这次作业的过程中收获当然也不少,首先是接触了一些新技术和新知识,然后就是算是入门了一门新语言。
技术路线图相关的仓库
1.AFNetworking
作者是 NSHipster 的博主, iOS 开发界的大神级人物, 毕业于卡内基·梅隆大学, 开源了许多牛逼的项目, 这个便是其中之一, AFNetworking 采用 NSURLConnection + NSOperation, 主要方便与服务端 API 进行数据交换, 操作简单, 功能强大, 现在许多人都用它取代ASIHTTPRequest
2.GPUImage
一款强大的图片滤镜工具, 支持自定义滤镜, 可用来实时处理图片和视频流, GPUImage 这个项目从 2012 年开始, 使用 OpenGL 图形程序接口编写, 性能非常好, 现在很多 iOS 程序员都用它来实现 iOS 的模糊效果
3.RestKit
主要用于 iOS 上网络通信, 允许与 RESTful Web 服务交互, 常用于处理 API, 解析 JSON, 映射响应对象等操作, 简单易用, 方便你把所有精力都放在对数据的操作上
4.SDWebImage
作者 Olivier Poitrey 是 Dailymotion 的 CTO, 拥有多个不错的开源项目, 此项目常用于对从 Web 端接受到的图片进行缓存
5.Masonry
一个轻量级的布局框架, 同时支持 iOS 和 Mac OS X, 语法优雅, 帮助开发者快速适配不同分辨率的 iOS 设备