Loading...
Loading...
Compare original and translation side by side
PrismaClientOctokitts-best-practices| Prefer | Over | Why |
|---|---|---|
| Data transformations | Mutations | Predictable, easier to reason about |
| Functions | Methods | No |
| Composition | Inheritance | Mix behaviors without coupling |
| Explicit | Implicit | State passed in, not hidden |
| Factories | Classes | Closure-encapsulated state, no |
PrismaClientOctokitts-best-practicesthisthis| 优先选择 | 而非 | 原因 |
|---|---|---|
| 数据转换 | 修改操作 | 可预测性更强,更易理解 |
| 函数 | 方法 | 无 |
| 组合模式 | 继承 | 无需耦合即可混合行为 |
| 显式实现 | 隐式实现 | 状态传入而非隐藏 |
| 工厂函数 | 类 | 通过闭包封装状态,无需 |
thisnew// Factory returning different implementations
function createLogger(env: 'dev' | 'prod') {
if (env === 'dev') {
return { log: (msg: string) => console.log(`[DEV] ${msg}`) }
}
return { log: (msg: string) => sendToLogService(msg) }
}thisthisreadonlyas constfunction processItems(items: readonly Item[]): readonly Item[] {
return items.filter((item) => item.active)
}
const STATUSES = ['pending', 'active', 'done'] as const
type Status = (typeof STATUSES)[number]thisnew// 根据环境返回不同实现的工厂函数
function createLogger(env: 'dev' | 'prod') {
if (env === 'dev') {
return { log: (msg: string) => console.log(`[DEV] ${msg}`) }
}
return { log: (msg: string) => sendToLogService(msg) }
}Result<T, E>interface Ok<T> {
readonly ok: true
readonly value: T
}
interface Err<E> {
readonly ok: false
readonly error: E
}
type Result<T, E = Error> = Ok<T> | Err<E>
const ok = <T>(value: T): Ok<T> => ({ ok: true, value })
const err = <E>(error: E): Err<E> => ({ ok: false, error })readonlyas constfunction processItems(items: readonly Item[]): readonly Item[] {
return items.filter((item) => item.active)
}
const STATUSES = ['pending', 'active', 'done'] as const
type Status = (typeof STATUSES)[number]| Use Result | Don't use Result |
|---|---|
| JSON parsing, validation | Truly exceptional errors (out-of-memory) |
| External API calls | Programming bugs (assertion failures) |
| File I/O, network | Internal invariants that should never fail |
| Business logic with known failure modes | Operations with no realistic failure |
Result<T, E>interface Ok<T> {
readonly ok: true
readonly value: T
}
interface Err<E> {
readonly ok: false
readonly error: E
}
type Result<T, E = Error> = Ok<T> | Err<E>
const ok = <T>(value: T): Ok<T> => ({ ok: true, value })
const err = <E>(error: E): Err<E> => ({ ok: false, error })async function attemptAsync<T, E = unknown>(fn: () => Promise<T>): Promise<Result<T, E>> {
try {
return ok(await fn())
} catch (error) {
return err(error as E)
}
}
const result = await attemptAsync(() => fetch('/api/users'))
if (!result.ok) return logger.error('fetch failed')
const response = result.value| 适用Result的场景 | 不适用Result的场景 |
|---|---|
| JSON解析、验证 | 真正异常的错误(如内存不足) |
| 外部API调用 | 编程错误(断言失败) |
| 文件I/O、网络操作 | 内部不变量(理论上永远不会失效) |
| 存在已知失败模式的业务逻辑 | 无实际失败可能的操作 |
Errorinterface ApiError {
type: 'network' | 'timeout' | 'unauthorized' | 'not_found' | 'server_error'
message: string
statusCode?: number
}
async function fetchUser(id: UserId): Promise<Result<User, ApiError>> {
// ...
}async function attemptAsync<T, E = unknown>(fn: () => Promise<T>): Promise<Result<T, E>> {
try {
return ok(await fn())
} catch (error) {
return err(error as E)
}
}
const result = await attemptAsync(() => fetch('/api/users'))
if (!result.ok) return logger.error('获取失败')
const response = result.value// pure
function calculateTotal(items: readonly Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0)
}
// impure — side effects
function calculateTotal(items: Item[]): number {
console.log('Calculating...') // side effect: I/O
analytics.track('total_calculated') // side effect: external state
return items.reduce((s, i) => s + i.price, 0)
}// pure business logic
function validateUser(user: User): Result<User, ValidationError> {
// ...
}
// side effects at the edge
async function handleUserCreate(user: User) {
const validation = validateUser(user) // pure
if (!validation.ok) {
logger.warn({ validation }, 'invalid user') // I/O at edge
return
}
await db.user.create(validation.value) // I/O at edge
}const normalize = (s: string) => s.trim().toLowerCase()
const validate = (s: string) => s.length > 0
const format = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
function processName(input: string): string | null {
const normalized = normalize(input)
if (!validate(normalized)) return null
return format(normalized)
}Errorinterface ApiError {
type: 'network' | 'timeout' | 'unauthorized' | 'not_found' | 'server_error'
message: string
statusCode?: number
}
async function fetchUser(id: UserId): Promise<Result<User, ApiError>> {
// ...
}// 纯函数
function calculateTotal(items: readonly Item[]): number {
return items.reduce((sum, item) => sum + item.price, 0)
}
// 非纯函数——存在副作用
function calculateTotal(items: Item[]): number {
console.log('Calculating...') // 副作用:I/O操作
analytics.track('total_calculated') // 副作用:修改外部状态
return items.reduce((s, i) => s + i.price, 0)
}// 纯业务逻辑
function validateUser(user: User): Result<User, ValidationError> {
// ...
}
// 副作用位于边缘层
async function handleUserCreate(user: User) {
const validation = validateUser(user) // 纯函数
if (!validation.ok) {
logger.warn({ validation }, '无效用户') // 边缘层I/O操作
return
}
await db.user.create(validation.value) // 边缘层I/O操作
}const normalize = (s: string) => s.trim().toLowerCase()
const validate = (s: string) => s.length > 0
const format = (s: string) => s.charAt(0).toUpperCase() + s.slice(1)
function processName(input: string): string | null {
const normalized = normalize(input)
if (!validate(normalized)) return null
return format(normalized)
}| Acceptable | Reason |
|---|---|
Wrapping external SDK ( | Existing API uses class form |
| Long-lived stateful resources (WebSocket handlers) | Lifecycle naturally maps to instance |
Framework requirements (React class components, custom | No alternative |
| Single instance whose constructor does meaningful setup | The class form is genuinely clearer |
class Counter {
count = 0
increment() {
this.count++
}
decrement() {
this.count--
}
getValue() {
return this.count
}
}interface Counter {
increment: () => number
decrement: () => number
getValue: () => number
}
export function createCounter(initial: number = 0): Counter {
let value = initial
return {
increment: () => ++value,
decrement: () => --value,
getValue: () => value,
}
}thisnewfunction parseConfig(json: string): Config {
if (!json) throw new Error('Empty input')
return JSON.parse(json) // can also throw SyntaxError
}interface ConfigError {
type: 'empty_input' | 'invalid_json'
message: string
}
function parseConfig(json: string): Result<Config, ConfigError> {
if (!json) return err({ type: 'empty_input', message: 'Empty input' })
try {
return ok(JSON.parse(json))
} catch (e) {
return err({ type: 'invalid_json', message: (e as Error).message })
}
}
// caller now must handle both branches at compile time
const result = parseConfig(input)
if (!result.ok) {
return match(result.error)
.with({ type: 'empty_input' }, () => respondWithError(400, 'Empty body'))
.with({ type: 'invalid_json' }, () => respondWithError(400, 'Bad JSON'))
.exhaustive()
}
processConfig(result.value)| 可接受的类使用场景 | 原因 |
|---|---|
封装外部SDK( | 现有API采用类形式 |
| 长期存在的有状态资源(WebSocket处理器) | 生命周期自然映射到实例 |
框架要求(React类组件、自定义 | 无替代方案 |
| 构造函数执行有意义初始化的单例实例 | 类形式确实更清晰 |
| Skipped rule | Verbatim excuse | Why it's wrong |
|---|---|---|
| Replace the class with a factory | "the class works fine and refactoring feels risky — I'll just touch it as little as possible" | The "small change" is exactly when discipline pays off; risk compounds across the next ten changes. The factory is mechanically safe (interface + closure + return), and |
Convert | "Result is ceremony for a 2-line function — try/catch at the caller is fine" | Caller "fine" decays the moment one caller forgets the try/catch. Result puts failure modes into the type signature so the compiler enforces handling. The ceremony is one wrapper. |
Stop mutating | "we own this array, no one else holds a reference — mutation is faster" | Mutations leak through closures, async boundaries, and React renders. "We own it" is true today and false next refactor. Spread/map/filter are O(n) — the same as the loop you just wrote. |
Use | "we've always thrown, the codebase is consistent — switching one function makes it inconsistent" | The codebase is consistently buggy — that is what the rule fixes. Pick a boundary (this module, this PR), apply it consistently inside that boundary, and migrate outward. |
| Pure functions + side effects at the edge | "logging inside the calc is convenient and only one line — pulling it out adds plumbing" | "One line" of side effect makes the function untestable without mocks and unreusable in a different runtime (worker, batch job). Lift the log to the caller; the function stays pure. |
class Counter {
count = 0
increment() {
this.count++
}
decrement() {
this.count--
}
getValue() {
return this.count
}
}interface Counter {
increment: () => number
decrement: () => number
getValue: () => number
}
export function createCounter(initial: number = 0): Counter {
let value = initial
return {
increment: () => ++value,
decrement: () => --value,
getValue: () => value,
}
}thisnewfunction parseConfig(json: string): Config {
if (!json) throw new Error('Empty input')
return JSON.parse(json) // 也可能抛出SyntaxError
}interface ConfigError {
type: 'empty_input' | 'invalid_json'
message: string
}
function parseConfig(json: string): Result<Config, ConfigError> {
if (!json) return err({ type: 'empty_input', message: 'Empty input' })
try {
return ok(JSON.parse(json))
} catch (e) {
return err({ type: 'invalid_json', message: (e as Error).message })
}
}
// 调用方现在必须在编译时处理两个分支
const result = parseConfig(input)
if (!result.ok) {
return match(result.error)
.with({ type: 'empty_input' }, () => respondWithError(400, 'Empty body'))
.with({ type: 'invalid_json' }, () => respondWithError(400, 'Bad JSON'))
.exhaustive()
}
processConfig(result.value)ResultReadonlyDeep| 被跳过的规则 | 原话借口 | 错误原因 |
|---|---|---|
| 用工厂函数替代类 | "这个类运行正常,重构有风险——我尽量少改动" | "小改动"正是纪律发挥作用的时候;风险会在接下来的十次改动中累积。工厂函数在机械层面是安全的(接口+闭包+返回),而类容易引发 |
将 | "对于两行函数来说,Result是繁琐的仪式——调用方用try/catch就够了" | 调用方的"够用"会在某个调用方忘记添加try/catch时失效。Result将失败模式纳入类型签名,让编译器强制处理错误。所谓的仪式只是一层包装。 |
停止使用 | "这个数组是我们自己的,没有其他引用——修改更快" | 修改操作会通过闭包、异步边界和React渲染泄漏出去。"我们自己持有"今天成立,下次重构就不成立了。扩展运算符/spread、map/filter的时间复杂度是O(n)——和你刚写的循环一样。 |
解析/验证/I/O操作使用 | "我们一直用抛出异常,代码库保持一致——修改一个函数会破坏一致性" | 代码库是一致的,但也是充满bug的——这正是规则要修复的问题。选择一个边界(如当前模块、当前PR),在边界内一致应用规则,然后向外迁移。 |
| 纯函数+副作用隔离在边缘层 | "在计算函数里加日志很方便,只有一行——把它移出去会增加代码复杂度" | "一行"副作用会让函数无法在不使用mock的情况下测试,也无法在不同运行时(如worker、批处理任务)中复用。将日志移到调用方,函数保持纯函数。 |
ReadonlyDeep