mcpField
S
skimmable

skimmable

github.com/ckorhonen其他2439 次安装5 周前更新
代码仓库

关于 skimmable

用于改进代码变更的可读性,让评审者能快速理解,同时保持行为稳定;仅处理明显无效或与类型不一致的行为。重点是简化数据结构、明确状态并缩小差异,而非增加抽象。

主要功能

  1. 01删除无关差异
  2. 02简化数据结构与 API
  3. 03用判别联合表达明确状态
  4. 04在输入边界进行断言
  5. 05扁平化控制流程

适用场景

  1. 01在评审前理清拉取请求
  2. 02减少类型化代码路径中的分支
  3. 03用明确变体替代布尔模式标记

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ckorhonen/claude-skills --skill skimmable

安装后在 Claude Code 中输入/skimmable确认已加载。

详细介绍

易于快速审阅

像一位疲惫的审阅者必须快速看一遍就理解变更那样来审阅。

优先删除代码,而不是解释代码。

保持审阅范围精简:提升可读性、状态建模和差异清晰度;除非当前行为显然已失效、多余或与类型不一致,否则不要改变产品行为。

检查清单

积极应用以下规则:

  1. 让代码一眼就能看懂。
  2. 减少代码可能表示的状态数量。
  3. 减少参数数量。
  4. 除非调用方确实需要,否则移除可选参数。
  5. 当行为因模式而异时,用判别联合替代布尔标志和松散的选项对象。
  6. 穷尽处理所有已知变体。
  7. 遇到未知变体时立即失败。
  8. 在类型化核心中信任静态类型;不要为不可能的状态添加防御性分支。
  9. 在加载数据、解析输入或读取外部系统时,于边界处进行断言。
  10. 优先使用提前返回,避免嵌套条件判断。
  11. 保持函数职责内聚;不要拆分成许多微小的包装函数。
  12. 避免巧妙的辅助函数、间接调用和泛型抽象。
  13. 除非有确凿需求,否则移除覆盖项、逃生口和自定义扩展点。
  14. 移除无用路径、临时兼容层和无关改动。
  15. 倾向于减少代码行数和变动因素。

工作方法

按以下顺序进行:

1. 精简差异

移除无助于 PR 主要目标的改动。

删除:

  • 顺带进行的重命名
  • 顺手进行的重构
  • 没有行为价值的样式调整
  • 对已不存在的调用方的兼容代码
  • 未使用的辅助函数、类型、分支和注释

如果简化需要另行决定行为,就停下来明确指出,不要把这个决定悄悄混入可读性改进中。

2. 先简化数据形状

先简化类型,再调整控制流。

优先选择:

  • 必填字段,而非可选字段
  • 一种清晰的输入形状,而非多种灵活形式
  • 判别联合,而非布尔值或彼此松散关联的字段
  • 有名称的领域类型,而非泛型映射和字符串类型对象

逐一审视每个参数:

  • 它是必需的吗?
  • 能从其他值推导出来吗?
  • 两个参数能合并为一个领域对象吗?
  • 它只是为过于灵活的 API 设置的覆盖项吗?
3. 简化控制流

让正常路径简短清晰。

优先选择:

  • 提前返回
  • 直线式代码
  • 尽可能只保留一层缩进
  • 在使用处就近进行局部判断

避免:

  • 多层嵌套的 if
  • 为不可能的状态设置防御性的 else 分支
  • 对预期值使用 try/catch
  • 将隐式分支散布在辅助函数中
4. 明确报错无效状态

在边界处进行断言。

在以下情况下使用断言:

  • 解码外部数据
  • 读取文件、环境变量或请求载荷
  • 将无类型数据转换为有类型的领域值
  • 检查必须成立的不变量

越过边界后,编写简单代码并信任已断言的形状。

5. 穷尽处理变体

当代码依赖某个变体时,应明确该变体并处理所有情况。

优先使用对判别字段的 switch。

如果语言支持,以穷尽性检查或断言结束。

不要静默忽略未知值。

6. 合并不必要的抽象

当小型包装函数只会掩盖实际逻辑时,将其内联。

仅在以下情况下保留辅助函数:

  • 能消除真正的重复
  • 能表达领域概念
  • 能让调用方明显更清晰

不要仅仅为了显得整洁,就把一个易读的 20 行函数拆成 5 个 4 行的辅助函数。

首选模式

保持参数精简且严格

不良迹象:

  • 参数超过 3 个
  • 存在多个布尔值
  • 可选回调、可选覆盖项和可选配置同时出现
  • 传递对象上已有的值

优先选择:

  • 字段彼此相关时,使用一个必需对象
  • 存在多种模式时,使用一个判别联合
  • 行为确实不同时,使用不同的函数
优先使用判别联合类型,而不是布尔值

如果布尔值会改变行为,就直接对行为进行建模。

使用:

  • kind: "draft" | "published"
  • source: "cache" | "network"
  • status: "idle" | "loading" | "success" | "error"

避免:

  • isDraft
  • useCache
  • 在同一对象上同时使用 isLoading、error 和 data,除非状态机确实允许所有组合
优先使用断言,而不是防御性代码

在边界处断言一次。内部代码信任该断言。

避免重复进行这类检查:

  • if (!user) return
  • if (!user.id) return
  • if (typeof user.id !== "string") return

如果类型和边界验证已经能保证对象形状,就不必再做这些检查。

优先选择一种明确的路径

如果存在默认行为,就直接将其编码进去。

除非调用方确实需要,否则不要添加 sortOrder = "desc"、strategy = "auto"、separator = ","、shouldLog = false 之类的覆盖项。

每个选项都会增加状态数量。

评审提示

评审时使用以下提示:

  • 这个函数能否减少参数?
  • 这个对象能否去掉可选字段?
  • 这个分支能否改为提前返回?
  • 这两个布尔值能否合并为一个联合类型?
  • 这个辅助函数能否内联?
  • 这条错误处理路径能否改为在边界处断言?
  • 这段代码能否在失败时明确报错,而不是猜测?
  • 这个差异能否减少文件或代码行?
  • 如果评审者在 30 秒内快速浏览,是否能看出意图?

示例

减少参数并移除覆盖项

修改前:

function createReport(
  userId: string,
  includeDrafts = false,
  sortBy: "date" | "name" = "date",
  sortDirection: "asc" | "desc" = "desc",
  limit?: number,
  overrides?: { timezone?: string; now?: Date }
) {
  const now = overrides?.now ?? new Date()
  const timezone = overrides?.timezone ?? "UTC"

  return buildReport({
    userId,
    includeDrafts,
    sortBy,
    sortDirection,
    limit,
    now,
    timezone,
  })
}

修改后:

type ReportScope =
  | { kind: "published" }
  | { kind: "all" }

function createReport(userId: string, scope: ReportScope) {
  return buildReport({ userId, scope, now: new Date(), timezone: "UTC" })
}

优先选择更精简的 API。只有实际调用方有需求时才增加可配置性。

用判别联合类型替代布尔值

修改前:

type SaveState = {
  isSaving: boolean
  error?: string
  receipt?: string
}

修改后:

type SaveState =
  | { kind: "idle" }
  | { kind: "saving" }
  | { kind: "error"; message: string }
  | { kind: "saved"; receipt: string }

第二种写法可以避免不可能的组合。

穷尽处理各种变体

修改前:

function badgeColor(status: Status) {
  if (status === "success") return "green"
  if (status === "error") return "red"
  return "gray"
}

修改后:

function badgeColor(status: Status) {
  switch (status) {
    case "success":
      return "green"
    case "error":
      return "red"
    case "pending":
      return "gray"
    default:
      return assertNever(status)
  }
}

开发过程中遇到未知变体时,应明确报错。

在边界处断言,在内部信任类型

修改前:

async function loadAccount(accountId: string) {
  const raw = await db.accounts.find(accountId)

  if (!raw) {
    throw new Error("Account not found")
  }

  if (typeof raw.email !== "string") {
    throw new Error("Invalid account email")
  }

  return raw
}

function sendWelcomeEmail(account: any) {
  if (!account) return
  if (!account.email) return
  mailer.send(account.email)
}

修改后:

async function loadAccount(accountId: string): Promise<Account> {
  const raw = await db.accounts.find(accountId)
  assert(raw, "Account not found")
  assert(typeof raw.email === "string", "Invalid account email")
  return raw as Account
}

function sendWelcomeEmail(account: Account) {
  mailer.send(account.email)
}

只验证一次。保持类型化核心简单。

优先提前返回,而不是嵌套

修改前:

function visibleItems(items: Item[], user?: User) {
  if (user) {
    if (user.isAdmin) {
      return items
    } else {
      return items.filter((item) => !item.hidden)
    }
  } else {
    return []
  }
}

修改后:

function visibleItems(items: Item[], user?: User) {
  if (!user) return []
  if (user.isAdmin) return items
  return items.filter((item) => !item.hidden)
}

让代码结构更扁平。

不要把逻辑拆分成太多细小函数

修改前:

function checkout(cart: Cart) {
  validateCart(cart)
  const subtotal = calculateSubtotal(cart)
  const tax = calculateTax(subtotal)
  const total = calculateTotal(subtotal, tax)
  return finalizeCheckout(total)
}

function calculateTotal(subtotal: number, tax: number) {
  return subtotal + tax
}

修改后:

function checkout(cart: Cart) {
  assert(cart.items.length > 0, "Cart is empty")

  const subtotal = sum(cart.items.map((item) => item.price))
  const tax = subtotal * TAX_RATE
  const total = subtotal + tax

  return finalizeCheckout(total)
}

让简单的逻辑靠近其使用位置。

PR 检查关卡

创建或更新 PR 前,请确认:

  • 差异中只包含必要的更改。
  • 公共 API 更严格或更简单,而不是更可配置。
  • 模式都已明确建模。
  • 各种变体都已穷尽处理。
  • 边界处存在断言。
  • 核心逻辑信任类型,而不是反复检查所有内容。
  • 函数从上到下易于阅读,嵌套尽可能少。
  • 参数数量较少。
  • 仅在辅助函数能提升清晰度时才使用它们。
  • 最终版本更短、更直白,也更易于快速浏览。

如果需要在巧妙和明确之间做选择,就选明确。

如果需要在灵活和简单之间做选择,就选简单。

如果需要在增加代码和删除代码之间做选择,就优先删除代码。

评论

支持 Markdown