图片来自Pexels。作为一个开源项目,它也吸引了无数第三方开发者和终端用户,成为顶级开源项目之一。功能足够,在体验上易上手,在有大量插件的情况下简单流畅,确实值得称赞。我是VSCode用户,我也为它开发过插件。插件市场上的很多Java插件基本上都是我们团队的作品,所以我在日常工作中也观察到了VSCode的很多工程亮点。讨论。贯穿始终的简洁而专注的产品定位。你知道VSCode的开发团队才二十出头吗?很难相信,大家都认为VSCode无所不能,这么强大的工具怎么可能是这么少的人做出来的。其实,丰富的功能是一种美丽的错觉,因为大部分针对特定编程语言和技术的功能都是由第三方插件提供的。VSCode的核心总是很精简,这很考验产品团队的处理能力:做的太多,臃肿,人手不够;能做的太少,太弱,没人会用。他们的团队选择专注于核心功能的开发,为用户提供简单流畅的体验,而这一理念贯穿于产品研发的每一个环节。在我看来,这是第一个亮点。第一个亮点也是一个难点,因为“简单”毕竟是产品的“形”,更关键的是前置问题——产品的定位,解决什么问题。从用户的角度来看,这个问题可以转化为以下几点:为什么我们需要一个新工具?是代码编辑器(Editor)还是集成开发环境(IDE)?让我们来看看负责的项目根据ErichGamma的说法:这张截图说明了VSCode的定位:编辑器+代码理解+调试。这是一个非常克制和平衡的选择,专注于开发者“最常用”的功能,同时在产品的形式上力求简洁和高效。从结果来看,这个定位还是比较成功的。在这种定位的指引下,这20多位工程师想出了VSCode。相对较小的功能集使开发人员能够在代码质量上精益求精,而最终用户也可以获得性能卓越的工具。这是VSCode从其他编辑器中脱颖而出的重要原因。正是因为产品的定位和团队职责的高度克制,团队成员才能花时间在这样的问题上,写出经得起考验的代码。同时,更小的团队也能让团队成员在行为上达到统一,这在社区互动中尤为明显。你可以去GitHub查看他们的Issues。超出产品定位范围的请求和反馈基本谢绝或转给第三方插件项目。可以说他们非常敬业。看到这里,好像一切都很好,但是问题来了,千千万万的coder,你用Node,我用Go,你做前端,我做后端,VSCode如何满足这些不同的需求呢?你已经机智地回答了——海量插件。那么让我们仔细看看VSCode是如何管理一个庞大的插件生态系统的。进程隔离的插件模型通过插件来扩展功能是很常见的做法,但是如何保证插件和原生功能一样好呢?历史告诉我们:没有保证。可以参考Eclipse,插件模式可以说是非常周密,功能层面无所不能,但是有几个恼人的问题:不稳定,难用,慢,所以很多用户转用IntelliJ。可以说,成功也是外挂,失败也是外挂。问题的本质在于信息不对称,导致不同团队写出的代码思想和质量不一致。最后,用户得到的是一个凌乱和卡住的产品。因此,让插件在稳定性、速度和体验上与原生功能保持一致,只能是一个美好的愿望。让我们看看其他IDE是如何做到的。VisualStudio自己处理所有功能,让其他人无所事事。开箱即用,插件是可选的。看起来什么都自己做是个好办法,但是你知道吗,VisualStudio背后有一个几千人的工程团队。显然,这不是VSCode可以处理的事情。他们选择让大家做插件,那么Eclipse遇到的问题怎么解决呢?这里分享一个小知识——Eclipse核心部分的开发者是早期的VSCode团队。好吧,所以他们没有两次踏入同一条河流。与Eclipse不同,VSCode选择将插件保留在盒子里。这种方式首先要解决的问题就是稳定性,这对于VSCode来说尤为重要。我们都知道VSCode是基于Electron的,它本质上是一个Node.js环境,单线程,任何代码崩溃都将是灾难性的。所以VSCode干脆不相信任何人,把插件放在一个单独的进程里,任由你折腾,主程序就好了。将插件与主进程隔离VSCode团队的决定不无道理。前面说了,团队里很多人其实都是Eclipse的老部分出身,自然对Eclipse的插件模型有深入的思考。Eclipse的设计目标之一就是将组件化推向极致,因此很多核心功能都以插件的形式实现。不幸的是,Eclipse的插件运行在主进程中,任何性能不佳或不稳定的插件都会直接影响Eclipse。最终的结果是每个人都抱怨Eclipse臃肿、缓慢且不稳定。VSCode实现了基于进程的物理级隔离,成功解决了这个问题。其实进程级的隔离还引出了另外一个话题,就是接口和业务逻辑的隔离。UI渲染与业务逻辑隔离,一致的用户体验“不稳定”“不稳定”之后的问题是“难用”,具体是界面和流程混乱,原因是插件之间的界面语言“不一致”,它导致异常陡峭的学习曲线,并且在遇到问题时没有统一的解决方案路径。VSCode的做法是根本不给插件“发明”新界面的机会。如上图,插件锁定在ExtensionHost进程中,而UI在主进程中,自然插件无法直接操作用户界面。VSCode管理所有用户交互入口并制定交互标准。所有的用户操作都会转化为各种请求,发送给插件。插件能做的就是响应这些请求,专注于业务逻辑。但自始至终,插件无法“决定”或“影响”界面元素的渲染方式(颜色、字体等,根本不是),至于弹出对话框,甚至更不可能。VSCode对用户界面的把控可以说是极其谨慎。做过外挂的都知道。有兴趣的同学可以深入挖掘TreeView的历史,会有更直观的体验。乍一看,第三方开发者卡住了。这不是限制了大家的创造力吗?我想说,这种做法与这个团队的背景息息相关,换一批人??很可能会失败。他们之所以能够成功,是因为团队在开发工具领域深耕多年。他们将自己的经验转化为意见,最终实现在VSCode的界面元素和交互语言上。从结果来看,他们很受欢迎。接口与业务逻辑的完全隔离,使得所有插件的行为一致,用户获得统一的体验。不仅如此,这种界面和行为的一致性最终转化为另一个“伟大”的特性——远程开发,我们将在后面讨论。接下来我们要说的是VSCode的另一项创举——LanguageServerProtocol。LSP:text-basedprotocol上面提到的VSCode定位的两大特点:代码理解和调试,大部分是通过第三方插件实现的,中间的桥梁就是两大协议——LanguageServerProtocol(LSP))和调试适配器协议(DAP)。两者在设计上高度相似,下面重点介绍最火的LSP。首先,我们为什么需要LSP?全栈开发早已成为这个时代的主流,软件从业者越来越不受特定语言或技术的限制,这也对我们的钻石提出了新的挑战。比如我用TypeScript和Node.js做前端,同时用Java写后台,偶尔用Python做一些数据分析,所以大概需要几个工具的组合。这样做的问题是需要在工具之间频繁切换,无论从系统资源消耗还是用户体验角度来看都是低效的。那么有没有一种工具可以在同一个工作空间中处理所有三种语言呢?没错,就是VSCode——一个支持多种语言的开发环境,而支持多语言的基础是语言服务器协议(LSP)。该协议在短短几年内取得了前所未有的成功。到目前为止,已经有来自微软等大公司和社区的上百个实现,基本涵盖了所有主流编程语言。同时,它也被Atom、Vim、Sublime、Emacs、VisualStudio、Eclipse等其他开发工具所采用,从另一个角度证明了它的优秀。更难能可贵的是,该协议还具有轻量级和快速性,可以说是VSCode的杀手锏,也是微软最重要的IP之一。哇塞,强大又轻巧,怎么看都是骗局,看它是怎么做到的。重点第一:适度的设计,合理的抽象,周到的细节。如果我要设计这样一个支持所有编程语言的东西,第一反应可能是创建一个覆盖所有语言特性的超集。微软有过这样的尝试,比如Roslyn——一个语言中立的编译器,C#和VB.NET编译器都基于它。大家都知道C#的语言特性是非常丰富的,Roslyn能够支持C#足以见其强大。那么问题来了,为什么没有在社区广泛使用呢?我认为根本原因是“强大”的副作用:复杂和主观(Opinionated)。光是语法树就已经很复杂了,其他的各种特性和它们之间的关系就更让人望而生畏了。这样的庞然大物,普通开发者是不会轻易碰触的。相比之下,LSP显然将紧凑性作为其设计目标之一。它选择做最小的子集,实现了团队一贯的克制风格。它关注的是用户在编辑代码时最常打交道的物理实体(如文件、目录)和状态(光标位置)。它根本不去了解语言的特性,编译也不是它关心的事情,自然不会涉及语法树等复杂的概念。它不是一步完成的,而是随着VSCode功能的迭代逐渐发展起来的。因此,从诞生之日起,它仍然保持着体积小、易于理解、实现门槛低的特点。迅速获得了社区的广泛支持,各种语言的LanguageServer(LS)遍地开花。小就是小,功能少不了,所以抽象很关键。LSP最重要的概念是动作和位置。LSP的大部分请求都是表达“在指定位置执行指定动作”。例如,用户将鼠标悬停在类名上可以查看相关定义和文档。这时VSCode会向LS发送一个'textDocument/hover'请求。这个请求中最关键的信息是当前文档和光标的位置。LS收到请求后会经过一系列的内部计算(识别光标位置对应的符号,并找出相关文档),找出相关信息,然后返回给VSCode展示给用户.这样的来回交互在LSP中被抽象为请求(Request)和响应(Response),LSP也对其进行规范(Schema)。从开发者的角度来看,概念很少,交互形式也很简单,非常容易实现。看到这里大家应该对LSP有了更深的理解,本质上就是胶水,把各种语言的VSCode和LS粘在一起。但它不是普通的胶水,而是一种很有味道的胶水,这种味道体现在细节上。首先,这是一个基于文本的协议,文本降低了理解和调试的难度。参照HTTP和REST的成功,很难想象如果这是一个二进制协议会发生什么。就连同为文本协议的SOAP也早已逝去,足以说明“简单”在构建开发者生态系统中的重要性。其次,这是一个基于JSON的协议。JSON可以说是最易读的结构化数据格式。看看各个代码仓库的配置文件就知道这是多么正确了。还有人在新项目中使用XML?再次-“简单”。同样,这是一个基于JSONRPC的协议。由于JSON的流行,各大语言都对它有极好的支持,所以开发者根本不需要处理序列化和反序列化等问题。这是一个实现级别“简单”。从这些细节可以看出VSCode团队对当今技术趋势的把握非常精准,他们的决策充分考虑了“简单”,牢牢抓住了社区开发者的心。所以重要的是说三遍:做设计,一定要趋于简单。做设计的时候,一定要倾向于简单。做设计的时候,一定要倾向于简单。远程开发(VSCRD),有了它,我们可以在远程环境(如虚拟机、容器)中打开一个VSCode工作区,然后使用本地VSCode连接工作。下图说明了它的运行方式:VSCRD从本质上提升了远程开发的体验。与常用的远程桌面共享相比,具体改进如下:快速响应:VSCRD的所有交互都在本地UI完成,响应速度快;由于远程桌面传输截图,数据往返延迟很大,卡顿很正常。使用本地设置:VSCRD的UI在本地运行并遵循所有本地设置,因此您仍然可以使用习惯的快捷键、布局和字体,避免工作效率的开销。数据传输开销小:RemoteDesktop传输视频数据,VSCode传输操作请求和响应。开销与命令行类似,卡顿情况进一步改善。第三方插件可用:在远程工作区,不仅VSCode原生功能可用,所有第三方插件的功能依然可用;对于远程桌面,您必须一个一个地安装它们。远程文件系统可用:远程文件系统完全映射到本地,几乎一样。那么VSCRD到底做了什么神奇的操作才能达到上述效果呢?看一下它的架构图:其实答案在之前的文章:进程级隔离插件模型:ExtensionHost(也就是图中的VSCodeServer)和主程序物理分离,因此远程或本地运行ExtensionHost没有本质区别。UI渲染与插件逻辑隔离,插件行为统一:所有插件的UI统一由VSCode渲染,插件内只有纯业务逻辑,行为高度统一,它运行的地方没有区别。高效的协议LSP:VSCode的两大协议LSP和DAP非常精简,天然适用于网络延迟高的情况,特别适合远程开发。VSCode团队的架构决策无疑是非常具有前瞻性的,同时,他们对细节的把握也是无可挑剔的。正是因为有这么扎实的工程基础,才能诞生VSCRD这样的功能,所以我觉得这是一部杰作。对于那些还没有尝试过VSCRD的人,让我们再看看它。在以下场景非常有用:开发环境的配置非常繁琐,比如物联网的开发,需要自己安装配置各种工具和插件。在VSCRD中,可以完成一个远程工作区模板。如果需要安装额外的工具,也就是更改Dockerfile,非常简单。常见编程语言和场景的模板可以在这里找到。本地机器太弱,有些开发做不了,比如机器学习,海量数据和计算需求,需要非常好的机器。在VSCRD中,可以直接操作远程文件系统,使用远程计算资源。最终,VSCode就像一颗闪亮的明星,吸引了成千上万的开发者为其贡献力量。从VSCode的成功中,我们看到了良好的设计和工程实践可以创造多少奇迹。放眼软件行业,各个层级的模型都在不断刷新,这不仅令人兴奋,也需要从业者不断提升自己的技能。从个人学习的角度来看,理解这些模型的前因后果,理解工程实践中的决策过程,对于提升工程能力是非常有益的。作者:李少霞编辑:陶家龙来源:zhuanlan.zhihu.com/p/35303567
