leehbhs 2020-02-22
一个产品的开发和运营,往往需要长时间的开发、运维,这期间伴随着开发人员、产品经理、CTO等岗位的入职与离职,用户需求的变更。人员的变动、产品功能的修改与升级,种种问题都需要一个可控的、可靠的方式来进行管理。让我们来思考一下下面几个问题:
如果产品的新功能反馈不好(产品背锅,哈哈哈),怎么马上撤掉这个功能?
如果产品的新版本出现了重大bug(开发背锅),怎么马上回溯到上一个稳定的版本?
如果出现问题事后要追责,怎么找到那个犯错误的人?
如果一边要修复线上系统的bug,一边要开发新功能,然而新功能还没开发完(不能发布),就要必须发布一个修复Bug的版本上线,肿么办?
类似的,正在开发一个新功能,突然收到一个需要紧急修复的Bug,但手上的新功能还没有开发完成,不能发布,怎么办?
我们看看解决这些问题需要什么功能:
我们需要一个“时光机”,可以回溯到代码的任何时间点。
我们需要一个“复制机”,可以将一个项目复制成几个项目,至少一个拿来修复bug,一个拿来写新功能,二者之间互不影响。
我们需要一个“记录仪”,详细记录哪一句代码是哪个开发者在什么时候写的。
为此,出现了“版本控制”的概念。它就是专门为了解决这些问题应运而生的。
svn、git是常用的版本控制系统。我不跟你扯什么分布式、集中式、svn和git的优缺点这种网上一搜一大把的东西,反之你记住git优于svn
,现在大部分项目已摒弃svn使用Git进行代码的管理。
我们先说一下版本控制系统是什么(svn、git都适用)。版本控制系统分Server
(服务器)和Client
(客户端)。
Server是负责仓库、用户、权限、提交记录等信息的管理系统。Client则是一个个“用户”,拉取项目代码、写代码、提交代码。其中“仓库”是个新概念,可以简单理解为我们保存产品资料、代码的地方。
正常来说,我们需要先搭建git或svn的服务器,才能使用版本控制。但我们现在并不需要,因为有很多公司、组织机构都提供了免费的、有条件免费的、收费的版本控制服务,比如github、azure devOps、、国内的码云等等。
这些网站使我们可以不再搭建服务端,而直接作为一个Client,把代码托管到他们的版本控制系统上。所以我们可以简单的直接创建项目、克隆,开始我们的开发工作,而不是从搭建版本控制系统的服务器开始。
当然,我们可以了解一下如何搭建svn、git的服务器,因为部分公司基于业务的考虑(涉密等需求),可能也会选择搭建自己的版本控制系统服务器。关键词“搭建Git服务器”、“搭建svn服务器”或者“搭建Git/svn服务器 + 操作系统”,可以搜索到诸如搭建Git服务器、Windows环境下搭建SVN服务器的文章。
下面讲的内容都是Client
的部分,也是开发者日常接触最多的内容。
首先以GitHub为例,简单讲一下使用流程。
1.注册一个GitHub账号。
2.右上角,点击“Your profile”,进入个人页面。点击“Repositories”进入仓库tab。点击“New”按钮,新建仓库。
3.填写仓库名等信息,选择“公有/私有”,创建仓库。
公有库是大家都可以看到的仓库,私有库是需要授权才能查看的仓库。
至此,我们在GitHub上创建了一个仓库,下面我们就要“克隆”这个仓库到本地(我们的开发机),并且往仓库里面装东西了。
git可以直接使用指令,不过我更推荐使用TortoiseGit来操作。能点一点就解决的事情,为什么要围栏自己去写命令行呢?VS内置的“Team Explorer”(团队资源管理器)、GitHub Desktop等其他插件也是不错的选择————不用去纠结这些,都是拿来就用的工具,选择一个自己喜欢的就好。
这里我们选择安装TortoiseGit
。安装完成后,进行下面的步骤。
4.在新建的仓库的首页(这里是我随意选择的一个仓库),有一个很醒目的“Clone or download”按钮,点击出现下图。
可以看到,我们可以通过GitHub Desktop、VS、压缩文件等多种方式克隆或者下载项目。
我们点击图标,复制git的克隆地址。
在我们确定“克隆”仓库的文件夹内,右键使用TortoiseGit。
替换Git的Url,文件目录默认是当前的文件目录。
点击OK,TortoiseGit将为我们从Github服务器克隆仓库到本地。
需要注意的是,我们克隆的仓库我们需要拥有权限,才能克隆。所以这里我们可能会需要登录(这里填写Github的登录账户即可)。而之后我们提交代码的时候,Git仓库也会以我们登录的身份来标识不同的开发者。
5.我们的电脑文件夹中出现了仓库,之后我们像正常开发一样,在这个仓库中创建解决方案,开始写代码。
之后我们需要通过“Commit”和“Push”操作,将我们在本地完成的代码上传到仓库。
“Commit”是将更改内容提交到本地,“Push”是将本地的Commit提交到远程仓库Github。
在Message中填写提交的信息,比如“修复XXX问题”,“完成XXX功能”。Message下面的列表则可以查看修改、新增、删除了哪些内容。
然后“Push”。
至此,我们完成了单人的克隆仓库、提交代码到远程仓库的流程。但还有很多点没有讲到。
我们来小结一下这一部分。我们创建了一个新仓库,获得它的服务器地址,Clone到了本地,完成开发工作,Commit修改内容,并将其Push到了远程仓库。好像还是很晕,那就用图说话吧。
需要注意的是,刚刚讨论的是单人开发的情况,在多人开发的时候,会比上述流程多一个步骤————合并代码,解决冲突
。想象一下,大家都在向远程仓库提交代码,同时有两个人修改了同一文件中的相同部分,远程仓库就很疑惑啊:我到底应该以谁为准呢?那它也只能说,你们自己商量好了再提交给我,对吧~所以整个流程就变成了下图的样子:
在执行Push操作之前,我们需要先执行“Pull”命令,拉取其他开发者提交的代码到本地,这个时候可能会出现冲突,即自己和其他开发者都修改了的地方,我们需要逐一确认这些地方应该采用谁的部分:我们本地的、远程仓库Pull拉到的,还是二者都要保留。
在完成合并代码,解决完所有冲突之后,我们才能将我们的代码提交到远程仓库。而其他开发者此时又可以拉下我们的代码,合并代码解决冲突,继续提交他们的代码————整个流程就没有问题了。
上面这部分真的很小白,VS里面的Team Explorer操作更简单更小白,我真的不想写的,但我觉得我不写根本没法往下讲。上面的部分,自己多操作多用就自然会了。
好,这个话题到此结束,我们下面重点讲Git中的重要概念,去尝试理解它。
我们回到刚刚的问题,“时光机”、“复制机”、“记录仪”,它们分别是怎么实现的呢?
“记录仪”实际上我们已经讲完了。
(1)我们使用Git克隆仓库时,登录的账户,就是我们的身份标识。这个账号密码也是可以修改的,想想关键词?“git 配置用户名和密码”、“Git 修改用户名和密码”。
(2)我们在Commit的时候,会填写提交信息,这里一般写本次提交的内容、完成的工作、需要注意的地方。
(3)Git会记录提交的时间。
好,小作文里面的时间、人物、干什么都有了,完成。我们可以在远程仓库的Commit记录里看到每个人提交的详细信息。
“时光机”体现在哪里呢?在我们一次次的提交代码到远程仓库后,远程仓库会拥有每次提交后的一个“版本”,并且给它一个版本号。
我们可以根据这个版本号,使用git命令,回溯到任意一个时间点/任意一个版本。是不是很酷?如此,就解决了回溯问题。这里的关键词应该是“git 回退版本”、“Git 版本回退”,都可以搜索到指令。
“复制机”比较复杂,它运用了一个叫做“brach”(分支)的概念,来解决问题。
分支相当于从当前分支的当前时间点(版本)拷贝一份出来,形成新的“分支”副本。分支之间可以任意切换。
如下图所示,原本我们的开发进程是第一行这些蓝色的点,每个点是一次提交,从左到右串在一起形成了一个完整的分支“Master”。一般“Master”是主分支,也是默认分支。
而我们从“Common base”这个版本起,创建一个新的分支“Feature”,即图上第二行绿色的点串成的线。
Master和Feature相互独立,互不影响,各自执行自己的开发任务,直到将Feature分支合并到Master分支时。
在Master进行了两次提交,Feature进行了三次提交后,执行了合并分支的命令,将Feature合并到Master,这里的“合并”和之前Pull时的合并操作完全相同,也是为了解决不同分支之间的冲突。
合并完成后,Master分支就拥有了原Master分支和Feature此期间开发的所有内容了。
因为可以不停的创建分支,根据不同的分支创建分支,所以关于分支的内容可以很复杂,下图是一种比较复杂的情况。
Master先创建了Develop分支,Develop分支又创建了Feature1(第四行Feature)和Feature2(第五行Feature1)分支。
然后Master、Develop、Feature1、Feature2分支都在持续开发,
一段时间后,Feature1被合并到Develop分支。再根据此时的Develop分支创建Release分支。
Release分支确认功能无误后,合并到Master分支,完成Master分支从0.1-1.0版本的进化。
之后将Release分支的代码合并到Develop分支,继续在Develop分支上开发。
而Feature2分支一直在开发过程中,从未合并到其他分支。
上图的流程是一个比较完善的开发的版本控制流程了。
这里举一个单人开发的例子来说明分支的好处。
假设Master分支是发布版本。我需要开发一个新功能,大约需要3-4天的时间,所以我不能在Master分支上直接写新功能的代码,我应该新建一个“XXX功能”的新分支,从Master分支切换到该分支,在这个新分支上完成新功能的开发。
而在开发期间,我需要修改一个紧急bug,则我应该在Master分支上修改,以便可以及时发布。所以我应该从新分支切换到Master分支,修复bug。之后再切换到新分支,继续完成新功能的开发。
更加详细的说明可以看3.2 Git Branching - Basic Branching and Merging。想搜索中文资料,关键词“Git 分支”、“Git Brach”。
到这里,Git的基本内容算是讲解完毕了,不过Git还有很多高能用法,比如stash
、cherry pick
,可以记个概念,重点是记住关键词和应用场景,这样在需要使用的时候就可以搜索、使用了。
和git一样,svn推荐使用TortoiseSVN。
svn的学习教程有SVN 教程。
其实版本控制不止可以用于开发,在日常生活中,我们也很需要它。回忆一下写毕业论文的时候,你的毕业论文是不是这样的:
毕业论文.docx
毕业论文-1.docx
毕业论文-2.docx
......
毕业论文-终极版.docx
毕业论文-终极版-2.docx
......
哇,看着都头大有没有?为什么要把一个文档复制粘贴那么多遍?
有了版本控制,这个文档就始终只有“一份”,只是它拥有很多个历史版本,而且我们可以通过时光机不断穿梭在它的不同版本中。
所以我现在的很多文档,都是放在Azure DevOps的私有仓库中的,管理起来很方便,还可以多端同步。
有时会遇到这种情况:在同一个分支里,你提交了1-10这10次记录。在第2次提交的时候,完成了功能A。但在第10次提交后,你发现功能A报错了/没有被实现。(假定功能A仅由你一个人负责开发)
此时,第一步是回忆自己在3-10的提交记录中修改了什么内容,大概率可以回忆起自己哪里把功能A修改错误了。但有时通过回忆无法得到答案,因为可能是自己不小心改了其他地方,比如升级框架版本,导致功能A在这个版本下失效,而自己无法找出这个问题。有时可能因为有其他开发者加入(其他开发者在此期间在该分支有提交记录),导致排错工作变得更加复杂。
这时可以回滚到第2次提交的版本,从第2次开始逐步“回到”第10次的提交版本。依次测试功能A是在哪两个版本之间失效的,借此锁定是哪一次提交的内容有问题。之后再查看提交记录,即可迅速找到问题所在。
针对产品的重大更新,可以采用“灰度发布”的方式,先选定部分用户使用新版本,大部分用户仍使用旧版本。通过这部分用户的反馈,继续修复Bug。在确认新版本的产品基本稳定后,再全线发布,使所有用户的产品更新到新版本。