初探《构建之法》及学习良好开发姿势
项目 | 内容 |
---|---|
这个作业属于哪个课程 | 2021春北航计算学院软件工程(罗杰 任健) |
这个作业的要求在哪里 | Here it is. |
我在这个课程的目标是 | 1. 体验体系化、规范化的软件工程实践流程。2. 锻炼码力。 |
这个作业在哪个具体方面帮助我实现目标 | 通读《构建之法》,梳理git协作姿势,尝试使用CI/CD工具 |
我的问题
Q1:全自动化生成单元测试靠谱吗?
首先,通过阅读《构建之法》第2.1.2节的相关内容,我提取到了单元测试的一些基本特点:
-
体量大。
单元测试应该准确、快速地保证程序基本模块的正确性。
单元测试应该覆盖所有代码路径。单元测试应覆盖所测单元的所有代码路径,包括错误处理路径。
也就是说,单元测试需要对于当前工程所有方法进行全覆盖,以保证能测试到全部系统基本功能(笔者曾参与的开发中要求单元测试覆盖率达到90%)。
-
各模块具有独立性。
最好是在设计的时候就写好单元测试。
独立性—单元测试的运行/通过/失败不依赖于别的测试
单元测试多基于各个单独模块、方法的功能进行书写,少有涉及系统不同层次中的逻辑联系。书中提到了单元测试是可以在设计时边设计边输写的,这同样也是Ruby课老师为我们介绍单元测试时涉及到的内容。由于各测试对象的逻辑独立性,单元测试自身也同样具备了逻辑独立性。
-
稳定性。一旦写好,鲜有更改。
我们还是要用随机数等办法“增加测试的真实性”,但不是在单元测试中。
单元测试的测试用例多为人工手撸的,且由于后期各模块之间可能会相互调用,为了防止出错不稳定复现的问题,单元测试一般在写好之后就不会进行调整,其所涉及的数据也应当足够典型。
综合单元测试的以上特点,我不禁发出思考:
”既然单元测试写起来费时费力,自身逻辑却独立且简单,那是否可以全自动生成单元测试呢?“
其实经过去年OO课程的学习,我们已经对JUnit单元测试有了一定的了解,包括其工具链的使用,即目前已经有一系列批量自动生成单元测试的工具了。
但是,通过这篇博客中的使用样例可以发现,该类单元测试自动生成工具多能达到极高的覆盖率:
却忽略了最典型的逻辑的检测,仅对空置、异常数据进行了测试:
而且,目前单元测试基本还是又程序员手动输写的。那么,自动生成的弊端究竟在哪里?是否有克服的方法呢?
Q2:改进别人的方法,是否一定不会有突破(或突破一定小于作者本人)
这种“微创新”论调是对创新的侮辱,对大众智商的鄙视,对天使的亵渎。抛开道德和法律方面的问题不谈,我认为这种模仿的行为其实就是“背口诀”,我觉得不大靠谱。——《构建之法》3.3
在书中,作者阐述了一种国内创业团队的普遍现象:完全拷贝国外公司的创意和设计,在中国快速推出自己的产品。这种商业模式被作者称为”微创新“,由于其具有直接copy其他公司想法,像别口诀一样进行套用的特点,因而被认为不具备竞争力。并且,面对不同客户的区别性要求,修改当前模板的能力的存在性和可行性也被质疑。
我的问题是,这种能力一定不存在吗?换言之,在别人的开发基础上进行拓展研发,一定不能做到”精通“,有所突破吗?又或者,其突破一定要比模板提出者能够做出的要小吗?
在现实生活中,我相信很多产品都不是单单一代人或者一代团队开发出来的,许多都经历了多了团队、前辈后辈之间的迭代和接手,代代优化运维。那么,这些在最初开发者退役后接任的后继者,和上例中的”国内创业团队“的本质区别在哪里呢?
如果答案是否定的,那么纵观中国发展阶段的艰苦历程,以及清华大学公共管理学院院长在这段演讲中所说的:
中国制造在发展阶段模仿外国产品是不可避免的。
中国如何能在模仿国外产品的大环境中想办法弯道超车,取得超越呢?
Q3:在MVP方法中,如何保证雏形产品的不完备性不影响用户反馈?
MVP——Minimal Viable Product,最小可行产品,又称为Minimal Feature Set,最小功能集。具体的做法是:把产品最核心的功能用最小的成本实现出来(或者描绘出来),然后快速征求用户意见。——《构建之法》5.3.5
书中提到,为了让产品团队尽早获得用户的反馈,防止浪费性的精力投入,一些互联网团队正在尝试MVP方法,即花费极小的成本设计一个类似”功能雏形“的东西,来测试用户对于这部分最小功能集的需求量,以判断该功能的开发效益大小。
我的问题是,成本极低的产品雏形相比最终的成品,肯定质量有所欠缺,并不完备。那么,这种不完备性,是否会影响用户对于该最小功能集的需求量,从而导致获得的用户反馈相对于真实需求有所偏颇呢?
Q4:不同类型产品的迁移成本关键问题所在?如何减少迁移成本?
很多用户会说:“等我的朋友都上你这个网站玩了,我就过来了。”但是这个条件也许永远也满足不了。
书中提到,许多新生的聊天软件都面临着巨大的迁移成本问题,即用户为何要选择抛弃本来使用的产品而转而使用新产品呢?
迁移成本主要分为以下三类:
- 时间和精力上的转换成本,包括学习成本、时间成本、精力成本等
- 经济上的转换成本,包括利益损失成本、金钱损失成本等
- 情感上的转换成本,包括个人关系损失成本、品牌关系损失成本等
书中后半部分还提到一个有趣的例子:
机械打字机的打字臂由于相邻字符的原因而互相碰撞,因此要把经常一起出现的字符分开,这样才能减少碰撞的几率,QWERTY键盘的设计便源于此。但是后来打字机都抛弃了机械打字臂的设计,演变为球形字模打字,到了后来的电子打字,就更没有任何“机械碰撞”的可能性。因此,原始布局设计的优点失去了原有的价值,反而变成了弱点。但是,长期以来,人们已经习惯了QWERTY键盘,所谓先入为主。
qwerty热区图:
dvorak热区图:
从热区图来看,dvorak的分布确实要优于传统的qwerty分布,使用dvorak键盘的打字效率也确实要高于qwerty。但是,一旦程序员选用dvorak键盘,将面临一种“用不了别的电脑”的窘境,这导致qwerty直至今日一直处于垄断地位。这个迁移成本,可以说是相当大了。
其实我们发现,市面上的大部分新面世的软件多多少少都具有迁移成本的问题(除非此类产品完全完全没有类似的前辈)。但是,迁移成本的大小似乎与产品的类型有很大的关系,那么具体每种产品的潜意成本主要集中在以上哪一类里?如何才能尽量减少迁移成本以获得更大的用户量呢?
Q5:专业性产品是否需要考虑非专业类人群客户?
以前我不理解为什么360的装机量那么大,现在我懂了:1. 海量用户并不知道如何管理使用电脑,360那种傻瓜式的一键解决才是他们需要的;2. 他们不想花钱,但是不会找什么“破解版”、“序列号”、“注册机”
VS的微软团队也有很多开发人员,他们也是用户,只听取他们的意见是不是就够了呢?
在书中,作者对于产品的用户在这两个地方进行了阐述:
- 对于专业程序员,几乎不使用360,hao123等软件,但他们却获得了海量的普通用户的使用量。
- VS软件的典型用户,要么是专业的程序员,要么日常工作中国也需要使用编程作为工具。
我发现,二者的典型用户仿佛天差地别。
对于360、hao123等全民普遍使用的软件,再评估用户需求的时候,这些普通社会民众仿佛才是最典型的使用人群,是应该focus的调查对象。但是,对于vs类带有专业特点的软件,其典型用户就是他的开发者,就是程序员。
那么,vs等专业性的产品,在考虑用户需求的时候,是否还需要考虑非专业类人群的需求?
直观来看,仿佛非专业人士并不会使用它们,显然是不用考虑。
但细想,人人都有入门的需求,且现在编程行为也越来越趋于大众化。
但是,如果考虑非专业人士的需求,是否会降低专业人员的使用体验呢?
这其中,仿佛存在着一些矛盾...
Q6:一个优秀的创新是否一定逃不过泡沫阶段?如何快速泡沫中跳出?
说到时机,任何新技术都有自身发展的规律。
很多大家正在使用的“新颖”产品,往往是经历了迷茫期之后的二代产品,那些在泡沫最大的时候匆忙出现的第一代产品大多数都没有等到这一天(电子书、平板电脑、社交网络等)。
大到AI这种突然大热的科技领域,小到任何一支股票,若是某一元素突然被大众发掘了其巨大的潜力和价值,仿佛都会经历技术成熟过程中的“泡沫时期”,也就是书中的期望膨胀期。
这种泡沫现象,可能来源于公众的舆论、炒作,又或许源自于人们对其中潜在利益的狂热追求,从而诞生的许多未经斟酌、质量参差不齐的产品或理论。仿佛在某一项技术成熟前,这种阶段是在所难免的,毕竟它代表了社会的走向,且这一阶段往往是某一技术开枝散叶最迅速的时期。只是,可能开枝散叶的方向,有些杂乱无章。
那么,是否存在一种方法,能避免技术的“泡沫时期”的出现,让社会对其的探究始终沿着正确的方向进行。或者,有什么办法能快速拯救正在经历泡沫阶段的技术或事物吗?
Q7:Bug Bar的确定有什么基准吗?
提升Bug bar是放走一些无伤大雅的Bug,换取项目能如期完成。其指导思想是抓大放小,既然没法全解决,就集中精力解决最重要的Bug。
在开发的过程中,bug往往随着开发的进程越来越多,而debug的难度和所需的成本也相当的高,有时候会消耗大量的人力和时间资源。因此,书中提到,有时可以忽略一些无伤大雅的bug,换取项目的进展速度。
但是,有的bug虽然一开始并不起眼,但后期可能会被逐渐增多的关联而放大。另外,debug难度会难以避免地会随着规模的增加而增大,到时候想要debug的成本或许远高于现在。这种叠加成本是否有衡量的标准呢?
尤其是针对俩种极端情况:
- The lowest must
- The highest no
二者似乎都处于边界情况,想要界定仿佛更加困难了。
良好的多人协作姿势
git是什么呢,在我眼里,它是一个学习之前听上去神通广大,学用的时候想一死了之,会用了之后相见恨晚的程序员必备小工具,也是在多人协作中的必备小帮手。接下来对于所有基于git的项目管理软件的统一协作姿势进行总结:
- 新建一个仓库
- git clone到本地
- 开启一个分支(develop)作为主战场
- 切换分支,每个人在自己的分支下进行开发,并将开发结果及时commit到自己的分支
- 切换到主战场分支(develop),merge每个人的分支
- 将合并后的主战场分支(develop)进行提交
- 在主战场分支稳定后,合并到master分支,在master分支上保留稳定的版本
以上就是多人协同合作进行开发时使用git进行版控的具体姿势了。
gitlab
github以及“私有版github”——gitlab都配备了一系列可以增加游戏体验感的多人协作工具。
使用gitlab开发
- 开发者将源仓库fork到自己目录下。
- 开发在自己的仓库下进行开发。
- 开发提交merge request到原仓库,并指定审核人。
- 审核人可以通过开discussions对开发者的提交提出修改意见
- 开发者在完成审核人的修改意见后,可以resolve掉相关discussion
- 重复以上操作,直到审核人认为满足和并要求,进行merge
issues
issues类似一个议题,可以是一个讨论、一个提问、一个bug、一个建议等。选择一个project并选择一个指定人即可创建。被制定者可以在issues中发现别人开的issues,及时进行解决和交互。
milestone
milestones即里程碑,可以用来让项目参与者识别项目阶段性目标和当前进度等。在创建好milestone后,可以创建issues归属于milestone。milestone可以根据其名下issues的解决进度显示当前进度,在milestone下的所有issues都解决之后,里程碑可以显示100%complete。
git、gitlab、github还有许多优美的操作姿势是我没有尝试甚至没有了解过的,希望能通过软公的小组协作多多了解多人协作的正确姿势。
github
github是当前使用最广泛的面向开源的代码版本管理平台,但本人目前还没有在github上进行过除了对自己的作业进行版控以外的任何合作型工作尝试,因此,本人通过阅读参考资料1、2、3了解了一些github上的协同开发方式,在此进行一些总结。
-
挑选项目。(若参与既定项目,可跳过)
-
通过issues和项目所有者进行讨论,协商是否需要维护更新。(若参与既定项目,可跳过)
-
准备仓库,确定分支。
- fork项目。
- clone到本地(注意要clone fork到自己名下的仓库)。
- 创建一个新分支(便于明确语义,以及方便master分支和远程保持同步状态)
-
开发中
- 注意:commit message要简介且明了
- 可以使用rebase整理日志
- 注意原项目的代码风格,并保持一致
-
提交修改:选择对应的分支创建PR(pull request),标题和内容能清晰展现所做的更改
-
审查:维护者对提交的pr进行审查,对新提出的问题可以继续push到分支上。
-
合并:审查通过后,PR即被merge
CI/CD工具
由于之后的软工项目开发过程中需要用到持续集成(Continuous Integration, CI)即持续部署(Continuous Delivery, CD)工具,因此对于典型的CI/CD工具进行尝试。
gitlab-ci
我使用了2020年OO课第三单元第三次作业的半成品源码进行测试,在工程的根目录加入.gitlab_ci.yml
文件触发ci测试。
仓库的根目录情况如图:
触发测试时,可以在这里看到pipeline各阶段的运行进度和结果:
我使用的gitlab_ci.yml
主要涉及了以下几个部分:
-
确定镜像
image: local-registry.inner.buaaoo.top/image-dev/java:11
根据该项目使用的jdk版本选择镜像。
-
定义宏
variables: START_INFO: start
对于宏定义进行了简单的尝试。
-
before_script
before_script: - java -version - javac -version - mvn -v
before_script阶段展示java、mvn版本信息。
-
stages
-
build
mvn_build: stage: build script: - echo "${START_INFO}" - mvn compile - tree -a
build阶段简单使用宏(虽然宏好像主要是用在路径确定、阈值等方面,但本次测试先采用这个笨办法先做尝试。
运行结果:
-
demo
mvn_demo: stage: demo dependencies: - mvn_build script: - echo "start demo test" - mvn compile - cd target && cd classes - echo -e "ap 194 xllxr 9772201821533713290 140" > input.txt - java MainClass < input.txt - rm input.txt
在demo阶段简单构造测试用例,进行测试。
运行结果:
-
test
mvn_test: stage: unit_test dependencies: - mvn_build script: - echo "Run JUnit4" - mvn cobertura:cobertura - mvn cobertura:dump-datafile coverage: '/coverage line-rate="\d+/'
在test阶段运行JUnit测试,并检查覆盖率
运行结果:
-
github aciton
GitHub Action是GitHub推出的CI/CD服务,其服务和gitlab-ci相同,但是使用方法和配置文件的输写略有不同。
为了与前文gitlab-ci的使用进行区分,我是用了曾经参与开发的python包来进行github action的测试。
仓库根目录情况如图:
可以在Actions中查看以往的测试结果:
点开某次具体测试,可以看到测试的详细信息,包括所有的jobs和每个job的阶段:
我使用的.github/workflows/test.yml
主要涉及了以下几个部分:
-
初始化和基础设置
name: Init Test on: - push - pull_request jobs: init: name: Init a test for github action runs-on: ${{ matrix.os }} strategy: matrix: os: - 'ubuntu-18.04' python-version: - '3.7' steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }}
该部分主要对测试环境、触发测试条件进行配置。
-
pip安装依赖
- name: Install python dependencies run: | python -m pip install --upgrade pip pip install --upgrade flake8 setuptools wheel twine pip install -r requirements.txt pip install -r requirements-test.txt
该部分主要用来安装pip,并使用pip安装该项目的相关依赖。依赖及其版本都已经写在
requirements.txt
和requirements-test.txt
中。 -
测试基本环境
- name: Test the basic environment run: | python -V python -c "import sys; print(sys.version)" pip --version pip list tree .
此阶段主要用来测试基本环境是否符合要求。
-
单元测试
- name: Start Unit Test run: | pip install -e . pytest --version pytest ./test --cov-report term-missing --cov=./dcmodule -sv -m unittest --cov-fail-under=90
单元测试阶段。触发pytest单元测试,并设定覆盖率阈值,展示详细测试结果。
运行结果如下:
-
部署项目
- name: Build package run: | pip install -e . dcmodule -v
该部分在测试环境中部署该项目,并简单测试是否部署成功。
运行结果如下:
-
测试项目功能
- name: Test function run: | git clone "https://github.com/CindyZhouYH/test_github_action_demo.git" "demo" cd demo dcmodule -t "test_main.py" -i "This is input" -o "This is output" dcmodule -t "test_main.py" -i "input.txt" -o "output.txt" cd ..
该部分将测试项目的基本功能是否正常。通过clone demo所在仓库获取demo文件,并进行测试。
运行结果如下:
使用感受
在使用CI/CD工具时,我目前体会到了以下优点:
- 在测试中加入单元测试环节、代码质量评价,可以及时发现代码中的bug,并确保开发的质量。但这一有点必须要勤push才能体现,即在开发过程中,一旦源代码有所改动,应当及时进行版本控制。
- 可以通过该工具控制和反映项目进展,便于在共同开发时对于进度的交流。
- 添加环境配置参数,可以限制所有开发者开发所用的版本等基本环境的一致性,便于之后的合并。
以上为我在简要尝试CI/CD后的感受和心得,希望在今后软工开发过程中能更加熟练地使用这些工具,感受其优越性。