no-workarounds

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

No Workarounds

拒绝权宜之计

The Fundamental Law

基本原则

A WORKAROUND IS A LIE TOLD TO THE COMPILER.
It makes the symptom disappear while the disease spreads.
A workaround is any change that makes a problem stop manifesting without addressing why the problem exists. Workarounds are not fixes. They are deferred failures with compound interest.
Philosophical foundation: Read
references/philosophical-foundations.md
for the engineering principles behind this skill, drawn from Toyota's Jidoka, Fowler's Technical Debt Quadrant, Torvalds' "good taste," and the Broken Windows Theory.
A WORKAROUND IS A LIE TOLD TO THE COMPILER.
It makes the symptom disappear while the disease spreads.
权宜之计是指任何仅让问题表面消失,却未解决问题根源的修改。权宜之计并非真正的修复,而是带有复利的延迟失败。
哲学基础: 阅读
references/philosophical-foundations.md
了解该实践背后的工程原则,这些原则源自丰田的自働化(Jidoka)、Fowler的技术债务象限、Torvalds的“良好品味”以及破窗理论。

The Workaround Detection Gate

权宜之计检测机制

BEFORE writing or proposing ANY fix:

1. STATE the problem clearly
2. ASK: "Why does this problem exist?" (not "How do I make it stop?")
3. TRACE to root cause (use systematic-debugging skill)
4. ASK: "Does my proposed fix address the ROOT CAUSE?"
5. ASK: "Would this fix be necessary if the code were correct?"
6. ASK: "Am I silencing a signal or fixing a source?"

IF any answer reveals symptom-patching:
  STOP — Redesign the fix to address root cause

IF root cause is in external code or truly unfixable:
  Document why, add defensive validation, and mark with WORKAROUND comment
  (See "The Escape Valve" section below)
在编写或提出任何修复方案之前:

1. 清晰地陈述问题
2. 自问:“这个问题为什么会存在?”(而非“我如何让它消失?”)
3. 追踪到问题的根本原因(使用系统化调试方法)
4. 自问:“我提出的修复方案是否针对根本原因?”
5. 自问:“如果代码本身是正确的,这个修复是否还有必要?”
6. 自问:“我是在掩盖信号,还是在修复源头?”

如果任何问题的答案显示这是症状修复:
  停止——重新设计修复方案以解决根本原因

如果根本原因存在于外部代码中或确实无法修复:
  记录原因,添加防御性验证,并使用WORKAROUND注释标记
  (参见下方“例外机制”部分)

The Seven Categories of Workarounds

七类权宜之计

Category 1 — TYPE: Type System Evasion

类别1 — 类型规避:绕过类型系统

The signal being silenced: The type system is telling the code is wrong.
typescript
// WORKAROUND: Lying to the compiler
const value = response.data as UserProfile;
const config = {} as AppConfig;
function process(input: any) { ... }

// PROPER FIX: Make types truthful
const value: UserProfile | undefined = response.data;
if (!value) throw new Error("Missing user profile");

const config: AppConfig = { theme: "light", locale: "en" };

function process(input: UserProfile) { ... }
Gate function:
BEFORE using `as`, `any`, `unknown` cast, or `!` (non-null assertion):
  Ask: "Why doesn't the type match?"

  IF the data shape is genuinely unknown:
    Use runtime validation (Schema, Zod, or type guards)
  IF the type is wrong:
    Fix the type definition
  IF the API returns unexpected shape:
    Fix the API contract or add a validation layer
  NEVER use type assertions to bypass compiler errors
被掩盖的信号: 类型系统提示代码存在错误。
typescript
// WORKAROUND: Lying to the compiler
const value = response.data as UserProfile;
const config = {} as AppConfig;
function process(input: any) { ... }

// PROPER FIX: Make types truthful
const value: UserProfile | undefined = response.data;
if (!value) throw new Error("Missing user profile");

const config: AppConfig = { theme: "light", locale: "en" };

function process(input: UserProfile) { ... }
检测机制:
在使用`as`、`any`、`unknown`类型转换或`!`(非空断言)之前:
  自问:“为什么类型不匹配?”

  如果数据结构确实未知:
    使用运行时验证(Schema、Zod或类型守卫)
  如果类型定义错误:
    修正类型定义
  如果API返回的结构不符合预期:
    修正API契约或添加验证层
  绝不要使用类型断言来绕过编译器错误

Category 2 — LINT: Lint and Warning Suppression

类别2 — 规则抑制:关闭Lint和警告

The signal being silenced: Static analysis found a real problem.
typescript
// WORKAROUND: Shooting the messenger
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const result = fetchData();

// @ts-ignore
someFunction(wrongArgs);

// @ts-expect-error - TODO fix later
brokenCall();

// PROPER FIX: Fix what the linter found
fetchData(); // Remove unused binding

someFunction(correctArgs); // Fix the arguments
Gate function:
BEFORE adding eslint-disable, @ts-ignore, @ts-expect-error, or any suppression:
  Ask: "What rule is being violated and WHY?"

  IF the code genuinely violates the rule:
    Fix the code, not the linter
  IF the rule is wrong for this codebase:
    Disable the rule in config (globally), not inline
  IF it's a third-party type issue:
    File an issue, add a minimal typed wrapper
  NEVER suppress a warning without understanding it
被掩盖的信号: 静态分析发现了真实存在的问题。
typescript
// WORKAROUND: Shooting the messenger
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const result = fetchData();

// @ts-ignore
someFunction(wrongArgs);

// @ts-expect-error - TODO fix later
brokenCall();

// PROPER FIX: Fix what the linter found
fetchData(); // Remove unused binding

someFunction(correctArgs); // Fix the arguments
检测机制:
在添加eslint-disable、@ts-ignore、@ts-expect-error或任何抑制规则之前:
  自问:“违反了什么规则?原因是什么?”

  如果代码确实违反了规则:
    修正代码,而非禁用规则
  如果该规则不适用于此代码库:
    在配置文件中全局禁用规则,而非在代码行内禁用
  如果是第三方类型问题:
    提交问题报告,添加最小化的类型包装
  绝不要在未理解警告原因的情况下抑制警告

Category 3 — SWALLOW: Error Swallowing

类别3 — 错误吞吃:忽略异常

The signal being silenced: Something failed and the code pretends it didn't.
typescript
// WORKAROUND: Pretending errors don't exist
try {
  await saveData(payload);
} catch {
  // silently ignore
}

try {
  result = JSON.parse(input);
} catch {
  result = {}; // default to empty - hides corrupt data
}

// PROPER FIX: Handle errors meaningfully
try {
  await saveData(payload);
} catch (error) {
  logger.error("Failed to save data", { error, payload });
  throw new SaveError("Data save failed", { cause: error });
}

const parsed = Schema.decodeUnknownSync(PayloadSchema)(input);
// Throws descriptive error if input is invalid
Gate function:
BEFORE writing a catch block:
  Ask: "What specific errors can occur here?"
  Ask: "What should happen when each error occurs?"

  IF the answer is "ignore it":
    STOP — Ignoring errors hides bugs
  IF the answer is "log it":
    Log AND propagate or handle meaningfully
  IF the answer is "use a default":
    Ensure the default is SAFE and the failure is LOGGED
  NEVER write an empty catch block
  NEVER catch Exception/Error broadly without re-throwing specific types
被掩盖的信号: 发生了错误,但代码假装没有发生。
typescript
// WORKAROUND: Pretending errors don't exist
try {
  await saveData(payload);
} catch {
  // silently ignore
}

try {
  result = JSON.parse(input);
} catch {
  result = {}; // default to empty - hides corrupt data
}

// PROPER FIX: Handle errors meaningfully
try {
  await saveData(payload);
} catch (error) {
  logger.error("Failed to save data", { error, payload });
  throw new SaveError("Data save failed", { cause: error });
}

const parsed = Schema.decodeUnknownSync(PayloadSchema)(input);
// Throws descriptive error if input is invalid
检测机制:
在编写catch块之前:
  自问:“这里可能发生哪些具体错误?”
  自问:“每个错误发生时应该如何处理?”

  如果答案是“忽略它”:
    停止——忽略错误会隐藏Bug
  如果答案是“记录它”:
    记录错误并传播或进行有意义的处理
  如果答案是“使用默认值”:
    确保默认值是安全的,并且失败情况已被记录
  绝不要编写空的catch块
  绝不要宽泛地捕获Exception/Error而不重新抛出特定类型

Category 4 — TIMING: Timing and Lifecycle Hacks

类别4 — 时序修复:时序与生命周期临时方案

The signal being silenced: Code runs in the wrong order or at the wrong time.
typescript
// WORKAROUND: Racing against the clock
setTimeout(() => {
  element.focus();
}, 100);

await new Promise((resolve) => setTimeout(resolve, 500));
// "wait for state to settle"

await retry(() => checkCondition(), { times: 10, delay: 200 });
// retry loop hiding a race condition

// PROPER FIX: Fix the lifecycle
// Use framework-native lifecycle hooks
useEffect(() => {
  if (ref.current) ref.current.focus();
}, [isVisible]);

// Use proper event-driven coordination
await waitForEvent(emitter, "ready");

// Use condition-based polling (not blind retries)
await waitUntil(() => service.isReady(), {
  timeout: 5000,
  message: "Service failed to become ready",
});
Gate function:
BEFORE adding setTimeout, delay, sleep, or retry loops:
  Ask: "WHY is the timing wrong?"
  Ask: "What event signals that the system is ready?"

  IF there's an event or callback available:
    Use it instead of arbitrary delays
  IF the ordering is wrong:
    Fix the initialization order
  IF it's a test timing issue:
    Use condition-based waiting, never arbitrary sleeps
  NEVER use setTimeout(fn, 0) to "fix" rendering issues
  NEVER use arbitrary delays to "wait for things to settle"
被掩盖的信号: 代码执行顺序错误或时机不当。
typescript
// WORKAROUND: Racing against the clock
setTimeout(() => {
  element.focus();
}, 100);

await new Promise((resolve) => setTimeout(resolve, 500));
// "wait for state to settle"

await retry(() => checkCondition(), { times: 10, delay: 200 });
// retry loop hiding a race condition

// PROPER FIX: Fix the lifecycle
// Use framework-native lifecycle hooks
useEffect(() => {
  if (ref.current) ref.current.focus();
}, [isVisible]);

// Use proper event-driven coordination
await waitForEvent(emitter, "ready");

// Use condition-based polling (not blind retries)
await waitUntil(() => service.isReady(), {
  timeout: 5000,
  message: "Service failed to become ready",
});
检测机制:
在添加setTimeout、delay、sleep或重试循环之前:
  自问:“为什么时序会出错?”
  自问:“什么事件能表明系统已准备就绪?”

  如果有可用的事件或回调:
    使用事件或回调替代任意延迟
  如果执行顺序错误:
    修正初始化顺序
  如果是测试时序问题:
    使用基于条件的等待,绝不要使用任意延迟
  绝不要使用setTimeout(fn, 0)来“修复”渲染问题
  绝不要使用任意延迟来“等待状态稳定”

Category 5 — PATCH: Monkey Patching and Runtime Mutation

类别5 — 猴子补丁:运行时修改

The signal being silenced: The API doesn't do what the code needs.
typescript
// WORKAROUND: Mutating things you don't own
Array.prototype.customMethod = function () { ... };
Object.defineProperty(window, "fetch", { value: customFetch });
library.internals._privateMethod = replacement;

// PROPER FIX: Composition over mutation
function customOperation<T>(arr: T[]): T[] { ... }
const wrappedFetch = createFetchWrapper(window.fetch);
const adapter = new LibraryAdapter(library);
Gate function:
BEFORE modifying prototypes, globals, or third-party internals:
  Ask: "Does the library provide an extension point?"

  IF yes: Use the official extension mechanism
  IF no: Wrap with composition/adapter pattern
  IF the library is broken: File issue, fork, or find alternative
  NEVER modify objects the code doesn't own
被掩盖的信号: API无法满足代码需求。
typescript
// WORKAROUND: Mutating things you don't own
Array.prototype.customMethod = function () { ... };
Object.defineProperty(window, "fetch", { value: customFetch });
library.internals._privateMethod = replacement;

// PROPER FIX: Composition over mutation
function customOperation<T>(arr: T[]): T[] { ... }
const wrappedFetch = createFetchWrapper(window.fetch);
const adapter = new LibraryAdapter(library);
检测机制:
在修改原型、全局对象或第三方库内部代码之前:
  自问:“该库是否提供了扩展点?”

  如果是:使用官方扩展机制
  如果否:使用组合/适配器模式进行包装
  如果库存在缺陷:提交问题报告、分叉或寻找替代方案
  绝不要修改代码不所属的对象

Category 6 — SCATTER: Defensive Duplication

类别6 — 重复防御:冗余校验

The signal being silenced: The data is unreliable at its source.
typescript
// WORKAROUND: Checking everywhere because source is broken
function renderUser(user: User) {
  const name = user?.name ?? user?.displayName ?? "Unknown";
  const email = user?.email ?? user?.contacts?.email ?? "";
  const id = user?.id ?? user?.userId ?? user?._id ?? "";
  // ... 20 more optional chains
}

// PROPER FIX: Validate once at the boundary
const user = Schema.decodeUnknownSync(UserSchema)(rawData);
// user is now guaranteed to have correct shape

function renderUser(user: User) {
  // No defensive checks needed — schema validated at entry
  return `${user.name} (${user.email})`;
}
Gate function:
BEFORE adding optional chaining (?.) or nullish coalescing (??) deeply:
  Ask: "Why might this value be missing?"
  Ask: "Where does this data enter the system?"

  IF data is unvalidated at entry:
    Add validation at the boundary, remove defensive checks downstream
  IF the type allows undefined but shouldn't:
    Fix the type to be non-optional
  IF it's truly optional:
    Handle the None/undefined case explicitly at the nearest decision point
  NEVER scatter optional chains as a substitute for proper validation
被掩盖的信号: 数据源不可靠。
typescript
// WORKAROUND: Checking everywhere because source is broken
function renderUser(user: User) {
  const name = user?.name ?? user?.displayName ?? "Unknown";
  const email = user?.email ?? user?.contacts?.email ?? "";
  const id = user?.id ?? user?.userId ?? user?._id ?? "";
  // ... 20 more optional chains
}

// PROPER FIX: Validate once at the boundary
const user = Schema.decodeUnknownSync(UserSchema)(rawData);
// user is now guaranteed to have correct shape

function renderUser(user: User) {
  // No defensive checks needed — schema validated at entry
  return `${user.name} (${user.email})`;
}
检测机制:
在深层添加可选链(?.)或空值合并运算符(??)之前:
  自问:“为什么这个值可能缺失?”
  自问:“这些数据从哪里进入系统?”

  如果数据在进入系统时未经过验证:
    在边界处添加验证,移除下游的防御性校验
  如果类型允许undefined但实际上不应该:
    修正类型为非可选
  如果确实是可选值:
    在最近的决策点显式处理None/undefined情况
  绝不要用分散的可选链替代适当的验证

Category 7 — CLONE: Copy-Paste Adaptation

类别7 — 复制粘贴:代码复用的错误方式

The signal being silenced: The abstraction doesn't fit but the developer forces it.
typescript
// WORKAROUND: Copy and "adapt" (badly)
// Copied from UserService and changed 3 lines
function createProject(data: ProjectData) {
  // 200 lines, 95% identical to createUser
  // but with subtle bugs from incomplete adaptation
}

// PROPER FIX: Extract shared pattern or write fresh
// Option A: Extract the common pattern
function createEntity<T>(schema: Schema<T>, repo: Repository<T>) {
  return (data: T) => pipe(
    Schema.decode(schema)(data),
    Effect.flatMap(repo.insert),
  );
}

// Option B: Write purpose-built code
function createProject(data: ProjectData) {
  // Clean, focused implementation for projects
}
Gate function:
BEFORE copying code and modifying it:
  Ask: "Am I copying because the pattern is the same or because I'm lazy?"

  IF the pattern is genuinely the same:
    Extract a shared abstraction first, then use it
  IF the pattern is similar but different:
    Write purpose-built code — similar-looking code with different intent
    should NOT be forced into the same abstraction
  NEVER copy-paste more than 5 lines without questioning why
被掩盖的信号: 抽象模型不适用,但开发者强行使用。
typescript
// WORKAROUND: Copy and "adapt" (badly)
// Copied from UserService and changed 3 lines
function createProject(data: ProjectData) {
  // 200 lines, 95% identical to createUser
  // but with subtle bugs from incomplete adaptation
}

// PROPER FIX: Extract shared pattern or write fresh
// Option A: Extract the common pattern
function createEntity<T>(schema: Schema<T>, repo: Repository<T>) {
  return (data: T) => pipe(
    Schema.decode(schema)(data),
    Effect.flatMap(repo.insert),
  );
}

// Option B: Write purpose-built code
function createProject(data: ProjectData) {
  // Clean, focused implementation for projects
}
检测机制:
在复制代码并修改之前:
  自问:“我复制代码是因为模式相同,还是因为偷懒?”

  如果模式确实相同:
    先提取共享抽象,再使用它
  如果模式相似但不同:
    编写专用代码——外观相似但意图不同的代码
    不应被强行纳入同一抽象
  绝不要在未质疑原因的情况下复制粘贴超过5行代码

The Compound Cost

复利成本

A workaround that saves 30 minutes today costs 30 hours when copied to 5 places, debugged 3 times, and confused 4 developers over 6 months. The interest rate on workarounds is predatory.
一个今天节省30分钟的权宜之计,在6个月内被复制到5个地方、调试3次、困扰4名开发者后,最终会耗费30小时。权宜之计的“利率”极具掠夺性。

Red Flags — STOP and Rethink

危险信号——停止并重新思考

Catch these thought patterns and STOP:
ThoughtWhat It Means
"Just add
as any
to make it compile"
TYPE — Type system evasion
"Disable the lint rule for this line"LINT — Warning suppression
"Wrap it in try-catch and ignore the error"SWALLOW — Error swallowing
"Add a setTimeout to fix the timing"TIMING — Lifecycle hack
"Override the prototype/global"PATCH — Monkey patching
"Add
?.
everywhere just to be safe"
SCATTER — Defensive duplication
"Copy this code and change a few things"CLONE — Copy-paste adaptation
"It works, don't touch it"Fear masking a fragile workaround
"We'll fix it properly later"Later never comes
"It's just temporary"Nothing is more permanent
发现以下思维模式时,请立即停止:
想法背后的含义
"Just add
as any
to make it compile"
类型规避——绕过类型系统
"Disable the lint rule for this line"规则抑制——关闭警告
"Wrap it in try-catch and ignore the error"错误吞吃——忽略异常
"Add a setTimeout to fix the timing"时序修复——生命周期临时方案
"Override the prototype/global"猴子补丁——运行时修改
"Add
?.
everywhere just to be safe"
重复防御——冗余校验
"Copy this code and change a few things"复制粘贴——错误复用
"It works, don't touch it"恐惧掩盖了脆弱的权宜之计
"We'll fix it properly later"“以后”永远不会到来
"It's just temporary"没有什么比“临时方案”更永久

The Escape Valve

例外机制

Not every problem can be fixed at root cause. When a workaround is genuinely unavoidable:
REQUIRED conditions (ALL must be true):
  1. Root cause is in external code the team does not control
  2. The proper fix requires upstream changes with uncertain timeline
  3. The business impact of NOT shipping exceeds the technical debt cost
  4. The workaround is ISOLATED (does not leak into other code)

IF all conditions are met:
  1. Mark with explicit comment: // WORKAROUND: [reason] — see [issue-link]
  2. File a tracking issue for removal
  3. Add a test that verifies the workaround behavior
  4. Add a test that will FAIL when the upstream fix lands (canary test)
  5. Set a review date (max 90 days)

IF any condition is NOT met:
  Fix the root cause. No exceptions.
并非所有问题都能从根本原因上修复。当权宜之计确实不可避免时:
必须满足以下所有条件:
  1. 根本原因存在于团队无法控制的外部代码中
  2. 正确的修复需要上游变更,且时间线不确定
  3. 不发布带来的业务影响超过技术债务成本
  4. 权宜之计是隔离的(不会扩散到其他代码)

如果所有条件都满足:
  1. 使用明确的注释标记:// WORKAROUND: [原因] — 参见 [问题链接]
  2. 创建跟踪问题以移除该权宜之计
  3. 添加测试以验证权宜之计的行为
  4. 添加当上游修复完成时会失败的测试(金丝雀测试)
  5. 设置复查日期(最长90天)

如果任何条件不满足:
  修复根本原因,没有例外。

Common Rationalizations

常见借口

ExcuseReality
"It's just a small workaround"Small workarounds become big patterns when copied
"We don't have time for the proper fix"Workarounds cost MORE time in debugging and maintenance
"The type system is too strict"The type system found a real bug — listen to it
"Nobody will copy this"Every workaround in a codebase gets copied within 3 months
"It's behind a feature flag"Feature flags don't expire — the workaround becomes permanent
"The test passes"A passing test with a workaround tests the workaround, not the code
"I'll create a tech debt ticket"93% of tech debt tickets are never resolved
"The external library forces this"Use The Escape Valve process above, with all 5 requirements
借口真相
"It's just a small workaround"小的权宜之计被复制后会变成大问题模式
"We don't have time for the proper fix"权宜之计在调试和维护上会耗费更多时间
"The type system is too strict"类型系统发现了真实的Bug——请倾听它的提示
"Nobody will copy this"代码库中的每个权宜之计都会在3个月内被复制
"It's behind a feature flag"功能标志不会过期——权宜之计会变成永久代码
"The test passes"带有权宜之计的通过测试,测试的是权宜之计而非代码本身
"I'll create a tech debt ticket"93%的技术债务工单永远不会被解决
"The external library forces this"使用上述例外机制流程,满足所有5个要求

The Bottom Line

核心结论

Every workaround is a bet that nobody will ever need to understand this code again.
That bet always loses.

Fix the disease, not the symptom.
Fix the source, not the signal.
Fix the code, not the compiler message.
For the detailed catalog of 30+ specific workaround patterns with before/after code: Read
references/workaround-catalog.md
.
For the philosophical and engineering foundations: Read
references/philosophical-foundations.md
.
Every workaround is a bet that nobody will ever need to understand this code again.
That bet always loses.

Fix the disease, not the symptom.
Fix the source, not the signal.
Fix the code, not the compiler message.
如需查看包含30+种具体权宜之计模式及前后代码对比的详细目录: 阅读
references/workaround-catalog.md
如需了解哲学与工程基础: 阅读
references/philosophical-foundations.md