惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

大猫的无限游戏
大猫的无限游戏
Webroot Blog
Webroot Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
T
Threat Research - Cisco Blogs
V2EX - 技术
V2EX - 技术
L
LINUX DO - 热门话题
Google DeepMind News
Google DeepMind News
Recorded Future
Recorded Future
S
Schneier on Security
I
InfoQ
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
The GitHub Blog
The GitHub Blog
S
Security @ Cisco Blogs
O
OpenAI News
W
WeLiveSecurity
Vercel News
Vercel News
阮一峰的网络日志
阮一峰的网络日志
Simon Willison's Weblog
Simon Willison's Weblog
人人都是产品经理
人人都是产品经理
Cloudbric
Cloudbric
The Last Watchdog
The Last Watchdog
The Hacker News
The Hacker News
Google Online Security Blog
Google Online Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
GbyAI
GbyAI
NISL@THU
NISL@THU
T
Tailwind CSS Blog
V
Visual Studio Blog
PCI Perspectives
PCI Perspectives
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Jina AI
Jina AI
D
DataBreaches.Net
B
Blog RSS Feed
N
News and Events Feed by Topic
N
News and Events Feed by Topic
H
Heimdal Security Blog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
腾讯CDC
Latest news
Latest news
V
Vulnerabilities – Threatpost
Hacker News: Ask HN
Hacker News: Ask HN
WordPress大学
WordPress大学
V
V2EX
aimingoo的专栏
aimingoo的专栏
博客园 - 司徒正美
Apple Machine Learning Research
Apple Machine Learning Research
D
Darknet – Hacking Tools, Hacker News & Cyber Security
The Register - Security
The Register - Security
Help Net Security
Help Net Security

BMPI

构建自己的信息简报 2.0 我的退休计划:把无期变成有期 从情绪化交易到系统化投资 策引2025 实盘大考:当算法跑赢人性 三十五 我的AI投资助手 我的投资之路 BMPI周记008:设止损而勇试错 BMPI周记007:经济下行 BMPI周记006:时光机 BMPI周记005:直播小完结 BMPI周记004:不要做空 BMPI周记003:十年前的今天 BMPI周记002:交易之难 BMPI周记001:开始直播 对交易的思考 AI驱动开发:从Prompt到Product(直播) 策引全球投资组合:A股1号 策引全球投资组合:A股2号 策引全球投资组合:A股3号 策引全球投资组合:A股全球 策引全球投资组合:加密币1号 策引全球投资组合:加密币2号 策引全球投资组合:美股1号 策引全球投资组合:美股2号 策引全球投资组合:美股3号 策引全球投资组合:美股4号 策引全球投资组合:美股全球 全球投资开户完全指南:A股与美股篇 我的2023 全球经济中的隐形巨人 海外银行证券开户薅羊毛小记 我与ChatGPT结对编程的体验 ChatGPT背后的语言模型简史 ChatGPT应用开发小记 我的AI阅读助手 SQLite的文艺复兴 2022亏了多少 # 组合季报(2022Q4) AI降临 构建自己的信息简报 善用GitHub Real-time Web应用开发新体验 我的巨亏经历 # 组合季报(2022Q3) 构建自己的杠杆 我的投资助手 Google软件工程之过程篇 Google软件工程之文化篇 交易之难 # 组合季报(2022Q2) 从技术难题中学习 编程语言是如何实现并发的之并发模型篇 疫情与战争 # 组合季报(2022Q1) 编程语言是如何实现并发的之操作系统篇 编程语言是如何实现泛型的 写在第二十五万字 走进 Web3 财富常识 复盘2021 # 组合季报(2021Q4) 分布式系统中的时间 分布式系统下的认证与授权 如何学习一门技术 K8S 云原生应用开发小记 使用 Beancount 管理家庭财务 三周年小记 # 组合月报(202109) 仓位管理是核心 # 组合月报(202108) 云端 IDE 护城河还在但城没了 # 组合月报(202107) 构建高质量的信息输入渠道 最牛指数 # 组合月报(202106) 国际化与本地化 慢慢变富 # 组合月报(202105) 失败驱动开发 加密币挖矿小记 家庭资产配置的阶段 # 组合月报(202104) OKR + GTD + Note => Logseq 我的个人项目技术栈 长期投资之难 # 组合月报(202103) 我的人生管理系统 当别人的股票基金上涨时 # 组合月报(202101) 基于Serverless实现静态博客访问统计功能 构建终身学习体系进行自我提升 我的家庭理财规划 投资的秘密 # 组合月报(202012) 零成本搭建现代博客之优化国内访问速度 我的绘图工具箱 关于银行分期贷款的坑 # 组合月报(202011) 投资交易的心理建设 # 组合月报(202010) 大跌时我们能做什么 # 组合月报(202009) 双均线交易策略 # 组合月报(202008) Serverless应用开发小记 投资理财书籍推荐 # 组合月报(202007) 200万小目标 # 组合月报(202006) 零成本搭建现代博客之加载速度优化篇 零成本搭建现代博客之订阅推送篇 我常用的浏览器插件 零成本搭建现代博客之SEO优化篇 什么是SEO 零成本搭建现代博客之搭建篇 用Markdown可以做什么 什么是互联网广告 使用AWS Lambda提高网站图片加载速度1X倍
Google软件工程之工具篇
马大伟 · 2022-09-16 · via BMPI
  • 软件工程还是软件设计
  • Google软件工程中的工具
    • 版本控制(Version control)
      • CVCS vs DVCS
        • 单一代码仓(Monorepo)
      • 分支管理(Branch management)
    • 代码搜索、构建与静态分析(Code search && Build system && Static analysis)
    • 依赖管理(Dependency management)
    • 持续集成与持续交付(Continuous Integration && Continuous Delivery)
  • 写在最后

这是Google软件工程系列的最后一篇,这篇主要是分享软件工程中常用的工具,这些工具支撑了软件工程中的流程。但在开始之前,我们先思考一个问题:软件的研发到底是工程还是设计?

软件工程还是软件设计

传统工程的流程比如土木工程是设计师先设计好图纸,然后工程队按照设计图纸去施工建造,所以这里的工程既包含设计又包含建造,但负责设计的人员明显与建造的人员不是同一类人,甚至有着非常大的差异。

那软件的生产流程是什么呢?以敏捷开发流程为例,组建一个软件开发队伍,先进行Inception确定好开发的需求及范围,之后根据需求拆分故事卡,开发人员根据故事卡实现产品需求。在实现故事卡的过程中,开发人员每天会写一部分代码并在本地做自测,之后会对代码做Code Diff,在这个过程中又可能重新修改设计与实现。不断重复这个过程,直到最终这部分代码进入集成环境被测试人员验收,最终会上线到生产环境。那么这个过程中既包含了设计又包含了实现(或者说建造),或者说这实际上是个不断设计的过程。

以下两篇文章推荐阅读,可能会让你对这个问题有更好的理解:

Google软件工程中的工具

以下是《Software Engineering at Google》一书第四部分工具篇的思维导图,由于此部分占全书近40%,所以本文不会详细地介绍其中的概念,想详细了解的读者建议阅读原书。本文会结合此书这部分内容分享作者的个人理解及相关经验。

# Google软件工程
## 文化(Culture)
## 过程(Process)
## 工具(Tool)
- 版本控制
  - 分类
    - 集中式(Centralized VCS)
    - 分布式(Distributed VCS)
  - 存储类型
    - Monorepo
    - Polyrepo
  - 分支管理
    - Git Flow
    - GitHub Flow
    - Trunk-Based Development
    - Scaled Trunk-Based Development
- 代码搜索
- 构建系统
  - 基于任务的构建系统
    - Ant
    - Make
  - 基于制品的构建系统
    - Bazel
    - Gradle
    - Nx
- 静态分析
- 依赖管理
  - 问题
    - 钻石依赖
    - 兼容性
    - 安全性
    - 许可证
    - 运营
  - 管理方式
    - 静态依赖模型
    - 语义化版本
    - 绑定分发模式
    - Live at Head
- 持续集成
- 持续交付

版本控制(Version control)

在众多软件工程所用的工具中,最重要的我觉得就是版本控制系统了(Version Control System)。版本控制系统从字面意思就可以看出来是控制源代码的版本的,VCS就像时间宝石一样让开发人员在源代码历史中穿梭,为什么这种能力很重要?

其实这和本文开头那个问题相关,如果说软件开发是一个设计的过程,那这个设计可能需要不断修改,能最低成本地在不同版本间切换非常重要,更重要的是这种能力可以让多人协作完成软件的设计与开发。

Development is inherently a branch-and-merge process, both when coordinating between multiple developers or a single developer at different points in time. (Software Engineering at Google)

版本控制也让软件开发过程中产生了Code Diff或Code Review的过程进而促进团队知识共享,而这又是软件工程中文化的一部分。版本控制也影响了软件的部署过程,比如结合Pipeline与Artifact Repository,可以构建出不同环境不同版本的软件制品。

CVCS vs DVCS

早期的版本控制系统是集中式(CVCS)的,比如Subversion,现在更流行的是分布式的(DVCS),比如Git。这两者的区别可以看这篇文章:

CVCS与DVCS仅仅是适用的场景不同,并不意味着后者是前者更好的替代。比如很多大的公司或组织,如Google、Microsoft与FreeBSD都在用CVCS。一般来说大的公司更偏向于用CVCS,与CVCS密切相关的就是单一代码仓(Monorepo)了。

分布式版本控制系统如Git,其实是没有中央存储库的。我们在GitHub克隆某个仓库到本地,其中的origin其实是刻意约定设置成中央仓库的,但我们可以在本地仓库中添加多个远端中央仓库,也可以rebase多个远端仓库的代码到本地仓库。

单一代码仓(Monorepo)

Monorepo简单理解就是把整个组织的所有项目的代码都放入一个仓库中。初看不可思议,但Monorepo并不仅仅是把代码放一块就行了,它需要一整套的流程与工具链支撑,比如不同团队协作模式、代码库之间的依赖管理、目录的权限配置、构建与发布等。

与以Git为主的Polyrepo(一个项目一个代码存储库)存储库模型相比,Monorepo有如下的好处:

  • 代码共享:所有人都可以看到其他人的代码,能降低重复代码;
  • 统一依赖:不会出现多个项目依赖相同三方包的不同版本导致的冲突问题;
  • 跨项目修改简单:大规模跨项目的重构更简单了,能一次修改多个项目的代码;
  • 共享构建发布流程:能共享同一套构建发布流程,简化基础设施的复杂性;

Developers within an organization must not have a choice where to commit, or which version of an existing component to depend upon. (Software Engineering at Google)

进一步了解,强烈推荐阅读这篇文档:

分支管理(Branch management)

版本控制系统不仅可以让开发人员具备时间穿梭的能力,还具备开辟多重宇宙的能力,这就是分支(Branch)的功能。分支不仅仅是代码的不同版本,它还深刻的影响了开发部署的流程。

早期流行复杂的Git Flow分支模型,但这种模型带来了很复杂的维护成本,包括分支的管理、冲突的解决等问题。最终逐渐演变出更简单的主干分支开发(Truck Based Development)模型。

主干开发分支在实践中可能存在的问题是,主干分支与流水线(Pipeline)的集成,一般会有不同环境,如CI、INT、UAT、PROD等。当开发人员要在集成环境测试时,如果有紧急的Hotfix代码要推送到生产环境,这时候主干分支中还包含着集成环境的开发代码,就算有特性开关(feature toggle)的支持,也不敢直接把这些代码推入到生产环境。此时能做到就是回滚(git revert)这部分代码回去。这个问题本质还是因为测试环境有限,无法做到一个代码变更部署到一个临时创建的测试环境中,这时候主干开发分支可能需要做一定的调整,比如用Release分支来发布,主干分支做开发代码的Single Source of Truth。

不同分支模型的介绍,推荐这篇文章:

代码搜索、构建与静态分析(Code search && Build system && Static analysis)

代码搜索可以用最简单的grep -r命令或者IDE的搜索功能来实现,但要在多个代码仓库间高效地对某些代码进行跨仓库搜索,那这些工具可能很难满足需求。

Google自研了一套代码搜索的工具,这个代码搜索工具甚至可以和其他系统如日志查看系统集成。

Sourcegraph是一个开源免费的代码搜索云服务,可以与GitHub集成,提供良好的代码阅读体验。

Google同样实现了自己的基于制品的构建工具Bazel,Bazel也是支持Monorepo很好的构建工具,同样的还有NxGradle

代码静态分析就像自动化的Code Review一样,能帮助发现代码中的质量与安全问题,减少不必要的Review时间,提升代码质量。流行的代码静态分析工具中,SonarQube是推荐的。

依赖管理(Dependency management)

依赖管理可能是软件工程中最复杂的问题之一(短期编程代码无需考虑此问题)。现代软件是建立在大量的依赖库或框架之上的,这些外部代码很多并不受开发人员的控制,当软件变得越来越庞大时,大量的依赖可能会形成复杂的依赖树(如在Gradle项目中,gradle dependencies命令可以打印出应用的依赖树)。

依赖问题最多的可能是钻石依赖问题,简单说就是同一个包的不同版本共存的问题,这在某些编程语言如Java中影响并不大,因为多个版本可以共存,除非在某些特殊的场景下,不同的包可能会造成一些很诡异的Bug。

Black Duck中又把依赖的问题分为三大类:

  • 许可证(License Risk):商业应用对依赖包的License有限制,比如无法使用GPL类的License。
  • 安全(Security Risk):依赖包经常会被爆出重大的安全CVE问题,有时候因兼容性的问题很难去通过版本升级来修复。
  • 运营(Operational Risk):一些小众的编程语言如Clojure的很多包,经常无人维护或者缺乏更新,导致存在潜在的运营风险。

另外一个主要的问题就是兼容性的问题,比如API出现破坏性的更新,或者ABI无法兼容。

编程语言ABI(Application binary interface)兼容性:与API(Application programming interface)类似,是描述二进制文件的兼容性。比如Java有ABI兼容性的保证,意味着基于新版本JDK的代码可以安全地调用老版本JDK的Jar包。

在解决API变化导致的依赖问题上,业界一个流行的方案是语义化版本:SemVer。通过将版本拆分为三部分,如x.y.z,x是破坏性更新版本号,y是特性版本号,z是Bug修复的版本号。我们可以在依赖配置文件如package.json中通过^~符号来指定依赖的最大版本号范围。

依赖管理的问题也可能和代码设计有关。比如应用对某个外部服务有依赖,如何降低外部API变化对应用代码的影响?这个问题可以从设计模式的角度去解决,比如创建一个适配层(如Gateway模式),通过定义一个抽象的接口层去实现,而非依赖具体的外部API去实现。

持续集成与持续交付(Continuous Integration && Continuous Delivery)

CI是一种团队开发软件的实践,在代码变更集成到主代码分支前尽早的捕捉变更带来的问题,流程主要有自动化的测试与构建,CI工具可以帮助开发人员快速获得代码变更是否正确的反馈。

常用的CI工具有:GitHub ActionsGoCDJekins。这些工具也称为流水线(Pipeline),不仅支持UI的操作,还支持Pipeline as Code

实际的CI工具一般受制于服务器资源的限制,很难做到一个代码变更(Code Commit)自动部署一个测试验证环境(这也被称为无限环境CI)。目前只有少数的云服务可以支持前端项目的无限环境CI,比如Cloudflare PagesVercelNetlify等。

CD发生在代码集成后,包括从代码集成后到发布变更的软件给用户的过程,良好的CD实践既可以快速进行价值交付,又可以快速获得用户反馈。持续交付的原则和敏捷的方法论有一些重合的部分:

  • 敏捷:小而频繁地发布过程,快速获取反馈。
  • 自动化:通过自动化的手段降低发布的时间成本。
  • 隔离:采用模块化的架构设计使需求变更和故障排除更简单。
  • 可靠:通过技术监控提高系统的可靠性。
  • 数据驱动:使用埋点或A/B测试获取用户反馈的数据,通过数据做决策。
  • 分步发布:产品特性先灰度发布,确保无误后再全量推送给用户。

写在最后

软件工程或者说软件设计是个复杂的活动,其中既涉及文化相关的东西,又有很复杂的流程及一系列的工具集。如果把这些过程与相关工具结合到一起看,就会出现这么一幅复杂的全景图:

从这个角度看,《Google软件工程》这本书只是从大纲的角度去介绍这些知识,真正的问题还需要我们在研发软件的实践中去体会。这系列文章也只是结合作者自己的知识经验去理解这本书,如有错误,欢迎指正。