前言世界上最难懂的就是面面俱到。世界上最不可理解的事情,就是它其实是可以理解的。--AlbertEinsteinOpenSource(Open-Source)创造了当今欣欣向荣、活跃的软件产业。开源使全世界的开发者能够共同编写优秀的工具项目,即所谓的“轮子”。在造福大大小小的公司和个人的同时,也能彰显创作者或贡献者的技术实力。现在很多开发者都在使用开源项目作为自己项目的第三方库或者依赖,来更快更高效的完成开发任务。作者也不例外。最近在用Vue3重构Crawlab前端的时候,使用了Element团队开发的升级版ElementUI,即Vue3重构的全新UI框架ElementPlus。ElementPlus,全面拥抱TypeScript;并且相比之前的Vue2版本丰富了一些组件;而整体风格和用法与上一版本保持一致;一些API在使用中也变得更加精简。因此,笔者在重构Crawlab前端的初期过程中并没有遇到太多的阻碍。再加上之前的写作经验,开发流程好像很熟悉。然而,好景不长。随着项目的不断发展,笔者遇到了一些技术难题。更确切地说,在实现一些复杂的功能时,我遇到了ElementPlus框架本身的限制。虽然最终成功解决了问题,但也深刻体会到破解开源项目源代码的难度。因此,我也希望借此机会,与读者分享我在控制开源代码方面的经验。本文将从解决ElementPlus问题的经验出发,循序渐进地讨论开源项目或开源框架的问题,进一步讨论开源项目源码控制的方法和技巧,分享我的阅读心得,理解和改变源代码。本文主要是方法论的探讨,没有过多的技术细节,任何专业背景的读者都可以阅读。如果你是使用过Vue的前端工程师,那你一定听说过ElementUI。这是一个UI框架,也就是说它是一个帮助你搭建web项目的工具框架,里面包含了很多常用的组件(Component)、布局(Layout)和主题(Theme)。早期的ElementUI是用Vue2编写的,Vue中最流行的UI框架,在Github上有49kstars,比第二名iView(24k)高出一倍多。在Vue作者游雨溪发布Vue3版本并宣告全面拥抱TS后,原Element团队基于Vue3开发了新版ElementPlus,也就是本次故事的主角。如果你对Vue3甚至Vue都不会,可以看看本博客之前的技术文章《TS 加持的 Vue 3,如何帮你轻松构建企业级前端应用》。ElementPlus的使用方法和之前的Vue2版本一样简单易用,文档风格也和老版本保持一致,非常全面。在官方文档的鼓励下,作者也尝试使用组件化的方式来优化旧的Crawlab组件,例如表格。除了简单封装列表(Column)、数据(Data)、分页(Pagination)等,作者还希望增加一些新的实用功能,比如自定义过滤(Filter)和排序(Sort)table,以及form可以让用户自定义(Customize)要显示的列和调整列的顺序等等。ElementPlus框架虽然有排序过滤功能,但个人觉得太基础了。通常用户需要简单易用的UI组件。下图是过滤表的前端截图,类似Excel的显示和操作。实现这个功能问题不是很大,按照官方文档解决即可。其中,主要使用了列组件中el-table-column的表头槽(Slot),并在其中添加了一个弹出框(Popover)。非常好,到目前为止一切顺利!接下来就是添加列的自定义功能了,貌似很快就搞定了。实现栏目自定义最完美的方式就是让用户点击操作按钮,会弹出一个对话框(Dialog),在对话框中可以选择要显示的栏目,通过拖拽的方式进行排序,然后点击好的。然后很快在ElementPlus的官网上找到了一个新的组件,穿梭箱(Transfer),效果如下图所示。理论上我只要参照对话框中的弹出框来控制显示列的数组,比如把要显示的列放在“列表2”中。铛铛!到目前为止,一切似乎都很顺利。但是仔细阅读文档后发现该组件不支持拖拽,所以暂时无法实现列排序功能。有点郁闷,不过没关系,我们主要想实现的是列选择功能。虽然排序也很重要,但先搁置一下,先实现组件选择!然而,现实总是出乎意料。在进一步的实验中,作者发现这个组件似乎有一个重大的bug:当所有候选元素在穿梭框中被选中时,无法选中或取消选中(左移或右移)!赶紧认真调试找原因,反复确认是不是我自己写的代码有问题。然后发现Vue更新组件时在runtime-core.esm-builder.js中的patchBlockChildren方法下oldChildren为null。进一步阅读了ElementPlus中穿梭盒组件的相关源码,怀疑是transfer-panel.vue子组件有问题。更新渲染时,el-checkbox-group不存在,导致更新时出错。然后我在ElementPlusGithub存储库中提交了一个错误问题。但是,维护者并不认为我报告的问题是一个bug,因为在CodePen上似乎没有问题。但是这个bug在我的项目中确实是客观存在的,并且可以在crawlab-frontend的commithistory中重现。面对困难和无奈,琢磨半天无果后,果断决定自己造轮子。很快我从头写了一个类似穿梭盒的组件,组件效果如下图所示。新写的组件在外观上和之前的非常相似,但是自由度高,所以也顺便支持了拖拽功能。看来问题要解决了。然而,就在我满怀期待准备测试拖拽排序功能的时候,意想不到的事情发生了:列顺序数据发生变化后,表格列根本没有变化!改变列组件el-table-column的顺序对界面显示没有影响!在官方文档的定义中,el-table-column的顺序决定了列的实际显示顺序。经过反复实验,作者不得不承认原来的实现方式有问题。简单地改变模板中列组件的顺序不会影响界面上的显示。当时,作者想到了一个“从(yu)明(chun)”的方法。我试图通过强制刷新来重新渲染表格组件的数据,以达到改变列顺序的目的。但是经过测试发现,这种暴力的方式存在很大的性能问题,每次点击“应用”后都会卡顿近一秒。于是,笔者陷入了绝境。左右为难,笔者停下来仔细思考。是不是对ElementPlus的框架不够了解?表格组件本身是如何实现的?它的局限性和缺点是什么?这些问题促使我进一步拆解ElementPlus框架本身,即阅读其源代码,了解组件本身的代码逻辑和工作原理。于是,笔者将ElementPlus代码仓库克隆到本地。好在ElementPlus项目的代码质量相当高,代码组织结构和命名方式都非常清晰。虽然注释相对较少,但其清晰的逻辑结构和良好的命名约定使其具有很强的可读性。在惊叹大厂工程师专业度的同时,我也很快定位到了table组件的源码位置packages/table。整个ElementPlus项目由MonoRepo管理,简单来说就是一个Git仓库中有很多NPM项目。管理工具使用Lerna。下面是el-table组件npm项目的代码组织。.├──测试│└──table.spec.ts├──index.ts├──package.json└──src├──config.ts├──filter-panel.vue├──h-helper.ts├──layout-observer.ts├──store│├──current.ts│├──expand.ts│├──helper.ts│├──index.ts│├──tree.ts│└──watcher.ts├──table│├──style-helper.ts│└──utils-helper.ts├──table-body│├──events-helper.ts│├──index.ts│├──render-helper.ts│├──styles-helper.ts│└──table-body.d.ts├──table-column│├──index.ts│├──render-helper。ts│└──watcher-helper.ts├──table-footer│├──index.ts│├──mapState-helper.ts│└──style-helper.ts├──table-header│├──event-helper.ts│├──index.ts│├──style.helper.ts│├──table-header.d.ts│└──utils-helper.ts├──table-layout.ts├──table.type.ts├──table.vue├──tableColumn.ts└──util.ts从这个结构可以看到,整个el-table表格组件由几个子组件组合而成,例如table-body、table-column、table-footer等。而整个项目只有table.vue这一个组件。根据Vue开发的经验,笔者很快意识到这是整个组件的入口。我们先来看看吧!整个table.vue文件有469行代码,算是一个比较大的文件了。限于篇幅,这里不再详细说明。其中,最重要的发现是一个叫做store的变量。仔细一看,这是Vuex创建的状态管理器。好小子!原来table组件是用Vuex来管理数据的。现在很清楚了,只要能解决Vuex部分,剩下的问题就应该解决了。事不宜迟,设置它!问题解决了我开始不断地寻找与专栏相关的代码,并开始在全球范围内搜索“专栏”和其他类似的关键字。玩完名侦探柯南的洞察力,渐渐注意到src/store/index.ts文件中的useStore方法,这是整个问题的关键!好了,整个问题的原因找到了。关键在于内部状态变量store.states._columns,它是渲染列数据的关键。但是只有在初始化或者增删列的时候才会赋值。调整el-table-column的顺序根本不会改变这个变量!找到原因,解决方法就简单了。作者添加了Mutation方法setColumns,更方便的设置列数组。具体实现过程限于篇幅不再详述。感兴趣的朋友可以看看CrawlabFrontend的源码。以下是完整表格自定义列的效果图。你完成了!开源框架的优缺点世界上没有完美的东西,优秀的开源框架也不例外。而这一次Hackingthesourcecode的经历,让笔者深刻的明白了这个道理。笔者认为,对开源框架或开源项目的使用和开发的相关问题进行探讨是非常有必要的。首先,让我们来看看它的优点。优势很多知名优秀的开源项目,如Nginx、Redis等,已经成为软件开发的核心技术。那么,我们为什么要使用好的开源框架呢?笔者认为开源框架有以下优点:免费。谁能拒绝不花钱的东西,很多免费的开源框架就够用了;透明的。开源框架的所有源代码都是公开的,任何人都可以看到;它可以改变。大部分开源项目为MIT或BSD开源版权,自由度高,可按需定制开发;他们可以合作。Github是最大的开源项目平台,全球开发者都可以参与迭代开源项目;影响。优秀的开源项目可以提升作者或贡献者在业界的知名度和影响力。缺点虽然开源项目有很多突出的优点,但是在使用开源项目的时候经常会遇到各种各样的问题。笔者认为使用开源框架存在一定的风险。笔者认为开源框架主要有以下缺点:潜在的安全隐患。虽然很多优秀的开源项目都是由企业或资深专家开发和维护的,但贡献者往往因为没有完全自己使用而疏忽安全。知名开源项目的安全漏洞例子数不胜数,例如OpenSSLHeartbleed、FastjsonRemotecodeexploits、AntdChristmaseggs等;好和坏。开源项目的开发者、贡献者、维护者可以是任何人,各自的经历和专业背景不同,难免会在代码或开源项目的质量上存在一些差异;虽然编码标准(CodingStandard)可以避免一些问题,但是好的项目毕竟是少数,看看托管了数百万项目的NPM或者Maven公共仓库;学习成本。笔者承认,一些优秀的开源框架拥有非常成熟和完善的文档体系,但大部分仍然缺乏有效的文档和教程支持;即使有详细的文档,开发者也会花费大量的时间和成本去阅读和学习;而且大部分付费产品包含专业的技术支持,可以有效帮助开发者节省时间;持续存在的问题。优秀的程序员可以开发出非常优质的开源项目,但由于开源项目本身并不能带来现金收益,很多作者不愿意长期投入开源项目,导致优秀的开源项目不断维护迭代.未知的风险。再好的框架也会有风险。由于开源框架前期没有经历过太多的实际业务考验,很多问题不能及时修复,所以在使用开源框架的过程中或多或少会遇到一些意想不到的问题,需要一段时间很多时间去解决它们,甚至有些问题无法解决。问题回顾根据笔者前面提到的解决开源框架问题的经验,未知风险和学习成本尤为突出。当我使用ElementPlus的Transfer组件时,我理所当然地认为它可以有效地满足我的需求。然而,没想到的是,它并不能支持我规划的列排序需求,而且出现了重大的bug,导致无法使用。这些问题导致我放弃了组件,转而采用成本高昂的重新发明轮子的解决方案。在啃ElementPlus表格组件的过程中,我并没有放弃使用该组件,而是通过阅读该组件的源码,根据逻辑和判断核心代码的位置以及可能出现问题的原因经验,并针对性进行了调整,最终顺利解决了问题。这次决定阅读源代码并进行调整,为我节省了大量的时间和精力,因为重新创建表格组件等复杂的轮子来做大量工作是非常不经济的。上图是官方文档中其中一个组件的截图。ElementPlus的文档其实是非常完整的。组件的使用方法、例子、API等都写得很详细。开发中遇到不明之处,直接去官方文档查一下就可以了。但是ElementUI最早诞生于饿了么,所以里面的组件主要是支持饿了么的外卖业务,所以有一定的局限性。作为ElementUI的升级版,ElementPlus虽然是同一个团队的产品,但是其功能和API相比老版本并没有太大变化。相比之下,蚂蚁金服诞生的UI框架AntDesign相对更加灵活和自由,因为它支持的业务线非常广泛,而阿里主推的中台系统也要求UI框架设计成很一般。因此,ElementPlus的局限性很可能来自其开发团队的背景。Crawlab的前端框架选择ElementPlus主要是因为老版本是ElementUI,迁移成本比较低,但也带来了一些框架不成熟带来的问题。甚至,要知道再优秀的开源项目也会出现这样那样的问题。从这个角度来看,了解开源框架本身的优缺点对于如何正确使用它是非常重要的。为了让开源框架为我所用,我们需要知道如何正确浏览开源项目的源代码。接下来,笔者将结合自己的项目经验,向读者介绍如何在开源项目中有效控制源代码。源码控制技巧如果一个古代人穿越到现代,看到各种各样的科技产品,一定会称其为魔法。然而,他们不知道的是,这些所谓的“魔法”,都是利用科学技术创造出来的,其背后的原理是通过科学实验总结出来的,并不是什么神秘的巫术。同样的道理也适用于软件行业。程序员用代码创造了一个神奇的互联网社会,这对于非技术人员来说是非常神秘的。他们知道这不是真正的魔法,但他们完全不知道它是如何完成的以及它背后的原因。对于程序员来说,使用一些优秀的开源项目可以让他们快速开发产品原型,验证产品可行性,甚至开发出可生产的系统。但是,如果程序员只是把开源产品当作工具使用,不了解其中的原理,那无异于施魔法,不知道为什么会起作用。这样做会给您带来麻烦。要正确使用一个开源框架,你必须掌握它;要掌握它,您必须了解它是如何工作的;要了解它的工作原理,您必须阅读其源代码;要让源代码服从你,你必须学会??如何控制它,即如何修改和优化源代码。接下来,笔者将介绍几种有效的方法来帮助大家控制源码。克服挫折读者可能会对这个看似无用的建议感到惊讶。但是,看到很多朋友想要学习一个开源项目时,往往会因为项目太复杂而半途而废,包括很多年前的我自己。这就是挫败感发挥作用的地方。我承认短期内理解一个大型的开源项目是不现实的。这让很多朋友被眼花缭乱的技术难点吓跑了,留下一句“只要你会用”,然后继续搬砖,拧螺丝。事实上,他们都太过低估了自己的潜力。笔者并不反对程序员的实用主义,但需要注意的是,过度的实用主义会导致功利主义,到头来原地踏步的还是自己。开源框架最大的好处就是对用户是完全透明的,任何人都可以看到里面的代码,包括底层的实现逻辑、代码组织结构、项目部署方式等等。这难道不是一个提升自己对源代码控制能力的绝好机会吗?通过阅读一个开源框架的源代码,你不仅可以了解它是如何工作的,还可以学到更好的编程方法,提高你的专业水平。我真的不提倡“从入门到放弃系列”的玩笑,你至少应该这样暗示自己,“我现在看不懂,因为我的基础知识不够,以后我就可以了”待我基础实力提升后控制。”笔者啃过一些复杂的开源框架的源码后,发现阅读源码其实并没有那么难。就像锻炼身体一样,过了这个山坡,这个冲刺,之后就会很轻松,身体素质也会提高。因此,克服挫折感对于浏览开源项目的源代码非常重要,你不得不暗示阅读源代码并不困难。人们不想阅读源代码的原因之一是因为他们不知道。就像当你不认识你喜欢的女孩时,你会认为她是完美的女神;但是当你真正追求她,和她约会交换生活的时候,你会发现她只是一个普通的女孩。因此,您可以尝试自己开始编写一个开源项目。这对你理解开源项目作者的思路会有很大的帮助。同样,这不是任何实用的建议,但如果你真的开始这样做,你一定会很快提高。俗话说:边做边学!当然,开始编写一个开源项目并不是一件容易的事。您可能一开始就陷入了写什么的困境。在这里推荐一下我之前写的关于开源项目开发的两篇文章《如何打造一个上千Star的Github项目》和《收获人生第一个 5k Star 开源项目,经验教训分享给大家》,详细阐述了作者开发、维护、推广开源爬虫管理平台Crawlab的经验,并将介绍如何定位痛点、研究用户、推广产品、项目管理等等。有兴趣的读者可以深入阅读。定位入口文件最后,这是一个稍微干巴巴的建议。阅读源代码时,首先要找到项目的入口文件(EntryFile)。所谓入口文件,就是项目模块或系统暴露给外部系统的一个公共通道,整个项目的运行、调用、执行都从这个文件开始。而其他实现具体逻辑的代码在入口文件或其他文件中引用。对于前端项目,一般是main.ts;对于Python项目,一般是app.py;对于Golang项目,一般是main.go等。如果把看似不可控的大型开源项目比作战无不胜的战神阿喀琉斯,那么入口文件就是他的致命伤。其实不光是我们在阅读开源项目的时候,在工作中阅读其他同事写的代码的时候,也是从入口文件入手的。一旦掌握了入口文件的内部逻辑,就可以看到它引用的其他子模块,然后就可以顺着线索找到其他核心模块,然后继续。通过入口文件遍历整个项目后,你会更容易理解代码的逻辑结构,有助于深入理解具体的工作原理。在前面解决ElementPlus表格问题的例子中,笔者定位到入口文件table.vue,然后进一步找到列数据的关键位置,最终轻松解决了问题。看!浏览源代码实际上并不难,不是吗?使用全局搜索这又是一个技术建议。由于大项目通常会拆分成很多文件,在这种情况下,遍历所有文件会花费很多时间。因此,可以尝试全局搜索可能的关键词,快速定位到核心代码部分。全局搜索在主流IDE中非常强大,通常支持正则表达式、模糊匹配、精确匹配、大小写等。下图是JetBrains的WebStormIDE中全局搜索结果的截图。对于TS这样的静态类型语言,也可以找到Java、C#、Golang等方法或变量的调用位置。在WebStorms中,它是“FindUsages”。在其他IDE中,名称可能不同,但都是A的意思。这些技巧可以有效地帮助您阅读和理解源代码。阅读技术文档好的文档包括项目架构、原理、概念等,可以帮助开发者快速理解框架的代码结构。但是并不是所有的开源框架都有详细的文档说明,这种方式很少见。但是,如果开源项目中包含技术文档,请务必花时间浏览一下。当然,你也可以尝试联系开源项目的作者。NPM项目信息通常包括作者的电子邮件地址;而如果你在看Github上的开源项目,你也可以主动向其提交Issue,在Issue中表达你的问题。总结本文介绍了笔者对开源Vue3UI框架ElementPlus的开发经验,讨论了开源项目的优缺点,得出优秀的开源框架也存在问题的结论。然后,作者结合自己的项目经验,提出了5个驾驭开源项目的技巧和方法,包括克服挫折感、自己写开源项目、定位入口文件、使用全局搜索、阅读技术文档。今天的软件发展离不开优秀的开源项目,但我们也必须认识到,开源项目并不是专门为自己服务的,所以它有一定的局限性。为了更好的使用开源框架,使其兼容你复杂的需求,很多时候你必须仔细阅读它的源代码,这必然会产生一定的学习成本。我们通常说,程序员必须会“写”出好的代码,才能提高自己的技术实力;然而,在团队合作越来越重要的趋势下,笔者认为程序员更重要的能力是能够“读懂”别人的代码,无论好坏。在编写代码的过程中,保证命名约定正确合理,并尽可能添加注释,以方便他人理解。这就是写代码的能力;在和别人合作的时候,仔细阅读别人写的代码,尽量去理解他们的想法,思考实现的方法是否正确等等,这就是读代码的能力。当然,写作和阅读只是很基础的部分。把控代码的能力不仅仅是“写”和“读”,还应该包括思维、逻辑、规划等能力,比如系统架构、算法原理、可扩展性设计等。因此,当我们抱怨代码太差时写我们同事的代码,不要生气、生气,因为可能是你阅读代码的能力不够,没有看清同事的想法;代码也很糟糕;或者,这就是传说中的五十步和一百步?社区如果对作者的文章感兴趣,可以加作者微信tikazyq1并注明“码之道”,作者会拉你进“码之道”交流群。
