软件工程2020秋学期 (福州大学 - 数学与计算机科学学院)

  1. 班级首页
  2. 作业列表
  3. 详情

福州大学软件工程实践个人编程作业 [已截止]


制作一个程序统计和分析 GitHub 的用户行为数据。

数据来源

GH Archive
GH Archive 是一个 GitHub 归档数据的下载地址,它提供了从 2011 年至今的 GitHub 活动数据。
点击下载示例数据

题目要求

本题关注以下这几种信息类型:

需要统计以下内容

  • (基础)个人的 4 种事件的数量。
  • (基础)每一个项目的 4 种事件的数量。
  • (提高)每一个人在每一个项目的 4 种事件的数量。

编码要求

  • 使用 C++、Java、Python 中任一种完成任务。
  • 制定合适的代码规范,进行测试。
  • 使用 Git 进行版本控制。

评测环境

  • GitHub Actions 具体配置自己搜索。

格式要求

  • 执行文件为 GHAnalysis,拓展名依所选语言而定,注意 Linux 操作系统文件系统区分大小写。
  • 初始化
    • C++:GHAnalysis <--init|-i> <path to data>
    • Java:java -Dfile.encoding=UTF-8 -jar GHAnalysis.jar <--init|-i> <path to data>
    • Python: python3 GHAnalysis.py <--init|-i> <path to data>
  • 查某用户的某事件数量运行格式:
    • C++:GHAnalysis <-u|--user> user <-e|--event> <PushEvent|IssueCommentEvent|IssuesEvent|PullRequestEvent>
    • Java:java -Dfile.encoding=UTF-8 -jar GHAnalysis.jar <-u|--user> user <-e|--event> <PushEvent|IssueCommentEvent|IssuesEvent|PullRequestEvent>
    • Python: python3 GHAnalysis.py <-u|--user> user <-e|--event> <PushEvent|IssueCommentEvent|IssuesEvent|PullRequestEvent>
  • 查询某项目的某事件数量运行格式:
    • C++:GHAnalysis user/repo <-r|--repo> user/repo <-e|--event> <push|IssueCommentEvent|IssuesEvent|PullRequestEvent>
    • Java:java -Dfile.encoding=UTF-8 -jar GHAnalysis.jar <-r|--repo> user/repo <-e|--event> <PushEvent|IssueCommentEvent|IssuesEvent|PullRequestEvent>
    • Python: python3 GHAnalysis.py <-r|--repo> user/repo <-e|--event> <PushEvent|IssueCommentEvent|IssuesEvent|PullRequestEvent>
  • 查询某用户在某项目的某事件数量运行格式:
    • C++:GHAnalysis <-u|--user> user <-r|--repo> user/repo <-e|--event> <PushEvent|IssueCommentEvent|IssuesEvent|PullRequestEvent>
    • Java:java -Dfile.encoding=UTF-8 -jar GHAnalysis.jar <-u|--user> user <-r|--repo> user/repo <-e|--event> <PushEvent|IssueCommentEvent|IssuesEvent|PullRequestEvent>
    • Python: python3 GHAnalysis.py <-u|--user> user <-r|--repo> user/repo <-e|--event> <PushEvent|IssueCommentEvent|IssuesEvent|PullRequestEvent>
  • 输出格式为单独的数字。

运行格式

  • 会先运行一次初始化指令,然后在运行其他指令。
  • 多个参数顺序不一定一致。
  • <path to data> 是一个文件夹。

数据范围

  • 数据文本小于 10GB。

前置要求

GitHub 初使用

为了保证你的代码能够帮助更多的人,并且能得到有效的管理和开源共享,请先学习 GitHub 使用:

  • 下载 Git。
  • 学习基础 Git 命令。
  • 本次作业采用 GitHub,fork 项目示例结构到自己的仓库,完成基本需求,并至少进行10次以上的 commit。
  • 对于非仓库要求的代码,如编译器生成的项目文件、输出文件、class、jar 包、exe 等,应该使用 .gitignore 进行忽略,并确保不会提交到 GitHub 上。

代码规范制定

为了其他同仁可以轻松的阅读你的代码,请制定属于你的代码规范,要求不能偏离主流代码规范:
请参照 《码出高效_阿里巴巴Java开发手册》/《腾讯c++代码规范》/《Python PEP8》,从以下几个角度制定你的编程规范,并撰写成 Markdown 文件,请牢记你制定的编码规范,并在此次作业中严格执行。

  • 缩进
  • 变量命名
  • 每行最多字符数
  • 函数最大行数
  • 函数、类命名
  • 常量
  • 空行规则
  • 注释规则
  • 操作符前后空格
  • 其他规则

作业步骤

  • Fork 自己的对应仓库。
  • 发送回到原仓库 master 分支的 Pull Request。
  • 编写程序,积极 commit。
  • 迭代更新,优化代码。
  • 编写博客,完成作业。

作业发布流程

  • 发布作业内容,同学在每次向 GitHub 提交代码时,都会自动编译,运行,如果失败请相关同学检查代码。
  • 分 3-4 次发布作业测试数据,更新评测脚本,自动计算分数及成绩。
  • 封闭评测,最后测出所有同学的分数。

博客要求

  • 给出此次作业的 PSP 表格。

  • 解题思路描述。即刚开始拿到题目后,如何思考,如何找资料的过程。

  • 设计实现过程。设计包括代码如何组织,关键函数的流程图。

  • 代码说明。展示出项目关键代码,并解释思路。

  • 单元测试截图和描述。

  • 单元测试覆盖率优化和性能测试,性能优化截图和描述。

  • 给出你的代码规范的链接,即你的仓库中的 codestyle.md。

  • 总结本次作业。

评分标准(115)

  • Git 部分(15)
    • commit 信息规范(5)
    • 文件规范(5)
    • 其他(5)
  • 代码部分(20)
    • 代码风格(5)
    • 代码规范(5)
    • 单元测试(10)
  • 博客部分(50)
    • 任务分解(10)
    • PSP 表格(5)
    • 迭代过程描述(10)
    • 困难描述及解决办法寻找(15)
    • 其他(10)
  • 程序评测(25)
    • 正确性(10)
    • 运行速度(15)
  • 提交时间(5)

附录

PSP 表格

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

参考程序

import json
import os
import argparse

class Data:
    def __init__(self, dict_address: int = None, reload: int = 0):
        if reload == 1:
            self.__init(dict_address)
        if dict_address is None and not os.path.exists('1.json') and not os.path.exists('2.json') and not os.path.exists('3.json'):
            raise RuntimeError('error: init failed')
        x = open('1.json', 'r', encoding='utf-8').read()
        self.__4Events4PerP = json.loads(x)
        x = open('2.json', 'r', encoding='utf-8').read()
        self.__4Events4PerR = json.loads(x)
        x = open('3.json', 'r', encoding='utf-8').read()
        self.__4Events4PerPPerR = json.loads(x)

    def __init(self, dict_address: str):
        json_list = []
        for root, dic, files in os.walk(dict_address):
            for f in files:
                if f[-5:] == '.json':
                    json_path = f
                    x = open(dict_address+'\\'+json_path,
                             'r', encoding='utf-8').read()
                    str_list = [_x for _x in x.split('\n') if len(_x) > 0]
                    for i, _str in enumerate(str_list):
                        try:
                            json_list.append(json.loads(_str))
                        except:
                            pass
        records = self.__listOfNestedDict2ListOfDict(json_list)
        self.__4Events4PerP = {}
        self.__4Events4PerR = {}
        self.__4Events4PerPPerR = {}
        for i in records:
            if not self.__4Events4PerP.get(i['actor__login'], 0):
                self.__4Events4PerP.update({i['actor__login']: {}})
                self.__4Events4PerPPerR.update({i['actor__login']: {}})
            self.__4Events4PerP[i['actor__login']][i['type']
                                         ] = self.__4Events4PerP[i['actor__login']].get(i['type'], 0)+1
            if not self.__4Events4PerR.get(i['repo__name'], 0):
                self.__4Events4PerR.update({i['repo__name']: {}})
            self.__4Events4PerR[i['repo__name']][i['type']
                                       ] = self.__4Events4PerR[i['repo__name']].get(i['type'], 0)+1
            if not self.__4Events4PerPPerR[i['actor__login']].get(i['repo__name'], 0):
                self.__4Events4PerPPerR[i['actor__login']].update({i['repo__name']: {}})
            self.__4Events4PerPPerR[i['actor__login']][i['repo__name']][i['type']
                                                          ] = self.__4Events4PerPPerR[i['actor__login']][i['repo__name']].get(i['type'], 0)+1
        with open('1.json', 'w', encoding='utf-8') as f:
            json.dump(self.__4Events4PerP,f)
        with open('2.json', 'w', encoding='utf-8') as f:
            json.dump(self.__4Events4PerR,f)
        with open('3.json', 'w', encoding='utf-8') as f:
            json.dump(self.__4Events4PerPPerR,f)

    def __parseDict(self, d: dict, prefix: str):
        _d = {}
        for k in d.keys():
            if str(type(d[k]))[-6:-2] == 'dict':
                _d.update(self.__parseDict(d[k], k))
            else:
                _k = f'{prefix}__{k}' if prefix != '' else k
                _d[_k] = d[k]
        return _d

    def __listOfNestedDict2ListOfDict(self, a: list):
        records = []
        for d in a:
            _d = self.__parseDict(d, '')
            records.append(_d)
        return records

    def getEventsUsers(self, username: str, event: str) -> int:
        if not self.__4Events4PerP.get(username,0):
            return 0
        else:
            return self.__4Events4PerP[username].get(event,0)

    def getEventsRepos(self, reponame: str, event: str) -> int:
        if not self.__4Events4PerR.get(reponame,0):
            return 0
        else:
            return self.__4Events4PerR[reponame].get(event,0)

    def getEventsUsersAndRepos(self, username: str, reponame: str, event: str) -> int:
        if not self.__4Events4PerP.get(username,0):
            return 0
        elif not self.__4Events4PerPPerR[username].get(reponame,0):
            return 0
        else:
            return self.__4Events4PerPPerR[username][reponame].get(event,0)


class Run:
    def __init__(self):
        self.parser = argparse.ArgumentParser()
        self.data = None
        self.argInit()
        print(self.analyse())

    def argInit(self):
        self.parser.add_argument('-i', '--init')
        self.parser.add_argument('-u', '--user')
        self.parser.add_argument('-r', '--repo')
        self.parser.add_argument('-e', '--event')

    def analyse(self):
        if self.parser.parse_args().init:
            self.data = Data(self.parser.parse_args().init, 1)
            return 0
        else:
            if self.data is None:
                self.data = Data()
            if self.parser.parse_args().event:
                if self.parser.parse_args().user:
                    if self.parser.parse_args().repo:
                        res = self.data.getEventsUsersAndRepos(
                            self.parser.parse_args().user, self.parser.parse_args().repo, self.parser.parse_args().event)
                    else:
                        res = self.data.getEventsUsers(
                            self.parser.parse_args().user, self.parser.parse_args().event)
                elif self.parser.parse_args().repo:
                    res = self.data.getEventsRepos(
                        self.parser.parse_args().reop, self.parser.parse_args().event)
                else:
                    raise RuntimeError('error: argument -l or -c are required')
            else:
                raise RuntimeError('error: argument -e is required')
        return res


if __name__ == '__main__':
    a = Run()

提交: 109 人,未提交: 17 人
未提交名单: 后来喵    披风风    kexiaoke    DoGoods    htx1    殤-l    Double-Ten    冲就完了    第一组    网益云    栋哥说的都队    2020软工-八级大狂风    冬天的冰淇淋不会化    军舰队    呜呼啦呼队    栋的都懂    以上队伍成绩无效队