我们经常听到个人开发和团队开发这两个词,所谓个人开发就是一个人把控产品的所有内容;而团队开发则是由多个人组团并完成产品的开发。要实施团队开发以下几点是必不可少的:
- 对开发过程中的各种事件(例如:谁到什么时间完成了什么事情)进行管理和共享。
- 在团队内部共享各类工作成果以及新的知识技巧等。
- 管理工作成果的变更,既要防止成果被破坏,又要保证各个成员利用现有成果并行作业。
- 证明团队开发出的软件在任何时候都是可以正常运行的。
- 使用自动化的工作流程,让团队成员能够正确的实施开发、测试和部署。
例如:使用邮件进行沟通可能出现邮件数量太多导致重要的邮件被埋没,无法管理状态,不知道哪些问题已经解决,哪些问题尚未处理,如果用全文检索邮件的方式来查询相关问题效率过于低下。
解决方案:使用缺陷管理工具。
例如:收到项目正式环境中发生的故障报告后,需要还原正式环境需要花费很长的时间。
解决方法:实施持续交付。
解决方法:实施版本控制。
例如:正式环境和开发环境中数据库表结构不一致或者某个表列的顺序不一致。
解决方法:实施版本控制。
例如:解决一个bug可能引入其他的bug或者造成系统退化,不正确的使用版本系统覆盖了其他人的修改,修改的内容相互发生了干扰,如果问题不能尽早发现,那么等过去几个月后再想追溯问题就非常麻烦了。
解决方法:实施持续集成,将团队成员的工作成果经常、持续的进行构建和测试。
解决方法:实施版本控制。
例如:在实施代码重构(在不影响代码产生的结果的前提下对代码内部的构造进行调整)时可能引发退化。
解决方法:大量的可重用的测试并实施持续集成。
解决方法:版本控制系统、缺陷管理系统和持续集成之间需要交互,最好能够和自动化部署工具集成到一起来使用。
解决方法:实施持续交付。
基于对上述问题的阐述和分析,我们基本上可以得到以下的结论,在团队开发中版本控制、缺陷管理和持续集成都是非常重要且不可或缺的。
针对上面提到的一系列问题,我们可以得出一个简单的结论,版本控制是实施团队开发的首要前提,必须通过版本控制对产品研发过程中产生的各种信息进行管理,这些内容包括:
- 代码。
- 需求和设计的相关文档。
- 数据库模式和初始数据。
- 配置文件。
- 库的依赖关系定义。
Git是诞生于2005年的一个开源分布式版本控制系统,最初是Linus Torvalds(Linux之父) 为了帮助管理Linux内核开发而开发的一个版本控制软件。Git与常用的版本控制工具Subversion等不同,它采用了分布式版本控制的方式,在没有中央服务器支持的环境下也能够实施版本控制。
对于有使用Subversion(以下简称为SVN)经验的人来说,Git和SVN的共同点是摒弃了传统的基于锁定模式的版本控制(早期的CVS和VSS使用了锁定模式,当一个开发者编辑一个文件时会锁定该文件,其他开发者在此期间无法编辑该文件),采用了更有效率的基于合并模式的版本控制,而二者的区别在于:
- Git是分布式的,SVN是集中式的,SVN需要中央服务器的支持才能工作。
- Git把内容按元数据方式存储,而SVN是按文件,即把文件的元信息隐藏在一个.svn文件夹里。
- Git分支和SVN的分支不同,SVN对分支的处理是相当“狗血”的。
- Git没有一个全局版本号,但是可以自己维护一个版本标签。
- Git的内容完整性要优于SVN,Git的内容存储使用的是SHA-1哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。
总而言之,Git真的非常棒!!!
可以在Git官方网站找到适合自己系统的Git下载链接并进行安装,macOS和Windows平台下安装Git都非常简单,Linux下如果要安装官方最新的版本,建议通过官方提供的Git源代码进行构建安装,步骤如下所示(以CentOS为例)。
下载Git源代码压缩文件。
wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.23.0.tar.xz
解压缩和解归档。
xz -d git-2.23.0.tar.xz
tar -xvf git-2.23.0.tar
安装底层依赖库。
yum -y install libcurl-devel
安装前的配置。
cd git-2.23.0
./configure --prefix=/usr/local
构建和安装。
make && make install
安装成功后可以在终端中键入下面的命令检查自己的Git版本。
git --version
如果之前完全没有接触过Git,可以先阅读《git - 简易指南》来对Git有一个大致的了解。
可以使用下面的命令将一个文件夹变成Git仓库。
git init
当你完成了上述操作后,本地目录就变成了下面的样子,下图左边是你的工作区(正在操作的工作目录),而右边是你的本地仓库,中间是工作区和本地仓库之间的暂存区(也称为缓存区)。
提示:用
ls -la
查看所有文件会发现在执行完上面的命令后,文件夹下多了一个名为.git
的隐藏文件夹,这个文件夹就是Git版本仓库。
通过git add
可以将指定的文件或所有文件添加到暂存区。
git add <file>
git add .
这个时候使用下面的命令可以查看工作区、暂存区和本地仓库的状态。
git status
提示:如果不希望将文件添加到暂存区,可以按照提示,使用
git rm --cached <file>
命令将文件从暂存区放回到工作区。
如果这个时候对工作区的文件又进行了修改使得工作区和暂存区的内容并不相同了,再次执行git status
可以看到哪个或哪些文件被修改了,如果希望用暂存区的内容恢复工作区,可以使用下面的命令。
git restore <file>
git restore .
注意:上面的命令目前仍然处于试验性阶段,在Git较早的版本中对应的命令是
git checkout -- <file>
。由于git checkout
这个命令还可以用于切换分支,容易引起混淆,所以Git最新版本中将这个命令的两项功能分别赋予两个新的命令,一个就是上面的git restore
,另一个是git switch
。
如果第一次使用Git,需要配置用户名和邮箱,然后才能将代码提交到仓库。
git config --global user.name "jackfrued"
git config --global user.email "[email protected]"
提示:可以用
git config --list
来查看Git的配置信息。
通过下面的命令可以将暂存区的内容纳入本地仓库,
git commit -m '本次提交的说明'
可以通过git log
查看每次提交对应的日志。
git log
git log --graph --pretty=oneline --abbrev-commit
Git不像SVN那样一定需要中央服务器才能工作,上面我们演示的版本控制操作都是在本地执行的,但是对于企业开发多人协作这样的场景还是需要中央服务器的支持。通常,企业可以选择使用代码托管平台(如GitHub)或自己搭建Git私服的方式来建立中央服务器(版本仓库),当然大多数的企业更倾向于后者。Github创办于2008年4月,目前是全世界最大的代码托管平台,支持企业用户(可以创建私有仓库,私有仓库内容不对外界公开)和普通用户(受限的使用私有仓库,不受限的使用公开仓库,公开仓库内容对他人可见)。Github上面代码库惊人的增长速度证明了它是非常成功的,在2018年6月被微软以75亿美元的天价收购。
国内也有不少类似Github的代码托管平台,最有名的当属码云和CODING,目前码云和CODING对注册用户都提供了受限的使用私有仓库的功能,支持Pull Request(一种对话机制,可以在提交你的工作成果时让相关人员或团队注意到这件事情),同时还提供了对缺陷管理、Webhook等功能支持,这些使得版本控制系统还具备了缺陷管理和持续集成的能力。当然,很多公司都不愿意将自己的商业代码托管于别人的平台,这样的公司可以用Gitlab来搭建公司内部的Git私服,具体的做法在下一章为大家介绍。
这里我们直接以码云为例来说明使用Git服务器的一些注意事项。首先需要在码云上注册账号,当然也可以使用第三方登录(github账号、微信账号、新浪微博账号、CSDN账号等),登录成功后就可以创建项目,创建项目几乎是“傻瓜式”的,无需赘述,我们只对几个地方加以说明。
-
创建项目时不建议勾选如下图所示的这些选项,编程语言可以暂时不做选择,而
.gitignore
模板也可以稍后自己编写或者通过更专业的工具(如:http://gitignore.io/网站)自动生成。 -
添加项目成员。创建项目后,可以在项目的“设置”或“管理”中找到“成员管理”功能,这样就可以将其他开发者设置为项目团队的成员,项目成员通常分为“所有者”、“管理者”、“普通成员”和“受限成员”几种角色。
-
项目的分支。创建项目后,项目只有一个默认的master分支,应该将该分支设置为“保护分支”来避免项目管理者之外的成员修改该分支。当然,如果需要我们也可以在线创建新的代码分支。
-
设置公钥实现免密访问。在项目的“设置”或“管理”中我们还可以找到“部署公钥管理”的选项,通过添加部署公钥,可以通过SSH(安全远程连接)的方式访问服务器而不用每次输入用户名和口令。可以使用
ssh-keygen
命令来创建密钥对。ssh-keygen -t rsa -b 2048 -C "[email protected]"
说明:上面命令生成的密钥对在
~/.ssh
目录下,公钥文件默认的名字为id_rsa.pub
,可以通过cat id_rsa.pub
来查看自己的公钥。Windows用户在安装Git工具后,可以通过Git Bash来输入上面的命令。
拥有了Git服务器之后,我们就可以通过Git的远程操作将自己的工作成果推到服务器的仓库中,也可以将他人的工作成果从服务器仓库更新到本地。我们以刚才在码云上创建的仓库(仓库名为python
)为例来说明如何进行远程操作。可以在如下所示的页面上找到仓库的地址(URL),如果配置了SSH Key就使用SSH方式访问仓库,否则就用HTTPS方式,后者需要在进行远程操作时提供用户名和口令。
-
添加远程仓库(Git服务器)。
git remote add origin [email protected]:jackfrued/python.git
其中
[email protected]:jackfrued/python.git
是上图中显示的仓库的URL,而前面的origin
是替代这个冗长的URL的字符串,简单的说origin
就是服务器上仓库的别名(如果有多个Git服务器,这个简短的名字也会有多个)。可以用git remote -v
来查看已经指定的Git服务,也可以用git remote remove
来删除指定的Git服务器。 -
将本地代码(工作成果)推送到远程仓库。
git push -u origin master:master
其中,
-u
是--set-upstream
的缩写,用来指定推送的服务器仓库,后面的origin
就是刚才给仓库起的简短的别名,冒号前面的master
是本地分支名,冒号后面的master
是远程分支名,如果本地分支master
已经和远程分支master
建立过关联,则冒号以及后面的部分可以省略。说明:一般不能直接将工作成果push到
master
分支,因为它通常是一个受保护分支。 -
从远程仓库取回代码。
git pull origin master
-
创建和切换分支。下面的命令创建了名为
dev
的分支并切换到该分支。git branch <branch-name> git switch <branch-name>
或
git switch -c <branch-name>
注意:在之前的Git版本中,切换分支使用
git checkout <branch-name>
命令,也可以通过git checkout -b <branch-name>
来创建并切换分支。git switch
命令目前仍然处于试验性阶段,但很明显这个命令更加清晰的表达了它要做的事情。 -
关联远程分支。例如:如果当前所在的分支还没有关联到远程分支,可以使用下面的命令为它们建立关联。
git branch --set-upstream-to origin/develop
如果需要为指定的分支关联远程分支,可以如下操作。
git branch --set-upstream-to origin/develop <branch-name>
提示:上面的操作假设Git服务器上存在名为
develop
的分支,--set-upstream-to
可以缩写为-u
。当然,在创建分支时,如果使用了
--track
参数,也可以直接指定与本地分支关联的远程分支,如下所示。git branch --track <branch-name> origin/develop
如果需要解除本地分支与远程分支的关联,可以使用下面的命令。
git branch --unset-upstream <branch-name>
-
分支合并。例如在
dev
分支上完成开发任务之后,如果希望将dev
分支上的成果合并到master
,可以先切回到master
分支然后使用git merge
来做分支合并,合并的结果如下图右上方所示。git switch master git merge --no-ff dev
使用
git merge
合并分支时,默认使用Fast Forward
合并,这意味着如果删除了分支,分支上的信息就全都丢掉了,如果希望将分支上的历史版本保留下来,可以使用--no-ff
参数来禁用Fast Forward
。在合并分支时,没有冲突的部分Git会做自动合并。如果发生了冲突(如
dev
和master
分支上都修改了同一个文件),会看到CONFLICT (content): Merge conflict in <filename>. Automatic merge failed; fix conflicts and then commit the result
(自动合并失败,修复冲突之后再次提交)的提示,这个时候我们可以用git diff
来查看产生冲突的内容。解决冲突通常需要当事人当面沟通之后才能决定保留谁的版本,冲突解决后需要重新提交代码。 -
分支变基。分支合并操作可以将多个分支上的工作成果最终合并到一个分支上,但是再多次合并操作之后,分支可能会变得非常的混乱和复杂,为了解决这个问题,可以使用
git rebase
操作来实现分支变基。如下图所示,当我们希望将master
和dev
上的工作成果统一到一起的时候,也可以使用变基操作。git rebase master git switch master git merge dev
当我们在
dev
分支执行git rebase
命令时,将首先计算dev
分支和master
分支的差集,然后应用该差集到dev
分支,最后我们切回到master
分支并执行操作合并,这样就看到了如上图右下方所示的干净的分支。 -
删除分支。删除分支可以使用
git branch
加上-d
参数,如果分支上的工作成果还没有合并,那么在删除分支时会看到error: The branch '<branch-name>' is not fully merged.
这样的错误提示。如果希望强行删除分支,可以使用-D
参数。删除分支的操作如下所示。git branch -d <branch-name> error: The branch '<branch-name>' is not fully merged. If you are sure you want to delete it, run 'git branch -D <branch-name>'. git branch -D <branch-name>
如果要删除远程分支,可以使用下面的命令,但是请慎重的操作。
git branch -r -d origin/develop git push origin :develop
-
git fetch
:下载远程仓库的所有变动,可以将远程仓库下载到一个临时分支,然后再根据需要进行合并操作,git fetch
命令和git merge
命令可以看作是之前讲的git pull
命令的分解动作。 -
git diff
:常用于比较工作区和仓库、暂存区与仓库、两个分支之间有什么差别。 -
git stash
:将当前工作区和暂存区发生的变动放到一个临时的区域,让工作区变干净。这个命令适用于手头工作还没有提交,但是突然有一个更为紧急的任务(如线上bug需要修正)需要去处理的场景。 -
git reset
:回退到指定的版本。该命令主要有三个参数,如下图所示。 -
git cherry-pick
:挑选某个分支的单次提交并作为一个新的提交引入到你当前分支上。 -
git revert
:撤回提交信息。 -
git tag
:经常用于查看或新增一个标签。
既然Git是团队开发必备的工具,那么在团队协作时就必须有一个规范的工作流程,这样才能让团队高效的工作,让项目顺利的进展下去,否则工具再厉害但团队成员各自为战,冲突就会无处不在,协作更加无从谈起。我们仍然以刚才码云上创建的python
项目为例,来说明Git的分支管理策略。
-
克隆服务器上的代码到本地。
git clone [email protected]:jackfrued/python.git
-
创建并切换到自己的分支。
git switch -c <branch-name>
-
在自己的分支上开发并在本地做版本控制。
-
将自己的分支(工作成果)推到服务器。
git push origin <branch-name>
-
在线发起一次合并请求(通常称之为Pull Request,有的地方称为Merge Request),请求将自己的工作成果合并到
master
分支,合并之后可以删除该分支。
上面这种分支管理策略就是被称为github-flow或PR的流程,它非常简单容易理解,只需要注意以下几点:
master
的内容都是可以进行发布的内容(不能直接在master
上进行修改)。- 开发时应该以
master
为基础建立新分支(日常开发任务在自己的分支上进行)。 - 分支先在本地实施版本控制,然后以同名分支定期向服务器进行push操作。
- 开发任务完成后向
master
发送合并请求。 - 合并请求通过审查之后合并到
master
,并从master
向正式环境发布。
当然,github-flow的缺点也很明显,master
分支默认就是当前的线上代码,但是有的时候工作成果合并到master
分支,并不代表它就能立刻发布,这样就会导致线上版本落后于master
分支。
除了上述的github-flow分支管理策略外,还有一种名为git-flow的分支管理策略,它也是大多数公司愿意使用的一套流程。Git-flow借鉴了中央集权型版本控制系统的长处,为团队内部统一建立、合并和关闭分支的方法,如下图所示。
在这种模式下,项目有两个长线分支,分别是master
和develop
,其他都是临时的的辅助分支,包括feature
(开发特定功能的分支,开发结束后合并到develop
)、release
(从develop
分离出来的为发布做准备的分支,发布结束后合并到master
和develop
)和hotfix
(产品发布后出现问题时紧急建立的分支,直接从master
分离,问题修复后合并到master
并打上标签,同时还要合并到develop
来避免将来的版本遗漏了这个修复工作,如果此时有正在发布中的release
分支,还要合并到release
分支)。具体的实施过程如下所示:
-
最开始的时候只有
master
和develop
分支,如上图左侧所示。 -
从
develop
分支创建feature
分支(上图右上),工作完成后将工作成果合并到develop
分支(上图右中)。创建
feature
分支:git switch -c myfeature develop
或
git checkout -b myfeature develop
将
feature
分支合并到develop
分支:git checkout develop git merge --no-ff myfeature git branch -d myfeature git push origin develop
-
从
develop
分支创建release
分支,发布结束后合并回master
和develop
分支。创建
release
分支:git switch -c release-0.1 develop ... ... ... git commit -a -m "............"
将
release
分支合并回master
和develop
分支:git checkout master git merge --no-ff release-0.1 git tag -a 0.1 git checkout develop git merge --no-ff release-0.1 git branch -d release-0.1
-
从
master
分支创建hotfix
分支,在修复bug后合并到develop
和master
分支(上图右下)。创建
hotfix
分支:git checkout -b hotfix-0.1.1 master ... ... ... git commit -m "............"
将
hotfix
分支合并回develop
和master
分支。git checkout master git merge --no-ff hotfix-0.1.1 git tag -a 0.1.1 git checkout develop git merge --no-ff hotfix-0.1.1 git branch -d hotfix-0.1.1
Git-flow流程比较容易控制各个分支的状况,但是在运用上github-flow要复杂得多,因此实际使用的时候通常会安装名为gitflow
的命令行工具或者使用图形化的Git工具(如:SmartGit、SourceTree等)来简化操作,具体的可以参考《git-flow 的工作流程》一文,因为这篇文章写得已经很好了,本文不再进行赘述。
没有好的团队管理工具必然导致项目进展不顺利,任务管理困难,而引入缺陷管理系统正好可以解决这些问题,通常一个缺陷管理系统都包含了以下的功能:
- 任务管理(包括必须做什么、谁来做、什么时候完成、现在处于什么状态等)。
- 直观而且可以检索过去发生的各种问题。
- 能够对信息进行统一的管理和共享。
- 能够生成各类报表。
- 能够关联到其他系统,具有可扩展性。
禅道是国产的专业项目管理软件,它不仅仅是缺陷管理工具,它提供了完整软件生命周期管理功能,支持Scrum敏捷开发,能够实现需求管理、缺陷管理、任务管理等一系列的功能,而且拥有强大的扩展机制和丰富的功能插件。可以从禅道的官方网站提供的下载链接来下载禅道,推荐使用一键安装包。
下面仍然以CentOS Linux为例,讲解如何利用官方提供的一键安装包来安装禅道。
cd /opt
wget http://dl.cnezsoft.com/zentao/pro8.5.2/ZenTaoPMS.pro8.5.2.zbox_64.tar.gz
gunzip ZenTaoPMS.pro8.5.2.zbox_64.tar.gz
tar -xvf ZenTaoPMS.pro8.5.2.zbox_64.tar
我们在/opt
目录下(官方推荐使用这个目录)下载了禅道的归档压缩文件,并进行了解压缩和解归档的操作,完成上述步骤后,会看到一个名为zbox
的文件夹。一键安装包中内置了Apache、MySQL、PHP等应用,也就是说这些都不需要单独安装部署了,接下来我们通过下面的命令来启动禅道。
/opt/zbox/zbox -ap 8080 -mp 3307
/opt/zbox/zbox start
说明:上面使用
zbox
文件夹下的zbox
命令,其中-ap
是为了指定Apache服务器使用的端口,-mp
是为了指定MySQL数据库使用的端口,这里使用3307端口是为了避开服务器上可能已经存在的MySQL服务的3306端口;start
表示启动服务,stop
可以用来停止服务。此外,需要打开防火墙8080端口以便访问禅道,注意数据库的端口决不能暴露给公网。
打开浏览器,输入服务器的公网IP地址就可以访问禅道,如果愿意,也可以通过DNS解析绑定一个域名来进行访问,禅道的首页如下图所示,默认的管理员是admin
,口令是123456
。
第一次使用禅道时,建议通过点击用户名,然后通过“帮助”菜单的“新手教程”来迅速了解禅道。官方网站的文档链接中提供了视频教程,初学者也可以通过视频教程来上手。
一般的代码托管平台或者是我们之前提到的Git私服Gitlab都提供了缺陷管理的功能,当我们要报告一个bug时,可以在如下图所示的界面创建一个新的问题票(issue ticket)。填写的内容包括:
- **[必填]**出现问题的软件版本号、具体的使用环境(如操作系统)等相关信息。
- **[必填]**能够稳定重现该问题的相关步骤。
- **[必填]**描述此处期待的行为和实际的行为。
- **[可选]**你对这个bug的看法(产生bug的原因是什么)。
如上图所示,我们在创建问题票时,还需要将问题指派给处理问题的人,如果不清楚应该由谁来修复这个bug,就指派给项目管理者,除此之外还要指定问题的优先级(十分紧急、紧急、普通、不紧急等)、问题的标签(功能缺陷、新特性、改进增强、前瞻研究等)、里程碑(通过里程碑可以将问题与某些特定的项目节点关联起来,之后可以查看每一个里程碑的进展,可以基于软件版本号来建立里程碑,也可以基于迭代周期来建立里程碑)以及需要在哪个时间点以前修复等信息。
有些敏捷团队使用问题票来管理产品的需求,称之为“问题驱动开发”(TiDD),也就是说新功能的开发是通过创建问题票来驱动的,具体的步骤包括:建立问题票、指定责任人、开发、提交、Push到代码库。如果要创建一个和需求相关的问题票,应该要填写以下的内容:
- **[必填]**简短的描述需求,并用它作为标题。
- **[必填]**这个需求是解决什么问题的。
- **[必填]**这个需求对软件现有功能会造成什么影响。
- **[必填]**这个需求应该实现什么样的功能。
- **[必填]**这个需求是否依赖其他模块提供相关支持。
- **[可选]**这个需求有哪些实现方式。
- **[可选]**这些可选的实现方式分别有哪些优缺点。
除了禅道和GitLab之外,JIRA、Redmine、Backlog等也是不错的缺陷管理系统。目前,这些系统大都不仅仅提供了缺陷管理的功能,更多的时候它们可以作为敏捷闭环工具来使用,关于敏捷闭环工具这个话题,请大家参考《基于JIRA的Scrum敏捷开发的项目管理》一文。
为了快速的产生高品质的软件,在团队开发中,持续集成(CI)也是一个非常重要的基础。按照经典的软件过程模型(瀑布模型),集成的工作一般要等到所有的开发工作都结束后才能开始,但这个时候如果发现了问题,修复问题的代价是非常具体的。基本上,集成实施得越晚,代码量越大,解决问题就越困难。持续集成将版本控制、自动化构建、代码测试融入到一起,让这些工作变得自动化和可协作。由于其频繁重复整个开发流程(在指定时间内多次pull源代码并运行测试代码),所以能帮助开发者提早发现问题。
在所有的CI工具中,Jenkins和TravisCI是最具有代表性的,前者是基于 Java的开源CI工具,后者是新晋的在线CI工具,下图是Jenkins的工作面板。
持续集成对于编译型语言的意义更大,对于Python这样的解释型语言,更多的时候是用于对接版本控制系统触发自动化测试并产生相应的报告。类似的功能也可以通过在Git服务上配置Webhook来完成,码云甚至可以直接对接钉钉开放平台使用钉钉机器人来向项目相关人员发送即时消息。Gitlab也对CI和CD(持续交付)提供了支持,具体内容请大家参考《GitLab CI/CD基础教程》。
说明:
关于敏捷开发的相关内容,有兴趣的读者可以阅读知乎上的《这才是敏捷开发》一文。
本章中的部分插图来自于网易云课堂《人人都会用Git》课程(免费哟),在此表示感谢。