熟悉新的 JavaScript 代码库的第一条秘诀
熟悉新的 JavaScript 代码库的第一条秘诀
熟悉新的 JavaScript 代码库的第一条秘诀
在我多年的软件工程师生涯中,我可能已经看过数百个代码库了。多到数不清。大多数时候,我都在努力寻找相关的代码。通常,在工单中寻求帮助,询问应该关注哪些内容并获得指导,就能让我有所进展。慢慢地,我最终会理解代码的功能。你也会的。有些人在这方面比较擅长,有些人则比较慢。这没什么丢人的。大多数代码都很复杂。但我发现了一个简单的工具,可以帮助你简化代码理解过程。它叫做代码复杂度分析器(code-complexity),你可以像下面的代码片段所示那样使用它:
npx code-complexity . --limit 20 --sort ratio
# You can also use --filter '**/*.js' to use glob patterns to filter files
它将返回类似如下的输出:
| 文件 | 复杂 | 搅动 | 比率 |
|---|---|---|---|
| src/cli.ts | 103 | 8 | 824 |
| test/code-complexity.test.ts | 107 | 7 | 749 |
| .idea/workspace.xml | 123 | 6 | 738 |
这将显示最大和更改次数最多的文件。这些文件对于理解应用程序至关重要的可能性非常高。请仔细阅读并理解它们。本文将详细解释这些数据的含义。
复杂性和流动性
本章将解释代码复杂性和变更的概念。这是理解我们在此使用的技术的基础,该技术旨在提高您对代码库的理解。
什么是复杂性?
复杂度可以用不同的方式定义。函数嵌套层级通常被用来衡量代码的复杂程度。由小型函数和组合行为构成的代码通常更易读易懂。因此,我们可以说复杂的代码也包含一些嵌套很深的函数,这在大多数情况下是正确的。然而,嵌套层级难以追踪,所以我们或许可以寻找其他衡量指标。
通常情况下,冗长的函数也会导致庞大的文件。如果一个函数包含大量代码,人们往往会把所有内容都放在一个文件中。因此,理论上我们也可以用代码行数来衡量函数的大小。有很多实用工具包可以解决这个问题。其中一个工具叫做sloc,它可以输出文件中的代码行数。但是不要直接使用它,因为我之前提到的工具已经默认包含了这个功能。
综上所述,我们可以说复杂文件要么嵌套层级非常高,要么长度非常长。通常情况下,这两者往往相伴而生,这很好,因为分析文件长度通常比分析嵌套层级要容易得多。
什么是客户流失?
变更(Churn)的概念比较复杂,难以解释。但我们先从某个地方入手。变更后的文件是指发生了很多更改的文件。但这究竟意味着什么呢?
当很多人修改过同一个文件时,文件就会发生很多次更改。但如何衡量这种更改呢?Git 历史记录会告诉我们文件被提交的次数。因此,我们可以利用这一点来判断文件被修改的可能性。通常,这意味着这类文件是应用程序的核心。然而,这里经常会包含一些配置文件,但你可以将它们从分析中排除。
复杂性和客户流失率能教会我们什么?
现在,在了解了复杂性和变更率的含义之后,我们可以重点关注它们的组合。那些通常占用大量资源且极其复杂的文件通常需要重构。而且大多数情况下,这些文件很可能是应用程序的核心。基本逻辑直接写在这些文件里,或者写在与其相关的文件中。那么,让我们来看看如何进一步分析这种情况。
详细检查文件
我详细检查文件的方法很简单。首先,我会浏览文件,查看导出的函数名称。理想情况下,我会把它们记下来。内部函数的理解对我来说并不重要。在了解所有导出的函数之后,我首先会检查是否有单元测试。如果函数也有参数,我也会尝试把它们记下来。使用 TypeScript 或 Flow 类型,就更容易掌握整体结构了。
单元测试是了解函数工作原理的一个很好的入门方法。要理解函数,你可能只需要查看输入、函数名和返回值。大多数情况下,类型信息会为你提供帮助,而单元测试会展示函数的边界情况以及它的使用方法。所以,这些信息通常足以理解函数。至少如果你熟悉这门编程语言的话。如果你想深入了解函数,当然可以,但这并非必须。为什么呢?下一章会解释。
为什么不能了解所有细节?
详细了解某个功能固然重要,但在入职培训期间,还有许多其他更重要的事情。你不可能在短时间内完全理解应用程序的每一个细节,但了解核心部分应该能让你掌握应用程序核心逻辑的执行位置。
有了这些知识,你就可以着手处理一些对你来说比较棘手的问题了。理想情况下,团队会在代码库中预先准备一些小任务,以便你顺利上手。如果没有,你可以询问你的经理或团队中的资深工程师,看看是否有适合你的现有问题。务必将你对代码库的了解传达给他们,让他们了解你的知识水平。
对于第一个问题,一个好办法是与团队中的其他软件工程师进行结对编程。务必告诉他们你主要负责编写代码,他们更像是指导者,这样你才能学会独立浏览代码库。由于有指导性的入门培训或更简单的任务,你无需一开始就深入了解细节。代码的细节会在修复 bug 或添加新功能的实现阶段逐步发现。你处理的任务越多,对代码库的了解就越深入。但要注意代码的变更和复杂性,因为它们会随着时间而变化。
调试细节?
现在,你需要处理代码库,而调试也意味着另一项更重要的任务。在你的第一个任务中,你很可能已经学会了如何在本地运行应用程序、运行单元测试以及集成测试或端到端测试(如果有的话)。一旦你实现了某个功能,这些测试就变得至关重要,因为它们可以确保你的应用程序按预期运行。然而,这些测试通常会覆盖大量的代码,而且比较抽象。在这种情况下,你必须学会调试代码。由于大多数测试都在 Node.js 环境中运行,我们将快速了解一下如何调试基于 Node.js 的应用程序。大多数工程师习惯于使用console.log调试器,这完全没问题。但如果你需要查看更复杂的代码结构,我建议你使用专业的调试器。JavaScript 和 TypeScript 都支持 `debug`debugger关键字,但是,在 Node.js 中运行测试套件并获得良好的调试体验会比较棘手,因为在 Node.js 中很难启动浏览器实例的开发者工具并将其连接到程序。另一种方法是使用你的 IDE 或编辑器连接到你的编码用户界面支持的调试器。例如,Visual Studio Code 支持直接在 IDE 中调试 Node.js 应用程序。有关“如何在 VS Code 中调试 Node.js”的指南,请参见此处。
调试本身就是一门艺术。你应该熟练掌握断点的使用,并了解“单步跳过”和“单步进入”这些调试功能的含义。这在调试嵌套函数时会非常有用。
一些例子
在本章中,我将通过一些运用此技术的代码库来解释应用程序的核心部分在哪里,以及上述过程如何帮助您更快地熟悉代码库。
Blitz.js
Blitz.js是一个基于 Next.js 构建的框架,它自诩为 JavaScript/TypeScript 版的 Ruby on Rails。该团队已为此框架投入了一年多的时间,其核心逻辑的实现方式也令人十分好奇。
当然,第一步是将存储库克隆到本地文件夹,然后运行:
npx code-complexity . --limit 20 --sort ratio
这将输出以下表格:
| 文件 | 复杂 | 搅动 | 比率 |
|---|---|---|---|
| nextjs/packages/next/compiled/webpack/bundle5.js | 91501 | 1 | 91501 |
| nextjs/packages/next/compiled/webpack/bundle5.js | 91501 | 1 | 91501 |
| nextjs/packages/next/compiled/webpack/bundle4.js | 74436 | 1 | 74436 |
| packages/cli/src/commands/generate.ts | 228 | 28 | 6384 |
| packages/cli/src/commands/new.ts | 177 | 35 | 6195 |
| packages/generator/src/generators/app-generator.ts | 235 | 23 | 5405 |
| packages/generator/src/generator.ts | 283 | 19 | 5377 |
| packages/server/src/stages/rpc/index.ts | 184 | 28 | 5152 |
| packages/server/test/dev.test.ts | 190 | 27 | 5130 |
| packages/core/src/types.ts | 160 | 28 | 4480 |
| packages/server/src/next-utils.ts | 176 | 25 | 4400 |
| packages/generator/templates/app/app/pages/index.tsx | 240 | 18 | 4320 |
| packages/server/src/config.ts | 116 | 37 | 4292 |
| packages/core/src/use-query-hooks.ts | 184 | 22 | 4048 |
| nextjs/test/integration/file-serving/test/index.test.js | 3561 | 1 | 3561 |
| examples/auth/app/pages/index.tsx | 210 | 16 | 3360 |
| packages/cli/src/commands/db.ts | 75 | 44 | 3300 |
| .github/workflows/main.yml | 132 | 24 | 3168 |
| packages/cli/test/commands/new.test.ts | 141 | 19 | 2679 |
| examples/store/app/pages/index.tsx | 181 | 14 | 2534 |
| packages/display/src/index.ts | 158 | 16 | 2528 |
如您所见,有很多不相关的文件可以像编译文件夹一样被过滤掉,但对于初步分析来说,这已经足够了。
我们可以看到多个目录在这里都很重要:
- packages/cli
- 软件包/生成器
- 软件包/服务器
- packages/core
如果我们接到任务,至少会知道去哪里查找相关代码。一开始,我会尝试理解文件,packages/core弄明白它们的作用。如果存在测试用例,也要理解它们,这样就能很好地掌握 Blitz 的工作原理。
React.js
React.js 是一个前端框架,几乎所有 Web 开发人员都耳熟能详。但大多数人并不了解它的代码库结构以及核心组成部分。那么,让我们一起来了解一下吧。
npx code-complexity . --limit 20 --sort ratio
运行该命令将产生以下结果:
| 文件 | 复杂 | 搅动 | 比率 |
|---|---|---|---|
| packages/eslint-plugin-react-hooks/**tests**/ESLintRuleExhaustiveDeps-test.js | 7742 | 51 | 394842 |
| packages/react/src/**tests**/ReactProfiler-test.internal.js | 4002 | 95 | 380190 |
| packages/react-reconciler/src/ReactFiberWorkLoop.new.js | 2373 | 139 | 329847 |
| packages/react-reconciler/src/ReactFiberWorkLoop.old.js | 2373 | 114 | 270522 |
| packages/react-dom/src/server/ReactPartialRenderer.js | 1379 | 122 | 168238 |
| packages/react-reconciler/src/ReactFiberCommitWork.new.js | 2262 | 71 | 160602 |
| packages/react-devtools-shared/src/backend/renderer.js | 2952 | 54 | 159408 |
| packages/react-reconciler/src/ReactFiberBeginWork.new.js | 2903 | 53 | 153859 |
| scripts/rollup/bundles.js | 760 | 199 | 151240 |
| packages/react-reconciler/src/ReactFiberHooks.new.js | 2622 | 56 | 146832 |
| packages/react-dom/src/client/ReactDOMHostConfig.js | 1018 | 140 | 142520 |
| packages/react-reconciler/src/ReactFiberHooks.old.js | 2622 | 50 | 131100 |
| packages/react-reconciler/src/**tests**/ReactHooks-test.internal.js | 1641 | 74 | 121434 |
| packages/react-dom/src/**tests**/ReactDOMComponent-test.js | 2346 | 51 | 119646 |
| packages/react-dom/src/**tests**/ReactDOMServerPartialHydration-test.internal.js | 2150 | 49 | 105350 |
| 包/react-noop-renderer/src/createReactNoop.js | 966 | 109 | 105294 |
| packages/react-reconciler/src/ReactFiberCommitWork.old.js | 2262 | 46 | 104052 |
| packages/react-reconciler/src/ReactFiberBeginWork.old.js | 2903 | 35 | 101605 |
| packages/react-reconciler/src/**tests**/ReactIncrementalErrorHandling-test.internal.js | 1532 | 62 | 94984 |
| packages/react-refresh/src/**tests**/ReactFresh-test.js | 3165 | 29 | 91785 |
我们可以看出,其中两个子包可能是最值得理解的:
- packages/react-dom
- packages/react-reconciler
理解 React Fiber 以及 react-dom 的部分渲染器的工作原理,能让你对 React 的架构有一个清晰的认识。React 代码的优点在于,即使它乍看之下比较复杂,但注释却非常完善,文档也很清晰。
Venom——一款用于 WhatsApp 的 TypeScript 客户端
Venom 是一个用于与 WhatsApp 交互的库。您可以通过此库发送消息并执行更多操作。它更加实用,因为在日常工作中,您通常会使用此类应用程序。那么,让我们运行常用的命令:
npx code-complexity . --limit 20 --sort ratio
| 文件 | 复杂 | 搅动 | 比率 |
|---|---|---|---|
| src/lib/jsQR/jsQR.js | 9760 | 5 | 48800 |
| src/lib/wapi/wapi.js | 474 | 44 | 20856 |
| src/api/layers/sender.layer.ts | 546 | 36 | 19656 |
| src/lib/wapi/store/store-objects.js | 362 | 24 | 8688 |
| src/controllers/initializer.ts | 178 | 48 | 8544 |
| src/lib/wapi/jssha/index.js | 1204 | 5 | 6020 |
| src/api/layers/retriever.layer.ts | 171 | 29 | 4959 |
| src/types/WAPI.d.ts | 203 | 24 | 4872 |
| src/api/layers/host.layer.ts | 258 | 17 | 4386 |
| src/api/layers/listener.layer.ts | 206 | 21 | 4326 |
| src/controllers/browser.ts | 141 | 29 | 4089 |
| src/controllers/auth.ts | 192 | 21 | 4032 |
| src/api/model/enum/definitions.ts | 589 | 6 | 3534 |
| src/api/whatsapp.ts | 95 | 30 | 2850 |
| src/lib/wapi/functions/index.js | 97 | 24 | 2328 |
| src/api/layers/profile.layer.ts | 82 | 22 | 1804 |
| src/lib/wapi/business/send-message-with-buttons.js | 323 | 5 | 1615 |
| src/api/layers/group.layer.ts | 115 | 14 | 1610 |
| src/api/layers/controls.layer.ts | 76 | 20 | 1520 |
| src/api/model/message.ts | 114 | 11 | 1254 |
我们可以看到,这里有一些按重要性排序的目录:
- src/lib
- src/api
- src/controllers
从目录结构可以看出src/lib,其中包含的文件都是自动生成的。理想情况下,我们可以将它们过滤掉,但现在,让我们先看看其他文件。
我们可以看到,这些文件src/api/layers/sender.layer.ts虽然src/api/layers/retriever.layer.ts并不复杂,但变化却很大。因此,每次添加或删除功能时,这些文件都会被修改。这些是应用程序的核心文件,理解它们将帮助你更好地掌握代码库的结构以及应该关注的重点。
这种技术源自何处?
这种分析代码库的技术最初源自亚当·托恩希尔(Adam Tornhill)的著作《软件设计X光》(Software Design X-Rays) ,该书通过流程化的方法讲解了大型代码库的重构。这是一本非常棒的书,它教会你很多构建代码结构的方法,以及哪些部分值得重构。我认为每个软件工程师都应该读一读,因为它能帮助他们以不同的视角理解代码库。在项目开发过程中,人们会逐渐熟悉软件的不同部分,当然,他们也会有自己特别擅长的“领域”。然而,这些代码是否优秀且易于理解,则是另一个问题,而这本书正是试图解答这个问题。
基于重构工作,我们还可以利用这些经验来判断应用程序中哪些部分至关重要。希望我在这篇博文中已经解释清楚了这一点。
其他语言
代码复杂度工具主要针对 JavaScript 和 TypeScript 代码库。对于 Java、C#、Python 或 PHP 等其他语言,也有相应的工具,但Code-Maat是一款通用工具,适用于大多数代码库。它是由上一章提到的那本书的作者开发的。
这样,你也可以分析一个软件项目,并得出与博客文章中提到的相同的结论。
结论
希望这篇文章对您有所帮助,让您的生活更轻松一些。接手新的代码库并非易事,尤其是在瞬息万变的 JavaScript 世界中,更是难上加难。本文介绍的工具和流程或许能助您更轻松地融入新的代码库。欢迎与您的同事分享这篇文章,并向他们介绍您正在使用的技术。据我所知,大多数开发者并不了解代码变更和复杂度分析,而这篇文章或许对大家都很有帮助。所以,请分享出去吧!
文章来源:https://dev.to/igeligel/the-1-tip-to-familiarize-with-new-javascript-codebases-2m1

