Home
>
单片机源代码
>
源代码管理
源代码管理

time:2020-07-30 10:07:07

author:重庆佰鼎科技有限公司

【Font size: big medium smail

本文由重庆佰鼎科技有限公司提供,重点介绍了源代码管理相关内容。重庆佰鼎科技有限公司专业提供单片机源代码,考试系统源代码,海王源代码等多项产品服务。公司产品服务因高质量,低价格等优势,获得行业内一致认可,获得了客户的一致好评。

源代码管理简介源代码根据不同使用者的需求有着不同的指标要求, 针对这些要求和工程成本之间的权衡, 可以使用分支和TAG策略, 并结合DevOps等思想, 对代码进行工程化的管理, 以实现高效的开发, 测试, 部署. 基本思想来自A successful Git branching model, 主要的分支除master外应包括:

Feature: 单一功能的开发, 测试分支, 对代码等级要求较低.

Dev: 开发团队用以合并功能的分支, 对代码的最低要求为可开发, 可复现, 可检验.

Release: 用以交付的分支, 对代码的最低要求为可开发, 可复现, 可检验, 可回滚.

Hotfix: Release的一种特殊类型, 常见于对旧系统的临时性修复, 但是其代码需要向dev和master进行合并.

使用以上分支模式, 并对其的检出, 提交, 合并加以权限控制, 使用自动化工具进行严格的流程安排, 来满足不同使用者的要求.

前言对于软件开发团队来说, 源代码是其最重要的产出和资产, 但是对源代码的管理远远没有一套标准的流程. 当然大部分公司都会有自己的一套管理规范, 但实际上往往流于形式, 真正跑起来那真是天马行空, 潇洒无比.

当然, 各家SCM系统已经运行了很多年, 无论CVS, SVN, Perforce还是GIT都有一些最佳实践. 网上有很多适用于不同场景的模式可供参考, 大家可以根据自己的需要和团队的情况进行选择. 本文真是根据个人的经验对于源代码的管理提出一些看法.

目标源代码即是团队的产出, 也是团队的用来进行生产的工具和资本. 我很喜欢的一个关于软件开发的类比就是电影的生产, 源代码即是原始胶片, 程序员则按照剧本和自己的了解在代码中记录将用户的需求进行演绎. 而在最终剪辑(编译, 链接)之前, 谁也不知道结果会是怎样.

很难想象一家电影厂的原始胶片就那么随随便便的堆在屋子里, 谁想扔点什么进去就扔进去, 你是导演你不砍人? 但是尽管源代码如此的重要, 很多公司对代码的管理是非常粗陋的, 仅仅停留在文件共享这个层面上. 就算有各种管理章程, 很多管理的主要目标也是保证代码的安全, 毫无疑问这也是由于长期以来对代码本身的漠视所导致的.

实际情况中, 大部分团队认为代码编译出的软件才是产出. 因此最起码的会有各种tag或者版本的管理. 这很正确, 但是远远不够, 代码更为重要的作用是团队用来生产的原材料: 新的功能, 更高效的算法, 都是基于现有的代码而进行开发. 因此, 对此种数字资产的管理, 也需要有着严格的使用上的界定和相应的措施.

我认为代码的使用人员可以分为四类, 即:

开发人员: 负责添加/修改/删除代码, 对代码的直接操作最多.测试人员: 负责保证代码可以实现预期的功能, 以及不会出现预期以外的行为.产品人员: 是用户需求在团队内的代理, 他们往往从最终用户的角度对代码提出修改意见.DevOps: 对上述三类角色进行居中调度, 沟通, 帮助, 贯彻技术栈. 也要负责开发团队和运维团队的对接工作而代码管理的目标, 即是最大限度的对以上四类人员提供便利, 提高效率. 或者按照人月神话中的说法, 降低团队的沟通成本, 是团队可以通过增加人力来提高生产力. 那么这四类人在代码上的主要痛点都有哪些呢? 以下是我在这么多年的工作中碰到的一些问题:源代码管理

开发: 尽量避免代码的merge, 或者说开发人员彼此之间的并发干扰带来了很大成本: 辛辛苦苦写好的代码, 被别人冲掉了都不算严重的问题, 好用的工具和流程可以很大程度避免本地开发的一些合并损失. 真正可怕的是安安静静引入bug, 往往要到最后的集成测试阶段才会发现.测试: 回归测试问题, 今天开发Fix掉的bug, 后天会不会被其他人再次引入; 当然实际上测试环境不稳定, 以及如何reproduce一个bug也是非常头痛的事情.产品: 这帮程序员到底搞出了什么东西? 如何确认当前的进度? 如果定期向用户演示以保证用户的参与感和认同度?DevOps: 跟所有人对接的痛苦就在于信息太多, 难以提炼重点. 你们到底想要什么? 可不可以用相同的语言, 文件格式来阐述各自的需求? 开发环境运行好好的代码, 生产环境怎么就出错了呢? 代码改怎么回滚, 依赖项都在哪儿? 是快照还是发布版本? 我怎么和运维人员进行对接?所以对于代码的管理就应该从以上的问题出发, 尽力加以解决.

指标:既然确定了目标, 那么我们就可以以此来对代码进行管理. 而任何的管理都可以看成对KPI的监控和干预, 对应代码管理, 我认为有如下方面的指标可供参考:

对于开发, 代码的拥有者, 主要的问题就在于如何与他人合作修改, 解决开发工作上的并发冲突, 此时对代码的指标要求是:

可开发性:

代码是可编译的/没有语法错误的: 各种IDE或者Debug工具可以编译或运行此份代码. 代码是可检出的:从任意版本可以被检出或rebase为新分支, 不会出现某个提交在逻辑上是不完整的..代码是可信赖的: 被检出的代码是不存在Bug的, 任何错误都只能由新添加的代码所引入.这三个指标可以认为是可开发性的三个级别. "可编译"是其中非常基本的需求, 一旦低于这个标准, 基本就是全组开骂的场面. 但"可检出的"可能大家没有注意过, 但实际上我们经常遇到. 举个例子, 我和大牛在开发一个功能, 我手贱了, 先提交一个不完整的接口声明, 心里想着明天上班了在改成正式的. 可大牛晚上2点加班, 以为这是终版, 检出到dev分支, 写了一个晚上的代码然后又被全组检出到各自的feature. 你说第二天我把接口改回来会不会被追杀整条街?

对于如何满足代码可检出到可信赖, 那就是引入各种代码审核工具和相应的分支策略. 尽可能的为每个人开辟独立的开发空间, 同时使用自动化的审核和合并工具来规范各独立Repo的同步. 当UT的覆盖率达到相应的水平, 基本上可以保证代码是可检出的.

而可开发的最高级别, 完全可信赖的代码则是一个非常理想化的情况, 这要求程序员任何一次提交都应当是完美的, 不含逻辑错误的原子提交. 事实上我认为光凭开发人员是无法高效满足这一要求的, 强行要求的话会造成开发成本上升到不可接受的底部. 我们只能再团队情况, 时间约束的前提下尽可能逼近这个标准. 具体细节的话我得开个单章来讲, 叫做<代码质量及其他>, 这个话题下各种工具, 各种讨论, 各种撕逼, 一言难尽~~~

对于测试, 怎么解决环境问题和回归问题? 此时其对代码的要求是:

可测试性:

代码是可以运行的: 无论在正常的运行环境, 测试环境还是桩环境下, 代码需要可以被运行. 代码是可以被追溯的: 任何一次代码的运行, 都可以定位到相应的代码版本.运行可以被复现的: 无论任何时刻, 任何相应代码版本都可以定位其彼时的依赖, 数据, 做到对测试环境的精确复现.代码是被保护的: 一旦测试完成, 已经被修复的bug将不会再次出现.同样, 这四个级别是依次递增的. 可运行是对测试来说的基本要求, 可追溯是报告bug的必要条件, 可复现是修复bug的先决条件, 而第四个级别的代码则是可以将回归测试的成本降到最低. 相应的其成本也会不断攀升.

对于代码的可运行, 一般的做法是建立Acceptance Testing或者叫做Smoking Test, 建立一套基本的测试逻辑, 自动化的对代码进行检测, 不满足最低要求(编译无错误, 可部署, 可运行, 通过最基础的运行要求, 如界面被渲染, 基本功能通过, 等等)则不对测试环境进行更新, 甚至阻止代码的合并.

而追溯和复现一般的情况是什么呢? 其实也很简单, 团队只维护一个分支和一个环境; 只有当代码可以运行时才开始进行测试, 不可以就歇着; 也不存在追溯, 因为任何时刻都是HEAD; 也不存在复现, 因为只有一个环境. 至于第4个级别就只能看程序员的自律.

但实际上大型项目的追溯和复现特别复杂, 一个分支同时进行开发和测试是非常低效的, 开发的提交要远远超过测试部署的速度, 使得报Bug时候的代码和程序员看到Bug时候的代码往往是不一致的. 而不断变化的测试环境也给复现带来极大的困扰, 因为最可怕的bug就是那种无法复现的, 最讨厌的情况是环境每次都崩溃的. 解决的办法往往是引入复杂的版本控制机制和相应的日志机制, 与之相对应的就是Bug描述的高度格式化文本(环境, 代码, 数据的相应版本). 而我之前曾经通过Feature Branch模式, 部署Docker化来解决这个问题, 事实上效果不错, 每个功能和迭代都有其相应的部署和测试环境, 将干扰降到最低.

事实上对于第四个级别, 也就是如何尽可能降低回归测试的成本, 哪怕完全引入自动化测试也无法彻底解决. 我们只能尽可能避免此种问题, 比如各种冻结策略(需求冻结, 功能冻结, 开发冻结, 代码冻结), 比如各种冲突监测(代码依赖静态分析等等).

相对于这种保守的策略, 也有一些较为激进的尝试, 比如任何新引入的代码都要受到多重的检查以及相应的回归测试, 结合全面的自动化测试手段, 尽可能的保护代码收到后续代码的污染. 效果还可以, 但是对测试人员和产品人员提出了相当高的要求. 并不是可以轻易适用于所有的团队. 强行做到也能: 要求代码全分支覆盖测试, 全逻辑覆盖测试, 嗯, 每天能写5行不?

这个其实看行业, 我觉得NASA的代码达到第四个级别应该是可以的, 也是必须的.

对于产品人员, 传统上他们是不关心代码的, 只需要维护相应的产品需求文档和开发合同即可. 但是随着敏捷, 全栈团队等开发概念的深入, 产品人员越来越直接的介入开发工作. 此时, 对于他们来说, 如何"检验"代码是最重要的, 即:

可检验性

可上线: 每个被测试通过的功能都应该可以上线.可实时上线: 每个被测试通过的功能都应该立刻上线.可回滚: 任意一个功能可以被下线.换句话说, 任何一个被QA确认的功能都应当可以被从用户的角度进行检验. 功能A做好了, 但是代码里面还有其他的半成品代码, 这是最好了还是没做好? 不能上线的功能就是未完成的功能.

而且不仅应该可以部署, 新完成的功能也必须立刻上线, 随时等待用户(产品和甲方)的审核, 此时可以尽快的了解实现和用户需求之间的偏差, 第一时间发现沟通上的问题. 不要让后续的开发基于错误的假设而进行.

但是检验的结果往往并不是总是积极的, 一个功能被确认无效甚至错误, 需要从代码中删除, 此时需要良好的代码管理机制来实现代码的回滚甚至是修改不会引入其他的错误. 虽然在传统软件的瀑布流程中并不常见, 但是在现在这个互联网时代, 无论对于A/B测试还是灰度发布都是必要的操作. 这个问题也并不是一个单纯的代码问题, 往往涉及到整个系统的架构和模块间的调用模式.

最后, 让我们需要来解决DevOps的问题, 作为团队的协调者, 贯穿产品, 开发, 测试到部署及运维的人员, 他需要什么呢?

可维护性:

代码是自治的: 所有编译, 运行, 部署的信息都可以从代码直接或间接得到.代码是自我验证的: 测试用例应当是代码的一部分, 结合CI流程以自动化的方式来对代码进行自我约束. 和前者不一样, 这两者之间不是递增的关系, 而是并列的. 能做到的越多, 越好, 对DevOps来说可以施展的余地就越大.

换句话说, 代码需要可以被准确的理解和被操作. 以下情况代码就是不可被维护的:

代码没有第三方依赖关系: 只仍源代码, 以来的jar包是什么, 版本是什么?代码里面有冗余: 源代码Repo有两个工程, 一个叫package1, 另外一个叫package2, 99%代码都一样, 毫无疑问是懒的改了直接copy了一份重来. 但是你得告诉我输出用哪个啊.以上缺失的信息都有, 但是在另外一个word文档上或者哪位的脑子里.完全没有部署或发布的信息.以上这些情况DevOps都会比较头大, 因为这些情况会导致一个很严重的问题, 无法自动化或者建立自动化流程需要进行复杂调整. 难道每次打包都要问一遍责任人打哪个吗?

而且在各种自动化手段非常成熟的今天, 无论采用Ivy, Maven, NPM还是其他的依赖工具和结构工具, 主流的IDE和CI工具都可以进行自动的识别和管理. 可维护的代码将极大的提升自动化的效果.

所以针对这些问题, 我们就要引入各种工具. 对于依赖, 推荐Maven, 毕竟这个用户最多, 搭建私有Repo也异常便捷. 对于配置管理工具, Java世界自然是Gradle, JS世界就呵呵大家随意, 我是webpack+grunt. Restful服务的话, 请写好swagger. 其他服务请按照各自的标准布局来写, 这样可以降低各种Customize操作的成本, 也方便新成员的接手, 降低团队风险.

管理方案综上, 对一份源代码的评价的指标如下:

可开发性, 级别递增为: 可编译 < 可检出 < 可信赖;可测试性, 级别递增为: 可运行 < 可追溯 < 可复现 < 可保护可检验性, 级别递增为: 可上线 < 可实时上线 < 可安全回滚可维护, 按照实现与否: 自治, 自我验证.按照理想的模式, 当然希望所有方面都做到最好. 但很可能这会让生产效率低到无法忍受的局面: 每写一行代码开4天会, 写5页文档, 召集4轮专家评审, 完全没问题, 一定能做到完美的代码. 但我们是工程师, 需要在成本和收益之间取得平衡, 因此我们就需要容忍那些不完美, 于此同时在必要的地方做到完美.

因此, 我们将使用各种SCM来对代码进行管理, 并结合各种CI/CD流程及工具来尽可能的实现自动化, 降低人员成本和误差. 为了简单起见(其实并不简单...), 使用GIT作为例子来说明(CVS和SVN没有cherry-pick, 负分...).

其实上面那么多字描述的场景的就是两件事情: 不要一起改代码, 会冲突; 人是会犯错的, 任何时候留记号, 留退路. 那么对应到代码里面, 就是两个方案: 分支和TAG.源代码管理

为了解决开发性的问题, 可以使用复杂的开发分支模式, 我知道的有三种:

所有的开发在不同的release上进行, 设定不同的Milestone来设定分支所扮演的角色.所有的开发在feature-branch上进行, 使用rebase/merge策略组装dev分支, feature冻结后dev检出为release分支, 也就是题图的模式.所有开发在master上进行, 使用复杂的cherry-pick策略组装release分支. 听说是G家采用的模式. 看起来很粗暴, 但是据说有及其复杂的CI流程进行辅助, 大概就是自己造了套自动SCM的意思, NB.方案一, 实际上是使用Milestone来标记单分支的等级, 是需要额外的信息对代码进行管理, 实际上是通过团队合同(Team Agreement)和流程控制来管理, 这也是早年SCM的经典模式, 运行了几十年, 服务过无数的项目. 但此种模式对测试来说还是标准的单分支模式, 对DevOps来说就很更难办了, 此时代码的维护性几乎没有, 往往需要专职的Build团队来负责. 但是优势在于较为简单易懂, 开发/测试人员不需要过多的知识, 过程较为直观.

方案二则是目前的互联网的主流模式, 因为复杂的分支模型可以提供不同的代码等级:

release: 最高等级要求的分支, 任意版本或tag都应该满足: 最低为可检出, 尽可能的可信赖: 出现任何问题都可及时启动hotfix分支对其进行修复.代码必须处于被保护状态: 被至于release分支的代码, 未经严格的回归测试不得添加新的新的提交实时上线: 活跃的release分支必须与线上部署保持一致, 否则这个分支的存在就没有意义.master: 对DevOps的分支, 这个分支是所有分支的源头, DevOps将创建这个分支并将其初始化为可维护的状态, 满足自治, 自我验证的要求. 此外这个分支还将保存所有重要的TAG, 不同的TAG可以被检出为独立的release分支, 满足维护上各种回滚的要求. 也是latest部署所使用的的分支. 基本上禁止开发人员对master的提交, 只有DevOps和Bot程序可以向其提交或合并代码. 但是在项目启动阶段也只有在启动阶段, master可以处于较低的代码等级.dev: 对开发的分支, 也应当是可被产品人员检验的分支, 需要达到可检出, 可复现, 实时上线. 敏捷开发中可以将这个分支部署为review.feature: 几乎可以认为是开发和对应测试人员的私人分支, 基本上就是单分支模式的开发模型. 在满足可测试性的要求下几乎可以随意提交, 甚至在前期阶段都可以不满足可测试性的最低要求. 但是在合并回dev的时候, 必须将代码等级提升至对应标准.代码之间的合并需要使用CI工具来自动完成, 这样在每个合并环节就可以增加检测工具来保证代码的等级要求, 而这就要求代码必须是可维护的. 他们之间的合并方向如下:

master: 只能由release向其合并, 每个commit都应该由明确的TAG进行说明.dev: 只能由master检出, 只能由branch合并, 合并前需要进行代码等级检测.release: 只能由dev检出, 只能由dev合并, 合并前需要进行代码等级检测.feature: 只能由dev分支检出, 不得从其他分支合并, 而只能从upstream进行rebase. 合并前需要进行代码等级检测并满足fast-forward.为了加强理解, 再放一下题图:

在这种模式下, 一方面我们可以保证开发的高效性而允许低质量的代码进入系统以进行合作, 另外一方面也可以通过层层检查来保证代码的级别.

而方案3, 据说也是方案2的变种, 实际上master被当做dev来用, 使用各种自动化工具来维护一个虚拟的master和之上的版本. G家的方案实在不可以常理度之, 不过听说最近也在转GIT.

分支管理的工程实现方案二当然是比较美好的方案, 依托于GIT可以达到很好的效果. 但是缺点相对于方案一甚至单分支方案是什么呢?

在于增加了很多开发代码以外的成本.

理论上来讲这不算什么, 但是实际情况是大部分程序员知道怎么写代码, 甚至有很多知道怎么写出好代码; 但是对于如何对一个项目进行工程化的管理, 保持开发的持续性, 保证团队的稳定性是没有概念的. 因为做这些事情并不能直接让功能完成的更好, bug解决的更快.

而且事实上额外的工作也会极大影响程序员的开发效率, 比如额外的rebase代码如同例行会议一样, 是对思路和工作状态的极大干扰.

因此, 使用复杂的代码管理必须引入自动化工具, 无论是CI, CD还是自动化测试, 都需要以实时的方式对开发人员, 测试人员, 产品人员进行反馈. 这就需要大量的DevOps的介入, 也就是为什么要强调代码的可维护性. 总的来说, 就是基于现有的软件和功能, 对代码的提交进行不同级别的自动review和人工review. 细节可以请见我另外一篇短文, 理想化的DevOps流程. 其要点在于:

分支的合并权限禁止授予开发人员, 而是赋给Bot, 在review流程后进行自动合并.Review流程需要自动化, 具体Review的要求视团队实际情况而有所调整.Remote Repo的分支检出操作应由Bot完成, 以保证分支的代码水平.master, 各活跃release, dev, 以及处于测试状态的feature分支, 应该都有其对应的CD部署, 且所有的部署都应该与对应分支的head同步.而除了使用工具外, 也必须配合相应的管理工具和流程, 比如上述的检出或合并操作可以与相应的JIRA事务通过分支和提交注释来建立关联, 让开发团队中不同角色都可以从中受益, 而不是为了流程而流程. 毕竟产生可以正常工作的代码才是一切开发活动的最终目标.

以上.

季长冰.

2017. 12. 21. 凌晨

Reprint please indicate:http://www.cnsoftweb.com/ydm-3427.html