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

推荐订阅源

L
Lohrmann on Cybersecurity
I
Intezer
M
MIT News - Artificial intelligence
博客园 - 【当耐特】
Martin Fowler
Martin Fowler
G
GRAHAM CLULEY
Jina AI
Jina AI
Engineering at Meta
Engineering at Meta
AI
AI
SecWiki News
SecWiki News
C
Cybersecurity and Infrastructure Security Agency CISA
IT之家
IT之家
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
The Blog of Author Tim Ferriss
P
Proofpoint News Feed
S
Security Affairs
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Microsoft Azure Blog
Microsoft Azure Blog
P
Proofpoint News Feed
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Google DeepMind News
Google DeepMind News
H
Hacker News: Front Page
爱范儿
爱范儿
F
Fortinet All Blogs
大猫的无限游戏
大猫的无限游戏
V
V2EX
H
Help Net Security
The Hacker News
The Hacker News
G
Google Developers Blog
Latest news
Latest news
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
T
Tor Project blog
宝玉的分享
宝玉的分享
博客园 - 司徒正美
H
Heimdal Security Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
MyScale Blog
MyScale Blog
S
SegmentFault 最新的问题
C
Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
S
Security @ Cisco Blogs
B
Blog RSS Feed
B
Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Cyberwarzone
Cyberwarzone
L
LINUX DO - 最新话题
K
Kaspersky official blog
MongoDB | Blog
MongoDB | Blog

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