
1. 从“t3code”这个标题说起它到底是什么第一次看到“t3code”这个词我脑子里蹦出来的第一反应是——这大概率是一个技术项目代号而且带着明显的版本或序列意味。“t3”这种命名方式在开发圈子里太常见了要么是某个框架的第三代迭代要么是某个工具链的第三个核心模块再要么就是某个团队内部约定的项目编号。而“code”这个词就更直白了它指向的是代码本身或者是与编码、代码生成、代码管理相关的一整套东西。我之所以对这个标题感兴趣是因为它足够简短、足够模糊反而给了很大的拆解空间。一个标题越短背后能承载的东西往往越多。如果它叫“t3code在线代码格式化工具”那反而没什么好聊的了功能一目了然。但偏偏它就叫“t3code”干干净净四个字符加两个数字这就意味着它可能是一个品牌名、一个项目代号、一个开源仓库名甚至是一个内部工具的外号。从实际经验出发我判断“t3code”最可能指向以下几个方向中的一个第一它是一个代码生成工具用模板加配置的方式批量产出项目骨架或业务代码第二它是一个代码质量分析工具对代码做静态扫描、规范检查、复杂度评估第三它是一个轻量级的在线编码环境或者代码片段管理工具第四它是某个技术栈的第三代脚手架比如“TypeScript 3 Code”之类的组合缩写。不管是哪一种核心都绕不开“代码”这个关键词而“t3”则暗示了某种版本、层级或者技术组合。这篇文章我想做的事情很明确把这个标题背后可能涉及的核心领域、技术要点、实操路径和踩坑经验完完整整地拆一遍。不管你是刚入行的新手还是已经带过几个项目的老手只要你对“t3code”这个方向有兴趣或者你手头正好有一个类似命名的项目要落地这里的内容都能直接拿来参考。我会尽量把每个技术选择背后的“为什么”讲清楚把每个操作步骤的“怎么做”写明白再把我自己踩过的坑和总结的技巧一并倒出来。2. 核心领域定位与需求拆解2.1 为什么“t3code”大概率落在代码工具赛道要理解一个标题先要看它的构词。“t3code”由两部分组成“t3”和“code”。在技术命名习惯里“code”作为后缀出现时通常指向三类东西代码编辑器/IDE、代码生成器、代码分析器。这三类工具的共同点是都直接跟源代码打交道区别在于介入的时机不同。编辑器是在你写代码的时候介入生成器是在你还没写代码的时候介入分析器是在你写完代码之后介入。“t3”这个前缀则提供了额外的信息。如果它是一个版本号那说明这个项目已经迭代到第三代了前两代可能叫“t1code”和“t2code”或者干脆就是“tcode”和“t2code”。如果它是一个技术组合缩写那“t3”可能代表三种技术的首字母比如TypeScript、Tailwind、Tauri之类的组合。如果它是一个内部编号那“t3”可能只是团队内部的任务编号没有太多技术含义。我个人的判断偏向第一种和第二种的结合这很可能是一个已经迭代到第三版的代码工具项目而且它的技术栈里大概率包含TypeScript因为“t3”在近年来的技术社区里经常被用来指代“TypeScript 某三个东西”的组合。当然这只是基于命名习惯的合理推测具体是什么还得看项目本身的定位。2.2 潜在需求谁需要“t3code”用来解决什么问题假设“t3code”是一个代码生成或代码管理工具那它的目标用户就很清晰了需要频繁创建新项目、新模块、新组件的开发团队。这类团队通常面临几个痛点第一每次新建项目都要重复配置一堆东西从目录结构到构建脚本到代码规范费时费力还容易漏第二不同人创建的项目结构不一致导致后续维护成本高第三业务代码里有大量重复的CRUD逻辑手写太慢还容易出错。如果“t3code”是一个代码分析工具那它的目标用户就是需要保证代码质量的团队。这类团队的痛点也很明确代码规范靠人盯盯不过来代码复杂度靠感觉判断不准潜在bug靠测试覆盖不全。他们需要一个工具来自动化地扫描代码、发现问题、给出改进建议。如果“t3code”是一个在线编码环境那它的目标用户就是需要快速验证想法、分享代码片段、协作调试的开发者。这类场景下本地环境配置太慢截图分享太low直接发代码文件又不够直观一个轻量级的在线环境就能解决这些问题。不管是哪一种核心需求都可以归结为一句话让开发者少做重复劳动把精力集中在真正有创造性的工作上。这也是所有开发工具的共同使命。2.3 技术选型的底层逻辑一个代码工具的技术选型通常围绕几个核心维度展开性能、可扩展性、生态兼容性、上手成本。性能决定了工具能不能处理大型项目可扩展性决定了工具能不能适应不同团队的需求生态兼容性决定了工具能不能跟现有的开发流程无缝衔接上手成本决定了工具能不能被团队快速接受。以代码生成器为例如果选择基于模板引擎的方案那模板语言的选择就很关键。用Handlebars还是EJS还是Pug直接影响到模板的可读性和可维护性。如果选择基于AST的方案那解析器的选择就更关键了用Babel还是TypeScript Compiler API还是SWC直接影响到生成代码的准确性和性能。再以代码分析器为例如果选择基于正则的方案那实现简单但误报率高如果选择基于AST的方案那准确率高但实现复杂如果选择基于类型系统的方案那准确率最高但依赖类型标注的完整性。每种方案都有取舍关键看工具的目标场景是什么。3. 核心细节解析与实操要点3.1 代码生成的核心机制模板、AST还是混合代码生成这件事说起来简单做起来坑很多。最朴素的做法是字符串拼接把变量往模板字符串里塞。这种做法上手最快但维护起来最痛苦因为一旦模板变复杂字符串拼接就会变成一场灾难。稍微进阶一点的做法是用模板引擎把模板和逻辑分离模板负责结构逻辑负责数据。这种做法在大多数场景下够用但遇到需要根据现有代码结构生成新代码的场景就力不从心了。真正强大的做法是基于AST的代码生成。所谓AST就是抽象语法树简单理解就是把代码解析成一棵树形结构每个节点代表一个语法元素。基于AST生成代码的好处是你可以精确地操作代码的每一个部分插入、删除、修改都不会影响其他部分。而且生成的代码天然符合语法规范不会出现字符串拼接常见的括号不匹配、分号遗漏之类的问题。但AST方案也有代价。首先你需要一个解析器来把源代码变成AST再需要一个生成器来把AST变回源代码。这两个环节都有性能开销对于大型项目来说可能成为瓶颈。其次AST的API通常比较复杂学习曲线陡峭新手很难快速上手。所以很多工具会选择混合方案用模板引擎处理全新的代码生成用AST处理现有代码的修改。实操心得如果你要做一个代码生成工具先想清楚你的目标场景是“从零生成”还是“增量修改”。前者优先考虑模板引擎后者优先考虑AST。不要一上来就追求大而全先把一个场景做透。3.2 代码分析的关键指标复杂度、重复率、规范符合度代码分析工具的核心价值在于发现问题。但问题分很多种有些是明确的bug有些是潜在的坏味道有些只是风格不一致。一个实用的代码分析工具通常需要关注几个关键指标。第一个指标是圈复杂度。圈复杂度衡量的是一个函数的逻辑分支数量分支越多复杂度越高测试和维护的难度就越大。一般来说圈复杂度超过10的函数就值得警惕了超过20就建议重构。计算圈复杂度的方法很简单从1开始每遇到一个if、else if、for、while、case、catch、、||就加1。这个数字虽然粗糙但非常直观。第二个指标是代码重复率。重复代码是维护的噩梦改一处漏一处的问题几乎都源于重复。检测重复代码的常用方法是基于token的序列匹配把代码拆成token序列然后找最长公共子序列。这种方法实现简单但对格式变化敏感。更先进的方法是基AST的子树匹配能忽略格式差异但实现复杂度更高。第三个指标是规范符合度。这包括命名规范、注释规范、文件组织规范等等。这类检查通常用lint工具来完成比如ESLint、Pylint、RuboCop。lint工具的好处是规则可配置团队可以根据自己的习惯定制规则集。指标检测方法推荐阈值处理建议圈复杂度统计分支关键字单函数≤10超过则拆分函数代码重复率token序列匹配单文件≤5%超过则提取公共逻辑规范符合度lint规则扫描零errorerror必须修复warning酌情3.3 工具链集成的三个关键接口一个代码工具再好如果集成不到现有的开发流程里也很难被团队接受。集成这件事核心是三个接口命令行接口、配置文件接口、编辑器接口。命令行接口是最基础的它决定了工具能不能在CI/CD流水线里跑。设计命令行接口时要注意几点第一退出码要规范成功返回0失败返回非0这样CI系统才能正确判断第二输出格式要可解析最好支持JSON格式方便其他工具消费第三参数设计要直观不要搞一堆缩写让人猜。配置文件接口决定了工具能不能被定制。配置文件的位置要符合社区惯例比如放在项目根目录下命名为.t3coderc或t3code.config.js。配置项的命名要自解释不要用opt1、flag2这种无意义的名称。配置的默认值要合理让用户不配置也能跑起来。编辑器接口决定了开发者的日常体验。最常见的集成方式是VS Code插件通过Language Server Protocol跟编辑器通信。这种方式的好处是跨编辑器兼容不仅VS Code能用其他支持LSP的编辑器也能用。实现LSP需要处理几个关键消息初始化、文档打开、文档变更、代码补全、代码诊断。注意事项命令行接口的输出不要用彩色字符因为CI日志通常不支持ANSI转义码彩色字符会变成乱码。如果确实需要彩色输出加一个--no-color参数让用户关闭。4. 实操过程与核心环节实现4.1 环境准备与项目初始化假设我们要从零搭建一个类似“t3code”的代码工具第一步是环境准备。我推荐的技术栈是Node.js TypeScript原因是生态成熟、上手快、跟前端工具链兼容性好。Node.js版本建议用18 LTS或以上TypeScript版本建议用5.0以上。初始化项目的命令很简单mkdir t3code cd t3code npm init -y npm install typescript types/node --save-dev npx tsc --inittsc --init会生成一个tsconfig.json需要修改几个关键配置。target设为ES2022module设为NodeNextmoduleResolution设为NodeNextoutDir设为distrootDir设为srcstrict设为true。这些配置的目的是让TypeScript编译出符合现代Node.js规范的代码同时保持严格的类型检查。接下来安装核心依赖。如果做代码生成需要模板引擎推荐Handlebars因为它的语法简洁、逻辑分离好。如果做代码分析需要解析器推荐typescript-eslint/typescript-estree因为它能解析TypeScript代码并生成ESTree格式的AST跟ESLint生态兼容。如果做命令行工具需要参数解析库推荐Commander.js因为它API直观、文档完善。npm install handlebars commander npm install typescript-eslint/typescript-estree --save-dev目录结构建议这样组织t3code/ ├── src/ │ ├── cli.ts # 命令行入口 │ ├── generator/ # 代码生成模块 │ ├── analyzer/ # 代码分析模块 │ ├── config/ # 配置加载模块 │ └── utils/ # 通用工具 ├── templates/ # 代码模板 ├── tests/ # 测试用例 ├── package.json └── tsconfig.json这个结构的核心思想是按职责分层cli.ts只负责参数解析和命令分发具体逻辑放在各个模块里。这样做的好处是每个模块可以独立测试也方便后续扩展新功能。4.2 代码生成模块的实现细节代码生成模块的核心是一个函数输入是模板名称和数据对象输出是生成的代码字符串。实现这个函数需要三步加载模板、编译模板、渲染模板。加载模板时要注意路径问题。模板文件通常放在templates目录下但编译后的代码在dist目录下所以需要用path.resolve(__dirname, ../templates)来定位模板目录。如果模板支持用户自定义还需要支持从项目根目录加载模板优先级是用户模板 内置模板。编译模板用Handlebars的compile方法编译结果可以缓存起来避免每次渲染都重新编译。缓存用Map实现key是模板路径value是编译后的函数。缓存的好处是性能提升明显特别是在批量生成代码的场景下。渲染模板时要注意数据对象的构造。数据对象通常来自两个地方命令行参数和配置文件。命令行参数的优先级高于配置文件这样用户可以临时覆盖配置。数据对象的字段命名要跟模板里的变量名一致否则渲染出来就是空字符串。import Handlebars from handlebars; import fs from fs/promises; import path from path; const templateCache new Mapstring, HandlebarsTemplateDelegate(); export async function generateCode( templateName: string, data: Recordstring, unknown ): Promisestring { const templatePath path.resolve(__dirname, ../templates, ${templateName}.hbs); let template templateCache.get(templatePath); if (!template) { const source await fs.readFile(templatePath, utf-8); template Handlebars.compile(source); templateCache.set(templatePath, template); } return template(data); }模板文件本身也有讲究。一个好的模板应该只包含结构不包含逻辑。所谓结构就是代码的骨架比如类名、方法名、参数列表。所谓逻辑就是条件判断、循环、数据转换。Handlebars本身支持if和each但建议尽量少用因为模板里的逻辑越多越难维护。更好的做法是在数据准备阶段就把逻辑处理完模板只负责渲染。实操心得模板文件的命名建议用kebab-case比如react-component.hbs、express-route.hbs。这样在命令行里输入模板名时不用切换大小写体验更好。4.3 代码分析模块的实现细节代码分析模块的核心也是一个函数输入是文件路径或目录路径输出是问题列表。实现这个函数需要四步读取文件、解析AST、遍历节点、收集问题。读取文件时要注意编码问题。大多数代码文件是UTF-8编码但也有例外比如某些Windows环境下生成的文件可能是GBK编码。稳妥的做法是先按UTF-8读取如果发现乱码再尝试其他编码。不过在实际项目中统一要求UTF-8是最省事的方案。解析AST用typescript-eslint/typescript-estree的parse方法。这个方法接受源代码字符串和配置对象返回ESTree格式的AST。配置对象里最重要的是loc和range前者记录节点的行列位置后者记录节点的字符偏移量。这两个信息在报告问题时都要用到loc用于人类阅读range用于程序处理。import { parse } from typescript-eslint/typescript-estree; export function analyzeCode(source: string, filePath: string) { const ast parse(source, { loc: true, range: true, comment: true, jsx: filePath.endsWith(.tsx), }); const issues: Issue[] []; traverse(ast, { FunctionDeclaration(node) { const complexity calculateComplexity(node); if (complexity 10) { issues.push({ file: filePath, line: node.loc.start.line, message: 函数 ${node.id?.name} 的圈复杂度为 ${complexity}建议拆分, severity: warning, }); } }, }); return issues; }遍历AST需要一个遍历器。最简单的做法是递归遍历但递归深度太大时可能栈溢出。更稳妥的做法是用显式的栈来模拟递归或者用现成的遍历库比如estree-walker。遍历时要注意节点类型的判断ESTree定义了上百种节点类型但常用的也就几十种不需要全部处理。计算圈复杂度时要注意不同节点类型的权重。IfStatement、ForStatement、WhileStatement、SwitchCase、CatchClause各加1LogicalExpression里的和||也各加1。但ConditionalExpression三元运算符要不要加不同工具有不同做法。我倾向于加因为三元运算符本质上也是分支。4.4 命令行接口的设计与实现命令行接口是用户接触工具的第一站设计好坏直接影响用户体验。用Commander.js实现一个命令行工具核心是定义命令、选项和动作。import { Command } from commander; const program new Command(); program .name(t3code) .description(代码生成与分析工具) .version(1.0.0); program .command(generate) .description(生成代码) .argument(template, 模板名称) .option(-o, --output path, 输出路径) .option(-d, --data json, 数据对象JSON格式) .action(async (template, options) { const data options.data ? JSON.parse(options.data) : {}; const code await generateCode(template, data); if (options.output) { await fs.writeFile(options.output, code); console.log(已生成: ${options.output}); } else { console.log(code); } }); program .command(analyze) .description(分析代码) .argument(path, 文件或目录路径) .option(--json, 以JSON格式输出) .action(async (targetPath, options) { const issues await analyzePath(targetPath); if (options.json) { console.log(JSON.stringify(issues, null, 2)); } else { issues.forEach(issue { console.log(${issue.file}:${issue.line} [${issue.severity}] ${issue.message}); }); } }); program.parse();这段代码里有几个设计决策值得说明。第一generate命令的--data选项接受JSON字符串而不是文件路径。这样做的好处是简单直接适合少量数据。如果数据量大可以再加一个--data-file选项。第二analyze命令的--json选项用于机器消费默认输出用于人类阅读。第三所有命令的action都是异步函数因为文件操作和解析操作都是异步的。注意事项Commander.js默认会在命令执行失败时输出错误信息并退出退出码是1。如果你需要自定义错误处理可以在action里捕获异常然后调用process.exit(2)来区分不同类型的错误。5. 常见问题与排查技巧实录5.1 模板渲染出来是空字符串怎么办这是新手最常遇到的问题。模板渲染出来是空字符串通常有三个原因模板路径不对、数据字段不匹配、模板语法错误。排查路径不对的方法很简单在加载模板之前打印一下路径看看文件是否真的存在。如果路径里有..要注意__dirname的值在开发环境和生产环境可能不同。开发环境下__dirname是src目录生产环境下是dist目录所以相对路径的基准不一样。稳妥的做法是用path.resolve配合process.cwd()来定位项目根目录再从根目录出发找模板。排查数据字段不匹配的方法是在渲染之前打印数据对象看看字段名是否跟模板里的变量名一致。Handlebars对大小写敏感{{name}}和{{Name}}是两个不同的变量。另外如果数据字段是undefined或nullHandlebars会渲染成空字符串不会报错。所以如果模板里某个变量没渲染出来先检查数据对象里有没有这个字段。排查模板语法错误的方法是先用一个最简单的模板测试比如只包含{{name}}如果这个能渲染出来再逐步增加复杂度。Handlebars的语法错误通常会在编译阶段抛出所以如果编译没报错但渲染结果不对大概率是数据问题而不是语法问题。5.2 AST解析报错怎么定位AST解析报错通常是因为源代码里有语法错误或者解析器配置不支持某些语法特性。定位这类问题的第一步是看错误信息里的位置信息通常包含行号和列号直接跳到对应位置看看代码有什么问题。如果代码本身没问题但解析器报错那可能是解析器配置的问题。比如解析TypeScript代码时如果没设置jsx: true遇到.tsx文件里的JSX语法就会报错。再比如解析ES2022代码时如果解析器版本太老遇到顶层await就会报错。解决办法是升级解析器版本或者调整解析器配置。还有一种情况是代码里用了实验性语法比如装饰器、管道操作符之类的。这类语法通常需要额外的插件或配置才能解析。稳妥的做法是在项目里统一语法标准避免使用实验性语法或者在解析器配置里显式开启对应的插件。问题现象可能原因排查方法解决方案解析报错位置在JSX处未开启jsx配置检查文件扩展名和配置设置jsx: true解析报错位置在装饰器处不支持装饰器语法查看解析器版本文档升级版本或开启插件解析报错位置在顶层await解析器版本过老检查解析器版本升级到支持ES2022的版本解析成功但AST不完整解析器配置缺失对比AST节点数量补全loc、range等配置5.3 性能瓶颈在哪里怎么优化代码工具的性能瓶颈通常出现在两个地方文件IO和AST解析。文件IO的优化空间不大主要是用异步API代替同步API用流式读取代替一次性读取。AST解析的优化空间就大多了因为解析是整个流程里最耗时的环节。优化AST解析性能的第一个方法是缓存。如果同一个文件被多次分析可以把解析结果缓存起来key是文件路径加文件修改时间value是AST。这样文件没变时直接命中缓存省去解析开销。缓存的失效策略很简单文件修改时间变了就重新解析。第二个方法是增量解析。如果只修改了文件的一小部分没必要重新解析整个文件。增量解析的实现比较复杂需要跟踪AST节点和源代码位置的映射关系但效果很显著。不过对于大多数项目来说全量解析加缓存已经够用了增量解析属于锦上添花。第三个方法是并行解析。Node.js虽然是单线程的但可以用Worker Threads来实现并行。把文件列表分成几组每组交给一个Worker去解析最后合并结果。并行的数量建议设为CPU核心数太多反而会因为上下文切换而降低性能。实操心得在优化性能之前先用console.time和console.timeEnd测量一下各个阶段的耗时找到真正的瓶颈再动手。我见过太多人凭感觉优化结果优化了不耗时的部分真正的瓶颈一点没动。5.4 配置文件加载的优先级陷阱配置文件加载看起来简单实际上有很多陷阱。最常见的陷阱是优先级混乱命令行参数、环境变量、项目配置文件、用户配置文件、默认配置这五者的优先级如果没定义清楚就会出现“我明明改了配置怎么不生效”的问题。我推荐的优先级从高到低是命令行参数 环境变量 项目配置文件 用户配置文件 默认配置。这个顺序的逻辑是越靠近当前操作的配置优先级越高越通用的配置优先级越低。命令行参数是当前操作最直接的表达所以优先级最高默认配置是兜底的所以优先级最低。实现这个优先级的方法是用一个配置合并函数从低优先级往高优先级依次合并。合并时要注意深合并和浅合并的区别。对于嵌套对象通常用深合并对于数组通常用替换而不是合并。比如ignore字段是一个数组用户配置了[node_modules]项目配置了[dist]合并结果应该是[dist]而不是[node_modules, dist]因为用户配置应该覆盖项目配置。function mergeConfig(...configs: PartialConfig[]): Config { return configs.reduce((acc, config) { for (const key of Object.keys(config)) { const value config[key]; if (Array.isArray(value)) { acc[key] value; } else if (typeof value object value ! null) { acc[key] { ...acc[key], ...value }; } else { acc[key] value; } } return acc; }, {} as Config); }这个合并函数的逻辑是数组直接替换对象浅合并基本类型直接覆盖。对于大多数配置场景来说这个逻辑够用了。如果遇到更复杂的合并需求可以引入lodash.merge之类的库但要注意它的合并行为可能跟你的预期不一致。6. 扩展方向与个人经验分享6.1 从工具到平台可能的演进路径一个代码工具做出来之后下一步往哪走是很多开发者会思考的问题。我的经验是不要急着做大而全的平台先把一个场景做深做透。工具的价值在于解决问题不在于功能多少。如果“t3code”是一个代码生成工具那它的演进路径可能是从命令行工具到编辑器插件从编辑器插件到Web服务从Web服务到团队协作平台。每一步演进都要解决一个新的问题编辑器插件解决的是“不用切窗口”的问题Web服务解决的是“不用装环境”的问题团队协作平台解决的是“多人共享模板”的问题。如果“t3code”是一个代码分析工具那它的演进路径可能是从单文件分析到项目分析从项目分析到历史趋势分析从历史趋势分析到质量门禁。每一步演进都要增加一个新的维度项目分析增加的是“跨文件依赖”的维度历史趋势分析增加的是“时间”的维度质量门禁增加的是“流程”的维度。不管往哪个方向演进核心原则是不变的先解决自己的问题再解决团队的问题最后解决社区的问题。如果自己都不用那做出来的东西大概率也没别人用。6.2 我踩过的三个坑第一个坑是过度设计。刚开始做代码生成工具的时候我想把所有可能的场景都覆盖到结果模板系统做得极其复杂支持条件、循环、嵌套、继承、自定义helper最后连我自己都记不住怎么用。后来砍掉了80%的功能只保留最核心的变量替换和简单的条件判断反而用的人多了。这个教训让我明白工具的功能不是越多越好而是越刚好越好。第二个坑是忽略错误处理。早期版本的代码分析工具遇到解析不了的文件直接崩溃整个分析流程中断。用户反馈说“我就想看看有多少问题结果一个文件报错就啥也看不到了”。后来改成每个文件独立处理解析失败就跳过并记录最后汇总时把失败的文件单独列出来。这个改动不大但用户体验提升明显。第三个坑是文档滞后。功能改了但文档没改用户按文档操作发现对不上就来提issue。后来我养成了一个习惯改代码的同时改文档把文档当成代码的一部分来维护。如果时间紧至少把变更点记在CHANGELOG里让用户知道哪里变了。6.3 给后来者的几点建议如果你正准备做一个类似“t3code”的项目我有几个建议。第一先想清楚目标用户是谁他们的核心痛点是什么你的工具怎么解决这个痛点。不要为了做工具而做工具工具是解决问题的手段不是目的。第二技术选型不要追新。用你最熟悉的技术栈用社区最成熟的库用文档最完善的方案。新技术虽然酷但踩坑的成本太高除非你的项目本身就是技术探索性质的。第三尽早让真实用户试用。自己用和给别人用是两回事自己用的时候会不自觉地绕过问题别人用的时候问题会直接暴露出来。找一个愿意给你反馈的同事或朋友让他们在真实场景里用你的工具然后根据反馈迭代。第四保持简单。代码工具的核心价值是提高效率如果工具本身的使用成本太高那就本末倒置了。命令行参数不要超过五个配置文件不要超过十个字段输出信息不要超过三行。简单的东西才容易被记住被记住的东西才容易被使用。最后再分享一个小技巧给你的工具加一个--dry-run选项。这个选项的作用是只显示将要做什么但不实际执行。对于代码生成工具来说--dry-run会显示将要生成哪些文件对于代码分析工具来说--dry-run会显示将要检查哪些文件。这个选项在调试和演示时特别有用用户可以先看看效果再决定要不要真的执行。实现起来也简单就是在执行动作之前加一个判断如果dryRun为真就只打印不执行。