一言以蔽之
GraphQL 格式化是门艺术,它将一致的样式规则应用于查询、变更和模式,能把一团乱麻的括号和字段,变成清晰易读、便于维护的杰作。
它解决了什么问题
想当年,如果你想为自己酷炫的新网页应用从服务器获取数据,你很可能会用 REST API。你会请求像 /users/123 这样的端点(endpoint)来获取用户数据,请求 /users/123/posts 来获取他们的帖子。问题在哪?你可能会得到远超你需要的数据(数据超取,即 over-fetching),或者你必须发起多次网络请求才能获取到所有你 真正 需要的数据(数据少取,即 under-fetching)。
就在这时,由 Facebook 开发的 API 查询语言 GraphQL 登场了。它彻底改变了游戏规则。不再由服务器决定发送什么数据,而是由客户端在单次请求中,精确地索取它所需要的一切。这就像是去餐厅单点,而不是吃店家配好的固定套餐。
# 只要用户 42 的名字和他前 3 篇文章的标题
query GetUserNameAndPosts {
user(id: "42") {
name
posts(first: 3) {
title
}
}
}
这是一场革命。但它也带来了一个新的、规模小点儿的问题。GraphQL 查询,由于其嵌套的花括号,可能会变得很复杂,甚至是非常复杂。如果没有规矩,一个开发者写的查询可能看起来就像一行长得没法读的文本。而另一个开发者可能会用完全不同的缩进风格写同一个查询。
当你在凌晨两点试图调试一个问题,或者一个新团队成员试图理解你 API 的结构时,这种缺乏一致性简直是一场噩梦。代码即沟通,而未格式化的 GraphQL 就像读一本没有段落、没有标点、字体也不统一的书。格式化则强加了一套共享的语法,让代码的意图对每个读它的人来说都一目了然。
底层工作原理
GraphQL 格式化工具可不只是在做一些花哨的查找替换。它是一个复杂的过程,涉及到理解代码的结构,应用一套规则,然后从头开始以一种优美、可预测的方式重建代码。
解析:从文本到树
首先,格式化工具必须读取原始的 GraphQL 代码字符串,并理解它到底是什么。它不能只是看到 { 就加个换行符。它需要知道这个括号是开启一个查询、一个类型定义,还是一个输入对象。
这个过程叫做解析(parsing)。格式化工具会对输入进行词法分析(tokenize),将其分解成有意义的块(如 query、user、(、id、:、"42"、)),然后构建一个抽象语法树(Abstract Syntax Tree, 简称 AST)。AST 是一个树状的数据结构,它代表了代码的语法结构。
对于一个简单的查询:
query { user { name } }
它的 AST 可能看起来像这样(用一种简化、概念性的方式表示):
- Document
- Definition (OperationDefinition, type: query)
- SelectionSet
- Selection (Field)
- name: "user"
- SelectionSet
- Selection (Field)
- name: "name"
文本不再仅仅是文本;它成了一个程序可以智能操作的结构化对象。
风格的规则
一旦格式化工具拿到了 AST,它就可以遍历这棵树并应用其风格规则。这些规则是格式化的核心,也常常是程序员们(大部分是毫无意义地)争论的话题。常见的规则包括:
- 缩进: 每个嵌套级别用多少个空格(或者如果你是异端,就用制表符)。几乎通用的标准是 2 个空格。
- 换行: 什么时候把东西放到新的一行。一个开括号
{应该和字段名在同一行还是新起一行?(大多数格式化工具会把它放在同一行。) - 空格: 确保冒号等操作符周围和括号内的空格一致。
- 字段排序: 对于大型的模式文件,一些格式化工具甚至可以按字母顺序对字段进行排序,以便更容易查找。
像 Prettier 这样的工具因其“有主见”(opinionated)而闻名——它们为你做出了这些选择,所以你就不必为这些事争论不休。目标不是找到那个“完美”的风格,而是选择 一个 风格,然后不折不扣地执行它。
格式化打印:从树回到文本
应用规则后,格式化工具的最后一项工作就是将修改后的 AST 再把它变回文本字符串。这个过程叫做格式化打印(pretty-printing)。格式化工具会遍历树,在每个节点(如 Field 或 SelectionSet)上,它会打印出相应的文本,并根据规则添加正确的缩进和换行符。
结果就是一段格式优美的 GraphQL 字符串。
一个相关的概念是压缩(minification)或紧凑化(compacting)。这与格式化打印正好相反。它同样会将代码解析成 AST,但在打印输出时会移除所有可选的空白字符。这就创建了一个单行的、紧凑的字符串,对人类来说不可读,但非常适合通过网络发送,因为它能节省几个宝贵的字节。
真实世界的案例
午夜调试事件
后端工程师 Jasmine 正在值班。凌晨 1:30,一条告警响起:一个关键的 GraphQL 变更(mutation)在生产环境中失败了。唯一的线索是一条日志,里面包含了客户端发送的确切查询——那是一行从压缩过的 JavaScript 包里复制粘贴出来的、长达 3000 个字符的天书。她盯着那堵文本墙,...customer{address{...,试图找到格式错误的部分。她看得两眼发直。沮丧之下,她把整个字符串扔进一个 GraphQL 格式化工具里。瞬间,查询变成了一个 70 行、缩进完美的结构。问题就在那里,在第 47 行清清楚楚地摆着:一个关键字段名的拼写错误,adress 写成了 address。修复本身微不足道,但在格式化之前,她根本 看 不到问题所在。
经验之谈: 可读性是可调试性的第一步,也是最重要的一步。格式化工具能把一坨无法理解的文本,变成人类可以解析的东西。
合并不了的 PR
一个小团队正在用 GraphQL 构建一个新的电商后端。两位开发者,Liam 和 Olivia,正在合作一个功能。Liam 把他的编辑器配置成了 4 空格缩进。而 Olivia 是 2 空格缩进的拥护者,她的设置不一样。当 Liam 提交他的 pull request 时,Olivia 审查了代码,做了一些逻辑上的修改,然后推送了她的提交。结果,“diff” 是一片红红绿绿。几乎每一行都被标记为已更改,仅仅因为他们的编辑器在为空格打架。那些真正有意义的改动完全淹没在噪音中。技术负责人不得不花了一个小时才把这烂摊子理清。第二天,他就在团队的提交前钩子(pre-commit hook)里加入了一个自动化的 GraphQL 格式化工具。现在,所有代码在被提交之前都会被格式化成完全相同的标准。
经验之谈: 自动化格式化可以终结代码风格之争,保持版本控制历史的干净,让代码审查专注于真正重要的事情:业务逻辑。
意大利面条式的模式
一家初创公司的 GraphQL 模式在三年里野蛮生长。类型被随意地加在任何地方,字段没有任何顺序,注释也零零散散。对于一个新员工来说,试图理解这个 API 的数据模型就像试图理清一抽屉旧电线。他们决定做一个实验:把整个 schema.graphql 文件喂给一个格式化工具。这个工具不仅正确地缩进了所有内容,还按字母顺序对每种类型中的所有字段进行了排序。突然之间,id 总是第一个字段,废弃的字段被归到了一起。整个结构一下子清晰起来。它不仅仅是变好看了;它现在成了一份有用的文档。
经验之谈: 一个格式良好的模式本身就是活文档。它揭示了你 API 的结构和意图,让每个人都更容易上手。
常见误区和陷阱
- 为风格争吵不休。 最大的陷阱就是浪费数小时争论用制表符还是空格,或者括号应该放在哪里。格式化的价值在于 一致性。选一个流行的、有主见的工具(比如 Prettier),大家同意使用,然后继续前进干正事。
- 提交前忘记格式化。 如果格式化是一个手动过程,总会有人忘记。这就会导致你本想避免的那些乱七八糟的 diff。把格式化集成到提交前钩子(pre-commit hook)中(使用 Husky 和 lint-staged 等工具),让它变得自动化且毫不费力。
- 混淆格式化与代码检查 (linting)。 格式化工具让你的代码看起来一致。而 linter(代码检查工具,如
eslint-plugin-graphql)则会检查你的代码中潜在的 bug 或不良实践,比如使用了废弃的字段或编写了低效的查询。你两者都需要。格式化工具负责打扫厨房;linter 负责检查你是不是忘了关火。 - 格式化生成的代码。 有些工作流会从其他来源(比如数据库模式或另一种编程语言)生成 GraphQL 模式文件或查询。格式化这些输出文件通常是浪费时间,因为你的修改在下次生成代码时就会被覆盖。你应该去格式化源头的代码。
- 在生产环境中发送格式化后的查询。 虽然优美的缩进在开发时很棒,但在网络上传输却是浪费字节。你的构建过程应该在查询从客户端应用发送到服务器之前,将其压缩(minify)。
为啥你应该关注它
当一个项目不止你一个人,或者当你的查询比单个嵌套字段更复杂时,你就应该开始考虑 GraphQL 格式化了。
它是专业软件开发的一个基础工具,恰好完美地适用于 GraphQL。这不仅仅是为了好看而“美化”东西。它关乎:
- 清晰性: 让代码更易于阅读和理解。
- 可维护性: 让代码更易于修改和调试。
- 协作性: 通过自动化风格选择来减少团队成员之间的摩擦。
如果你发现自己正对着日志文件里压缩过的 GraphQL 查询发呆,或者和队友争论缩进问题,那这就是一个信号:你的生活需要一个自动格式化工具了。
深入了解
- GraphQL Specification - GraphQL 语言本身的官方、权威来源。
- Prettier: GraphQL - 最流行的代码格式化工具对 GraphQL 支持的文档。
- Exploring GraphQL APIs with Abstract Syntax Trees - 来自 Apollo 的一篇很棒的博文,讲述了 AST 的强大功能。
- GraphQL on Wikipedia - 用于宏观了解其概览和历史。
- ESLint Plugin for GraphQL - 深入了解 linting,它是格式化的强大搭档。