高效团队开发

1. 什么是团队开发

待解决的问题

  • “谁”“到何时为止”做了“什么事情”,“怎样”算做“完成”等;
  • 在团队内部共享代码等各类工作成果;
  • 保证各成员能够利用工作成果并行作业,同时防止工作成果遭到破坏;
  • 在团队中共享从项目中学到的知识;
  • 证明开发出的软件,在任何时间都是可以正常运行的;
  • 构建自动化的工作流程,确保任何人都可以正确的开发、测试和发布;

如何解决问题

  • 版本管理
  • 缺陷管理
  • 持续集成
  • 持续交付
  • 回归测试

2. 团队开发中的问题

重要的邮件太多,无法确定处理的优先顺序

使用邮件交流问题并不是一个好的方法,因为邮件算不上一种格式化的数据,很难归类整理;

解决方法:使用版本缺陷系统进行问题的管理;

没有能用于验证的环境

如果没有用于验证的环境,将导致每次复现 BUG 需要花费很长的时间,导致效率很低;

解决方法:搭建多个环境,分别用于开发、验证、测试、发布等;

用别名目录管理分支

用于实现谁、何时、做了什么样的修改;

重新制作数据库比较困难

数据库的变更操作也应纳入版本系统进行管理,确保每次的操作内容、顺序在各个环境都是一致的,而不是各个开发环境执行自己的;

不运行系统就无法察觉的问题

测试时,需要确保将全员的代码集中到一起运行,以免发生退化;

解决方案:持续集成,每次提交新代码,就合并代码并自动化测试,在第一时间暴露问题;

覆盖了其他组员修正的代码

当合并其他成员的代码时,如果出现冲突,有些开发人员可能直接将其他成员的代码进行覆盖,导致出现问题;

解决方案:持续集成;

无法自信的地进行代码重构

缺少措施避免出现退化;

解决方案:自动化测试;

不知道 BUG 的修正日期,也不能追踪退化

缺少缺陷跟踪系统,导致需要从一堆邮件中查询当时的情况;

解决方案:CI、缺陷跟踪、版本管理三个工具是确保项目高效开发的神器;甚至还应该考虑使用自动化部署;

没有灵活使用分支和标签

导致合并的时候容易出现混乱;有时在切换分支修复某些 BUG,切换回新功能分支时,忘了合并刚才的 BUG 分支;

在测试环境、正式环境上无法运行

缺少统一管理第三方模块,确保在各个环境的依赖实现一致性的办法;

解决方案: docker;

发布太复杂,以至于需要发布手册

涉及如何更新DDL、依赖的库以及配置文件;

解决方案:持续交付;

3. 版本管理系统

Git 等分布式版本管理系统

优点

  • 能将代码完整地复制到本地
  • 运行速度快
  • 临时作业的提交易于管理
  • 分支、合并简单方便
  • 可以不受地点的限制进行协作开发

缺点

  • 系统中没有真正意义上的最新版本
  • 没有真正意义上的版本号
  • 工作流程的配置过于灵活,容易产生混乱
  • 有一定的学习成本

需纳入版本管理的内容

  • 代码
  • 需求定义和设计等文档
  • 库的依赖
  • 数据库初始化命令
  • 环境配置文件

标签

Git 的每次提交有一个唯一识别码,但是它比较难记,为了让它更具备可识别性,可以为该提交添加标签,通过标签来识别;

虽然分支也可以使用标签,但我发现好像并不是很有必要,貌似直接用分支名就够了;

工作模式

中央集权型工作流

Github 型工作流

分支策略模式

Git-flow

主分支
  • master:为发布而建的分支,每次发布时都打上标签
  • develop:开发用的分支,发布前的最新版本
临时分支
  • feature:分离自 develop,开发特定功能的分支
  • release:分离自 develop,为发布做准备的分支,避免混入多余的 feature;
  • hotfix:分离自 master,修复 master 分支的故障;修复后,需要被合并到 master\develop\release 三个分支;

有专门的 git-flow 脚本实现以上的管理模式;
git-flow 的缺点是有些复杂,需要学习适应一下;同时不支持 GUI 可视化工具;

Github-flow

git-flow 的优点是非常清晰可靠,缺点是看上去有点复杂不易理解,因此产生了 github-flow 模式来降低学习成本;

Github-flow 流程
  • master 分支的内容都可以进行发布;
  • 添加内容时,直接从 master 分支新建分支
  • 建立的分支在本地环境上提交,并以同名的分支定期向远程代码库进行 push
  • 开发结束后向 master 分支发送 pull request
  • pull request 在被审核通过后,合并到 master,并从 master 向正式环境发布;

Jenkins 可以监控仓库中中的所有分支,这样每个分支 push 到仓库后,都会触发自动构建和测试,确保代码没有问题;

数据库模式和数据的管理

问题发生的原因在于如果不同的开发人员,在设置数据库初始化的命令顺序上面,可能存在冲突,导致一些功能无法正常运行;

数据库版本管理的必要条件

  • 在任意环境中,都能使用相同的步骤来构建数据库
  • 能够反复执行多次
  • 格式为文件

数据库迁移

基本原理:将数据库初始化需要用的 SQL 命令写入文件,这些文件按数字进行顺序命令,每次启动程序时,按照这些文件进行数据库初始化;

由于是分布式开发,每个开发人员新添加的 SQL 文件可能存在命名冲突,当合并分支时,Git 会报错,此时需要解决冲突,并再次提效修改后的版本;由于修改后的版本是最新版本,因此其他人在合并该版本时,会自动覆盖其本地的版本,因此,仍然能够实现正确的初始化;

SQL 文件中的初始化命令是成对出现的,它提供了回滚机制,当出现冲突时,先回滚当前的数据库,再按照最新的版本,重新初始化;

由于冲突是通过手工合并 SQL 命令来解决的,因此不可避免存在错误的可能性,此时就需要通过增加测试代码,来确保万无一失;

配置文件的管理

配置文件包括环境变量、密码等信息,这些信息不适合纳入版本管理,因此需要单独上传部署的服务器,此时可以通过编写一个部署脚本,来实现自动上传;而上传的信息,同样可以写在配置文件中,而脚本本身可以纳入版本管理;

常用部署的工具:Chef, Puppet, Capistrano, Fabric, ServerSepc;

依赖关系的管理

大多数语言都有自己的依赖管理工具和公共仓库,例如 Java 的 Maven,Node 的 npm,Python 的 Pypi 等;

这些现成的工具的原理:

  • 设置一个中央仓库;
  • 使用一个文件来定义对库的依赖;
  • 执行上述依赖文件的脚本;

4. 任务管理

任务管理系统的优点

  • “有须做什么”的任务定义
  • “谁来做”的职责分配
  • “什么时候完成”的期限管理
  • “作业中或已完成”的状态管理

其他优点

  • 直观性
  • 方便检索
  • 对信息统一管理及共享
  • 能够生成各类报表
  • 能够与其他系统进行关联,具备可扩展性;

任务驱动开发

将新功能或者 BUG 任务登记在缺陷管理系统中,每次代码的提交,都与某一个具体的任务单相对应,禁止没有任务单号的提交;

通过设置 Github 的 Webhook,可以实现将 commit 和相应的任务单进行关联;

开发新功能、修改BUG的工作流程

  • 建立任务单
  • 指定责任人
  • 开发
  • 提交:提交的时候,记得标注对应哪个问题单号;这样可以通过问题单,查询到代码修改了哪些内容、什么时候修改的;也可以反向查询,即找到当前代码的修改,对应到哪些问题单,从而知道当时什么要做如此的修改;
  • Push 到代码库

管理对象

  • epic
  • story
  • task
  • bug

其他

Redmine 安装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
version: '3.7'

services:

redmine:
image: redmine
restart: always
ports:
- 8080:3000
environment:
REDMINE_DB_POSTGRES: db
REDMINE_DB_PASSWORD: secret
REDMINE_DB_USERNAME: redmine
networks:
redmine-network:
depends_on:
- db

db:
image: postgres
restart: always
environment:
POSTGRES_PASSWORD: secret
POSTGRES_USER: redmine
networks:
redmine-network:


networks:
redmine-network:

Redmine 访问

localhost:8080

5. CI 持续集成

主要的 CI 工具

  • Jenkins:插件众多,可配置性强;缺点是上手成本高;
  • Travis:需要配合 Github 使用,优点是上手简单;

build 工具

以 Java 为例,常用的构建工具有:

  • Maven:适用于新项目;
  • Ant:适用于已开发一半的项目;

测试代码的写法

常见的测试类型:

  • 单元测试
  • 集成测试
  • 用户验收测试
  • 回归测试

编写测试代码是要付出时间成本的,理想的情况下当然是覆盖以上所有的测试场景;如果不允许,则应至少包括单元测试和集成测试;

棘手的测试

  • 和外部系统有交互的测试
  • 使用 mock 框架进行测试
  • 使用内存数据进行测试:可避免跟数据库中的数据产生耦合;常用工具如 H2 数据库;
  • UI 相关的测试

Jenkins 使用流程

  • 新建任务:一个任务对应一个项目;
  • 下载代码:将 Github 代码地址与项目进行关联,并设置 Github 的 Webhook,在收到 push 请求后,调用 Jenkins 接口拉取最新的代码;
  • 自动执行 Build 和测试:制作一个构建的脚本,并由 Jenkins 进行调用即可;脚本中需设置退出值,以便 Jenkins 判断任务的执行是失败还是成功;
  • 统计结果并生成报表:使用 JUnitXML 形式输出报表有更好的通用性和直观性,虽然它是 JUnit 设定的格式,但其他语言也有相应的库可以生成该格式的报表;
  • 统计覆盖率:常用的覆盖率统计工具有 Cobertura, Jacoco, Scct, simpleCov, Rcov 等(Cobertura 已于 2011 年停止了开发);
  • 静态分析:常用工具 Checkstyle, PMD, Findbugs等;
  • 配置通知:对构建结果进行通知,选择常用插件即可,支持邮件、Twitter、IRC、XFD等;

构建失败的惩罚

设计一个搞笑的仪式,例如警报灯闪烁、弹射球、戴礼帽等;

当构建发生失败时,基于该构建的分支之后编写的代码,将需要禁止提交,直到构建修复成功为止;为了避免出现这种等待的情况,可考虑使用 Github flow 中的 pull request 流程;当收到 pull request 后会自动进行构建,如果成功,则合并到主分支;如果失败,则不合并;

以上功能在 Jenkins 中需要使用 Github pull request builder 插件实现;

确保可追溯性

通过相应的插件,可实现 Jenkins 和 缺陷管理系统的任务单相关,并可以方便的查看每次代码提交的差异;

6. 自动化部署–持续交付

由谁负责

由想实施部署自动化的人着手去做即可,因此一般来说是运维人员牵头;

前提条件

  • 全部团队成员都采用版本管理;
  • 所有的环境使用相同的方式构建;
  • 实现发布工作的自动化,并事先进行验证
  • 要反复多次进行测试

工具链

  • 引导:服务器 OS 的配置自动化;
  • 配置:服务器及中间件的配置自动化;
  • 业务流程:代码部署及发布的自动化;

引导

Kickstart

原理:安装 Linux 时,给内核参数加上 ks=<…> 选项,即可开启从外部设备加载配置文件,实现安装自动化;背后的本质是将安装过程中的选项,以配置文件的形式提前写好;

仅适用于 RHEL 系统的 Linux 系统,不适用于 Debian 系列;

Vagrant

用途:用来创建和配置虚拟环境,可以最大化的利用单台机器的性能;

配置

应用程序总是运行在一定的进程环境中,复杂的应用程序,可能涉及非常多的环境配置选项,从而带来很大的工作量;

Chef

根据提前写好的配置规则(cookbooks),让服务器安装软件包和配置中间件,实现自动化;

可以为应用服务器和数据库服务器分别编写 cookbook,这样就能够复制搭建服务器的步骤,实现批量化;

Serverspec

用途:一个测试框架,可对服务器的配置进行单元测试,确保服务器如预期的正常运行;

最佳实践1:使用虚拟环境

  • 使用 Vagrant 创建干净的虚拟环境;(怀疑之处可考虑结合 docker 来创建虚拟环境);
  • 拉取 Chef 的 cookbook 和 Serverspec 的测试用例;
  • 执行 Chef,完成服务器的配置;
  • 执行 Serverspec,完成对服务器的状态测试,确保配置成功;
  • 将结果反馈给 jenkins;

以上各个工具都有相应的替代器,因此无须局限于以上工具的使用,重点在于选择最适合团队使用的工具;

最佳实践2:使用物理机

Kickstart + Chef + Serverspec 的组合;

与实践1的差别在于将 Vagrant 替换为 Kickstart;

发布自动化

Capistrano

Capistrano 使用 Push 的方式,无须在应用程序和数据库服务上面安装,只需执行服务器能够通过 SSH 登录前两种服务器即可;

Fabric

功能同 Capistrano,差别在于使用 Python,而非 Ruby;另外 Fabric 任务可以顺序执行,也可以并行,甚至还可以分组顺序执行,组内则是并行;相当灵活;

Jenkins

Jenkins 同样可以用来实现发布的自动化,不过需要在从节点上安装 Jenkins 才行,还好只需能够 SSH 登录从节点,即可在主节点上远程进行安装;

相对于前面两个工具,Jenkins 的优点在于:

  • 可视化的控制台;
  • 可实现发布任务的权限管理
  • 可查询发布的详细历史记录;

最佳实践

组合使用 Jenkins + Fabric,这样既可以利用 Jenkins 的日志功能,又可以获得 Fabric 灵活易用的功能;

手动部署的工具

如果可能,尽量所有部署工作设置为自动化;如果出现少数需要手动部署的特例,例如某台机器磁盘空间不足,则有以下工具可以使用:

  • RLogin
  • Tera Term

以上两个工具可以实现一个终端输入,在多台机器上实施相同操作的效果;

当机器数据很多时,有时可能存在少数几台机器的命令执行不成功,此时在执行下一条指令前,应先就上一条指令的结果进行验证,确保无误后,再执行下一条指令;

其他相关问题

不中断服务的部署方法

蓝绿部署

原理:将机器分成两组,先部署其他的一组,成功后,再部署剩下的一组;

缺点:部署过程中会暂停一半的机器资源,会给系统带来比较大的压力,除非增加备用机器,但那样做的成功过高;

云蓝绿部署

原理:机器不分组,部署前增加一批新机器进行部署,部署成功后,删除旧机器;

该方法克服了传统蓝绿部署的缺点,但会增加复杂性和出错概率,因为务必要先实现部署的自动化和自动测试;

回滚

部署失败不可避免,因此应随时有回滚的机制,包括代码回滚、数据库回滚两种;

回滚时,除了回滚源代码外,还应以服务器的环境进行验证,确保可用;同时还需考虑数据库的迁移;

数据库的回滚分为两种情况,一种是允许新数据的丢弃,一种是不允许;后一种情况比较麻烦,因此,在发布新代码前,应分成两步进行测试,先确保只更新数据库的情况下,旧代码能够顺利进行;之后再进行新代码的发布;此时如果出现问题,也只需回滚代码部分,无须回滚数据库部分;

PasS

自动化部署需要花费一定的学习和时间成本,因此也有一个选项是考虑使用 PasS 服务;只需 push 代码到相应的平台,即可实现自动化部署;

PasS 适用场景
  • 没有足够资源,但希望快速推出产品,收集反馈的项目;(创新项目)
  • 无法预测峰值负荷的服务;(新上线的手机游戏)
  • 生命周期短的服务(例如展会);
  • 与现有产品进行配合的小项目;
PasS 缺点
  • 当流量很大时,费用成本出现不成比例的上升;
  • 有时难以获得想要的日志进行问题分析;
  • 获得的服务等级和合同约定可能不相符;

7. 回归测试

回归测试

Selenium

Selenium 并不是单个软件,而是一套工具集;

几个常用的 Selenium 工具

Selenium IDE

以 Firefox 的插件形式出现,可以录制键盘和鼠标动作,因此对于非技术人员依然非常友好;

Selenium Remote Control

在测试脚本和浏览器之间,增加了一个服务器作为中间层;由于这个抽象层后,可以使用各种语言编写测试脚本,使得测试更加灵活,例如可以实现循环和分支等;

Selenium WebDriver

出于安全考虑,主流的浏览器都会对 Javascript 的调用进行限制,WebDriver 的目标即是绕开这些限制,实现调用 OS 的原生接口;在结合 Selenium 后,推出了 Selenium2;

制作测试用例

测试由测试用例组成,多个测试用例可以组合一个测试组;测试组内部的用例是顺序执行的;而测试组之间则是并发执行的;好的测试应该能够尽量缩短测试时间,因此,应考虑将测试用例设计成可以并行测试的模式;好的测试组之间不应相互依赖;

由于测试用例的执行是代码级别的速度,而网页的加载则跟网速有关,因此 Selenium
的自动化测试会容易出现失败;解决办法在于执行下一条指令前,应对上一条指令的结果进行确认;如果确认失败,应该重复上一条指令;如果确认成功,再开始执行下一条指令;

Jenkins 和 Selenium 的协作

安装相应的插件,然后配置好相应的参数即可;

Selenium 测试的高速化

  • 利用 Jenkins 的分布式机制,设置主从节点,通过启动多个浏览器,实现并发测试;
  • 为每个浏览器客户端匹配相应的应用程序服务器,实现服务端测试的负载均衡(不太理解为什么不使用 nginx 之类的工具来实现,而是改动 hosts 文件)

多个应用程序版本的测试

通过 Jenkins 的 Parameterized Trigger plugin”插件,可实现对指定版本进行测试;

使用 Git 拉取相应版本的测试用例时,需要配置插件选项,为每个版本生成相应的目录,避免不同版本的测试用例混在一起;

8. 实践

在 Github 上面新建一个项目
拉取该项目的代码到本地
初始化 package.json
npm install 安装依赖
简单写一些代码,实现 hello world
运行代码,确保顺利
写一些单元测试代码
写测试脚本
设置测试脚本的文件为可执行
推送代码到 Github
登录生产服务器,新增用户,安装git,拉取代码,安装依赖,运行应用
登录 Jenkins 服务器,新增用户,安装 Jenkins,启动 Jenkins
配置 jenkins,安装插件,更改密码,新建任务,配置构建脚本;
配置 Github 的 Webhooks,以便 push 后触发 Jenkins 的构建;
生成 SSH-KEY,将公钥存放到应用程序服务器,开放存放文件夹的访问权限;
创建部署文件夹,创建部署的自动化脚本,设置脚本为可执行


高效团队开发
https://ccw1078.github.io/2016/11/04/高效团队开发/
作者
ccw
发布于
2016年11月4日
许可协议