扫码关注公众号:芋道源码

发送: 百事可乐
获取永久解锁本站全部文章的链接

《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 https://zhuanlan.zhihu.com/p/35303567 「李少侠」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

它作为一个开源项目,也吸引了无数第三方开发者和终端用户,成为顶尖开源项目之一。它在功能上做到了够用,体验上做到了好用,更在拥有海量插件的情况下做到了简洁流畅,实属难能可贵。

我是 VS Code 用户,同时也为它开发插件,插件市场里的众多 Java 插件基本都是我们团队的作品,所以我在日常工作中观察到不少 VS Code 在工程方面的亮点,下面就来逐一探讨。

简洁而聚焦的产品定位,贯穿始终

你知道 VS Code 的开发团队人数只有二十出头吗?

难以相信吧,大家都觉得 VS Code 无所不能,如此强大的工具那么几个人怎么做得出来。实际上功能丰富是个美好的错觉,因为大部分针对特定编程语言和技术的功能都是第三方插件提供的,VS Code 的核心始终非常精简,这很考验产品团队的拿捏能力:做多了,臃肿,人手也不够;做少了,太弱,没人用。

他们团队选择了专注于核心功能的开发,为用户提供简洁流畅的体验,并将该思路贯穿在产品开发的每个环节。在我看来,这就是第一个亮点。

第一个亮点同时也是一个难点,因为“简洁”说到底是产品的“形态”,更关键的其实是前置问题——产品的定位,它到底解决什么问题。该问题如果从用户的角度来看,可以转换为以下两点:

1.我们为什么需要一个新的工具?

2.它到底是代码编辑器(Editor)还是集成开发环境(IDE)?

让我们来看看项目负责人Erich Gamma 的说法:

(视频截图 - Erich 阐述了VS Code的定位:编辑器+代码理解+调试)

这张截图它阐述了 VS Code 的定位:编辑器+代码理解+调试。这是一个非常节制而平衡的选择,专注于开发者“最常用”的功能,同时在产品的形式上力求简洁高效。从结果来看,这个定位是相当成功的。

在这个定位的指导下,这二十多位工程师搞出了 VS Code。相对较小的功能集,使得开发者们能在代码质量上精益求精,最终用户们也得到了一个性能优异的工具,这是 VS Code 从一众编辑器中脱颖而出的重要原因。

正因为产品定位以及团队职责上的高度节制,团队成员才能把时间花在这类问题上,写出经得起考验的代码。

与此同时,较小的团队也使得团队成员做到了行为层面的整齐划一,这点在社区互动上体现得尤为明显,大家可以去 GitHub 上看他们的 Issues,超出产品定位范畴的请求和反馈基本都被婉拒或者转交到第三方插件项目,可以说是很专注了。

看到这里,似乎一切都好,但问题来了,码农千千万,你用 Node 我用 Go,你搞前端我弄后台,VS Code 如何满这些五花八门的需求呢?机智的你已经抢答了——海量插件。那么接下来我们来深究一下 VS Code 是如何经营一个庞大的插件生态的。

进程隔离的插件模型

通过插件来扩展功能的做法已经是司空见惯了,但如何保证插件和原生功能一样优秀呢?历史告诉我们:不能保证:)。

大家可以参考 Eclipse,插件模型可以说是做得非常彻底了,功能层面也是无所不能,但存在几个烦人的问题:不稳定、难用、慢,所以不少用户转投 IntelliJ 的怀抱。可谓成也插件,败也插件。

问题的本质在于信息不对称,它导致不同团队写出来的代码,无论是思路还是质量,都不一致。最终,用户得到了一个又乱又卡的产品。所以要让插件在稳定性、速度和体验的层面都做到和原生功能统一,只能是一个美好的愿望。

来看看其他 IDE 是怎么做的,Visual Studio 自己搞定所有功能,并且做到优秀,让别人无事可做,这也成就了其“宇宙第一IDE”的美名;IntelliJ 与之相仿,开箱即用,插件可有可无。这么看起来,自己搞定所有的事情是个好办法,但大家是否知道,Visual Studio 背后有上千人的工程团队,显然,这不是 VS Code 这二十几号人能搞定的。他们选择了让大家来做插件,那怎么解决 Eclipse 所遇到的问题呢?

这里分享一个小知识——Eclipse 核心部分的开发者就是早期的 VS Code 团队。嗯,所以他们没有两次踏入同一条河流。与 Eclipse 不同, VS Code 选择了把插件关进盒子里。

这样做首先解决的问题就是稳定性,这个问题对于 VS Code 来说尤为重要。都知道 VS Code 基于 Electron,实质上是个 Node.js 环境,单线程,任何代码崩了都是灾难性后果。所以 VS Code 干脆不信任任何人,把插件们放到单独的进程里,任你折腾,主程序妥妥的。

插件与主进程隔离

VS Code 团队的这一决策不是没有原因的,正如前面提到的,团队里很多人其实是 Eclipse 的旧部,自然对 Eclipse 的插件模型有深入的思考。Eclipse 的设计目标之一就是把组件化推向极致,所以很多核心功能都是用插件的形式来实现的。

遗憾的是,Eclipse 的插件运行在主进程中,任何插件性能不佳或者不稳定,都直接影响到 Eclipse,最终结果是大家抱怨 Eclipse 臃肿、慢、不稳定。VS Code 基于进程做到了物理级别的隔离,成功解决了该问题。实际上进程级别的隔离也带出了另一个话题,那就是界面与业务逻辑的隔离。

UI 渲染与业务逻辑隔离,一致的用户体验

“不稳定”之后的问题是“难用”,具体来说就是混乱的界面和流程,究其原因就是插件之间的界面语言的“不一致”,它导致学习曲线异常陡峭,并且在面临问题时没有统一的解决路径。VS Code 的做法是根本不给插件们“发明”新界面的机会。

如上图,插件们被关在 Extension Host 进程里,而 UI 则在主进程里,所以插件们天然没法直接在用户界面上做手脚。

VS Code 统管所有用户交互入口,制定交互的标准,所有用户的操作被转化为各种请求发送给插件,插件能做的就是响应这些请求,专注于业务逻辑。但从始至终,插件都不能“决定”或者“影响”界面元素如何被渲染(颜色、字体等,一概不行),至于弹对话框什么的,就更是天方夜谭了。

VS Code 对于用户界面的把控可以说是谨慎到变态,做过插件的人都懂的,感兴趣的同学可以去深挖一下 TreeView 的历史,会有更直观的体会。乍一看,第三方开发者被卡得死死的,这样不是限制了大家的创造力吗?

我想说这个做法跟这个团队的背景密切相关,换一拨人很有可能会失败。他们之所以能成功,是因为该团队在开发工具领域深耕多年,他们把经验转换为观点,最终落实到了 VS Code 的界面元素以及交互语言上,从结果来看,广受欢迎。

界面和业务逻辑的彻底隔离,使得所有插件有了一致的行为,用户就得到了整齐划一的体验。不仅如此,这种接口和行为层面的一致性,最终转化成了另一个“伟大”的功能——Remote Development,我们稍后讨论。接下来我们要聊的是 VS Code 另一个创举——Language Server Protocol。

LSP——基于文本的协议

前文提到了 VS Code 定位中的两个特色:代码理解和调试,绝大部分都由第三方插件来实现,中间的桥梁就是两大协议——Language Server Protocol(LSP)和 Debug Adapter Protocol(DAP)。两者从设计的角度来看高度相似,我们着重看一下最火的 LSP。

首先,为什么需要 LSP?

全栈开发早已成为这个时代的主流,软件从业者们也越来越不被某个特定的语言或者技术所局限,这也对我们手里的金刚钻提出了新的挑战。

举个栗子,我用 TypeScript 和 Node.js 做前端,同时用 Java 写后台,偶尔也用 Python 做一些数据分析,那么我很有可能需要若干工具的组合,这样做的问题就在于需要在工具间频繁切换,无论从系统资源消耗和用户体验的角度来看,都是低效的。

那么有没有一种工具能在同一个工作区里把三个语言都搞定呢?没错,就是 VS Code——支持多语言的开发环境,而多语言支持的基础就是Language Server Protocol(LSP)。

该协议在短短几年内取得了空前的成功,到目前为止,已经有来自微软等大厂以及社区的一百个实现,基本覆盖了所有主流编程语言。同时,它也被其他开发工具所采纳,比如 Atom、Vim、Sublime、Emacs、Visual Studio 和 Eclipse,从另一个角度证明了它的优秀。

更难能可贵的是,该协议还做到了轻量和快速,可以说是 VS Code 的杀手级特性了,同时也是微软最重要的 IP 之一。。。哇塞,又强大又轻巧,怎么看都是个骗局啊,那我们就来看看它到底怎么做到的。

先划重点:1、节制的设计 2、合理的抽象 2、周全的细节。

先来说说设计(Design),大而全是很常见的问题。如果让我来设计这么一个用来支持所有编程语言的东西,第一反应很可能是搞个涵盖所有语言特性的超集。

微软就有过这样的尝试,比如 Roslyn——一个语言中立的编译器,C# 和 VB.NET 的编译器都是基于它做的。大家都知道 C# 在语言特性层面是非常丰富的,Roslyn 能撑起 C# 足以说明它的强大。

那么问题来了,为啥它没有在社区得到广泛应用呢?我想根本原因是“强大”所带来的副作用:复杂、主观(Opinionated)。光是语法树就已经很复杂了,其他各种特性以及他们之间的关系更是让人望而却步,这样一个庞然大物,普通开发者是不会轻易去碰的。

相较之下,LSP 显然把小巧作为设计目标之一,它选择做最小子集,贯彻了团队一贯节制的作风。它关心的是用户在编辑代码时最经常处理的物理实体(比如文件、目录)和状态(光标位置)。它根本没有试图去理解语言的特性,编译也不是它所关心的问题,所以自然不会涉及语法树一类的复杂概念。

它也不是一步到位的,而是随着 VS Code 功能的迭代而逐步发展的。所以它自诞生至今依然保持着小巧的身材,易懂,实现门槛也很低,迅速在社区得到了广泛的支持,各种语言的 Language Server(LS)遍地开花。

小归小,功能可不能少,所以抽象就非常关键了。LSP 最重要的概念是动作和位置,LSP 的大部分请求都是在表达”在指定位置执行规定动作“。

举个栗子,用户把鼠标悬停在某个类名上方,查看相关的定义和文档。这时 VS Code 会发送一个’textDocument/hover’请求给 LS,这个请求里最关键的信息就是当前的文档和光标的位置。

LS 收到请求之后,经过一系列内部计算(识别出光标位置所对应的符号,并找出相关文档),找出相关的信息,然后发回给 VS Code 显示给用户看。这样一来一回的交互,在 LSP 里被抽象成请求(Request)和回复(Response),LSP 同时也规定了它们的规格(Schema)。

在开发者看来,概念非常少,交互形式也很简单,实现起来非常轻松。

看到这里,大家应该对 LSP 有了更进一步的理解,它本质上是胶水,把 VS Code 和各种语言的 LS 粘在一起。但它不是普通的胶水,而是非常有品位的胶水,这品位就体现在细节上。

首先这是一个基于文本的协议,文本降低了理解和调试的难度。参考 HTTP 和 REST 的成功,很难想象如果这是一个二进制协议会是什么局面,甚至同样是文本协议的 SOAP 也早已作古,足以说明“简单”在打造开发者生态里的重要性。

其次这是一个基于 JSON 的协议,JSON 可以说是最易读的结构化数据格式了。大家看看各个代码仓库里的配置文件都是啥格式就知道这是个多么正确的决定了,现在还有人在新项目里用 XML 吗?又一次——“简单”。

再次,这是一个基于 JSONRPC 的协议。由于 JSON 的流行,各大语言都对它有极好的支持,所以开发者根本不需要处理序列化、反序列化一类的问题,这是实现层面的“简单”。

从这些细节可以看出,VS Code 团队对当今技术趋势的把握是相当精准的,他们决策充分考虑到了“简单”,牢牢抓住了社区开发者的心。所以重要的事情说三遍:

在做设计的时候一定要倾向于简单。

在做设计的时候一定要倾向于简单。

在做设计的时候一定要倾向于简单。

集大成的 Remote Development

今年五月,VS Code 发布了 Remote Development(VSCRD),有了它,我们可以在远程环境(比如虚机、容器)里开一个 VS Code 工作区,然后用本地的 VS Code 连上去工作,下图说明了它的运行模式:

VSCRD 从本质上改善了远程开发的体验,与常用的远程桌面共享相比,具体改进如下:

响应迅速:VSCRD 所有的交互都在本地 UI 内完成,响应迅速;远程桌面由于传输的是截屏画面,数据往返延迟很大,卡顿是常态。

沿用本地设置:VSCRD 的 UI 运行在本地,遵从所有本地设置,所以你依然可以使用自己所习惯的快捷键、布局、字体,避免了工作效率层面的开销。

数据传输开销小:远程桌面传输的是视频数据,而 VS Code 传输是操作请求和响应,开销与命令行相仿,卡顿的情况进一步改善。

第三方插件可用:在远程工作区里,不仅VS Code的原生功能可用,所有第三方插件的功能依然可用;远程桌面的话,你得自己一个个装好。

远程文件系统可用:远程文件系统被完整映射到本地,这个两者差不多。

那么 VSCRD 做了什么神奇的操作能够实现以上效果呢?来看看它的架构图:

🔥 其实答案都在前文有所提及:

进程级别隔离的插件模型

Extension Host(也就是图中的 VS Code Server)与主程序做到了物理级别的分离,那么把 Extension Host 在远程或者本地跑没有本质的区别。

UI 渲染与插件逻辑隔离,整齐划一的插件行为

所有的插件的 UI 都由 VS Code 统一渲染,所以插件里面只有纯业务逻辑,行为高度统一,跑在哪里都没区别。

高效的协议LSP

VS Code 的两大协议 LSP、DAP 都非常精简,天然适合网络延迟高的情况,用在远程开发上再适合不过。

VS Code 团队在架构上的决策无疑是非常有前瞻性的,与此同时,他们对细节的把握也是无可挑剔。正因为有了如此扎实的工程基础,VSCRD 这样的功能才得以诞生,所以我认为这是集大成的作品。

🔥 安利几个 VSCRD 非常有用的场景:

开发环境配置起来很繁琐,比如物联网开发,需要自己安装和配置各种工具和插件。在 VSCRD 里,一个远程工作区的模板即可搞定,如需安装额外的工具,也就是改改 Dockerfile 的事情,非常简单。在这里可以找到常用的编程语言和场景的模板。

本地机器太弱,某些开发搞不了,比如机器学习,海量数据及和计算需求需要非常好的机器。在 VSCRD 里,可以直接操作远程文件系统,使用远程计算资源。

最后

VS Code 像一颗耀眼的星星,吸引着成千上万开发者为其添砖加瓦。

从 VS Code 的成功中,我们看到了好的设计和工程实践能创造多少奇迹。放眼软件产业,各个层面的模式不断被刷新,让人激动之余,也要求从业者不断提高技能水平。

从个人学习的角度来看,了解这些模式诞生的前因后果,理解工程实践中的决策过程是非常有利于提高工程能力的。

文章目录
  1. 1. 简洁而聚焦的产品定位,贯穿始终
  2. 2. 进程隔离的插件模型
  3. 3. UI 渲染与业务逻辑隔离,一致的用户体验
  4. 4. LSP——基于文本的协议
  5. 5. 集大成的 Remote Development
  6. 6. 最后