Files
playbook/brooks-lint/docs/gallery.html
T
2026-06-27 16:04:15 +00:00

544 lines
94 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gallery — brooks-lint</title>
<meta name="description" content="Real brooks-lint output across Python, TypeScript, Go, and Java — PR reviews, architecture audits with dependency graphs, tech debt assessments, and test quality reviews.">
<link rel="icon" type="image/svg+xml" href="logo.svg">
<meta property="og:title" content="brooks-lint Gallery">
<meta property="og:description" content="Real diagnostic output across four languages and four review modes.">
<meta property="og:image" content="https://hyhmrright.github.io/brooks-lint/hero.png">
<meta name="twitter:card" content="summary_large_image">
<style>
:root{
--paper:#f7f1e4; --paper-2:#fbf6ec; --card:#fffdf8; --ink:#2a2520; --ink-soft:#5c5347;
--rule:#e3d8c2; --rule-soft:#ece2cf; --blue:#2d4a7c; --blue-soft:#e7edf5;
--green:#2b8a3e; --gold:#b07d2b; --red:#b23b3b; --ink-dark:#211d18;
--serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
--sans:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",Roboto,Helvetica,Arial,sans-serif;
--mono:"SF Mono","JetBrains Mono","Fira Code",Menlo,Consolas,monospace;
}
*{margin:0;padding:0;box-sizing:border-box;}
html{scroll-behavior:smooth;}
body{font-family:var(--sans);color:var(--ink);background:var(--paper);line-height:1.65;-webkit-font-smoothing:antialiased;}
body::before{content:"";position:fixed;inset:0;pointer-events:none;z-index:0;
background-image:radial-gradient(rgba(120,90,40,.04) 1px,transparent 1px);background-size:4px 4px;opacity:.6;}
.wrap{max-width:1000px;margin:0 auto;padding:0 24px;position:relative;z-index:1;}
h1,h2,h3,.serif{font-family:var(--serif);font-weight:700;letter-spacing:-.01em;}
a{color:var(--blue);text-decoration:none;} a:hover{text-decoration:underline;}
nav{position:sticky;top:0;z-index:50;background:rgba(247,241,228,.85);backdrop-filter:saturate(140%) blur(10px);border-bottom:1px solid var(--rule);}
.nav-in{display:flex;align-items:center;gap:20px;height:64px;max-width:1000px;margin:0 auto;padding:0 24px;}
.brand{display:flex;align-items:center;gap:10px;font-family:var(--serif);font-weight:700;font-size:19px;color:var(--ink);}
.brand img{width:30px;height:30px;border-radius:7px;}
.nav-links{display:flex;gap:22px;margin-left:auto;align-items:center;}
.nav-links a{color:var(--ink-soft);font-size:14.5px;}
header.hero{padding:60px 0 30px;text-align:center;}
header.hero h1{font-size:clamp(30px,4.6vw,46px);margin-bottom:14px;}
header.hero p{color:var(--ink-soft);font-size:17px;max-width:60ch;margin:0 auto;}
.meta-row{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-top:22px;}
.meta-pill{background:var(--card);border:1px solid var(--rule);border-radius:999px;padding:6px 14px;font-size:13px;color:var(--ink-soft);}
.meta-pill b{color:var(--ink);font-family:var(--serif);}
.filters{display:flex;gap:8px;flex-wrap:wrap;justify-content:center;margin:34px 0 8px;position:sticky;top:64px;z-index:40;
background:var(--paper);padding:12px 0;}
.filters button{font-family:var(--sans);font-size:14px;font-weight:600;cursor:pointer;border:1px solid var(--rule);
background:var(--card);color:var(--ink-soft);padding:8px 16px;border-radius:999px;}
.filters button.active{background:var(--ink);color:var(--paper-2);border-color:var(--ink);}
section.mode-group{padding:24px 0;}
.mode-title{font-family:var(--serif);font-size:24px;margin:18px 0 4px;display:flex;align-items:center;gap:10px;}
.mode-title .badge{font-family:var(--mono);font-size:12px;color:var(--gold);border:1px solid var(--rule);background:var(--card);padding:3px 10px;border-radius:999px;font-weight:400;}
.mode-sub{color:var(--ink-soft);font-size:14.5px;margin-bottom:18px;}
.case{background:var(--card);border:1px solid var(--rule);border-radius:14px;padding:24px 26px;margin-bottom:20px;
box-shadow:0 6px 20px rgba(80,55,15,.05);}
.case-head{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:6px;}
.lang{font-family:var(--mono);font-size:11.5px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
padding:3px 9px;border-radius:6px;color:#fff;}
.lang.ts{background:#2d4a7c;} .lang.py{background:#2b6a8a;} .lang.go{background:#1f8a9c;} .lang.java{background:#9c6b1f;}
.case-head h3{font-size:19px;flex:1;min-width:200px;}
.hs{font-family:var(--serif);font-weight:700;font-size:15px;padding:4px 12px;border-radius:999px;white-space:nowrap;}
.hs.bad{background:#f6dede;color:var(--red);} .hs.mid{background:#f6ecd4;color:var(--gold);} .hs.good{background:#def0e1;color:var(--green);}
.case-summary{font-style:italic;color:var(--ink-soft);font-size:14.5px;margin:8px 0 16px;}
details{margin:0 0 18px;border:1px solid var(--rule-soft);border-radius:10px;background:var(--paper-2);}
details summary{cursor:pointer;padding:11px 16px;font-size:13.5px;color:var(--ink-soft);font-weight:600;user-select:none;}
details[open] summary{border-bottom:1px solid var(--rule-soft);}
pre.code{margin:0;padding:18px;background:var(--ink-dark);color:#cfc7b6;border-radius:0 0 10px 10px;overflow-x:auto;
font-family:var(--mono);font-size:12.8px;line-height:1.6;}
pre.struct{background:var(--ink-dark);color:#cfc7b6;}
.finding{border-top:1px solid var(--rule-soft);padding-top:14px;margin-top:14px;}
.finding:first-of-type{border-top:none;padding-top:0;margin-top:0;}
.finding h4{font-family:var(--serif);font-size:16px;margin-bottom:8px;display:flex;gap:8px;align-items:baseline;flex-wrap:wrap;}
.finding h4 .pri{font-family:var(--mono);font-size:11px;color:var(--ink-soft);font-weight:400;}
.ln{font-size:13.5px;line-height:1.55;margin-bottom:5px;color:var(--ink-soft);}
.ln b{font-weight:700;}
.ln.sy b{color:var(--ink);} .ln.sr b{color:var(--blue);} .ln.cq b{color:var(--gold);} .ln.rx b{color:var(--green);}
.mermaid{background:var(--paper-2);border:1px solid var(--rule-soft);border-radius:10px;padding:16px;margin:4px 0 18px;text-align:center;}
table.dbt{width:100%;border-collapse:collapse;font-size:13.5px;margin:4px 0 16px;border:1px solid var(--rule-soft);border-radius:8px;overflow:hidden;}
table.dbt th,table.dbt td{padding:9px 13px;text-align:left;border-bottom:1px solid var(--rule-soft);}
table.dbt th{font-family:var(--serif);background:var(--paper-2);}
table.dbt tr:last-child td{border-bottom:none;}
.note{background:var(--blue-soft);border-left:3px solid var(--blue);border-radius:8px;padding:11px 15px;font-size:13.5px;margin-top:6px;}
footer{border-top:1px solid var(--rule);padding:36px 0 56px;background:var(--paper-2);margin-top:30px;}
.foot-in{display:flex;flex-wrap:wrap;gap:18px;align-items:center;justify-content:space-between;max-width:1000px;margin:0 auto;padding:0 24px;}
.foot-links{display:flex;gap:20px;flex-wrap:wrap;} .foot-links a{color:var(--ink-soft);font-size:14px;}
.foot-note{font-size:13px;color:var(--ink-soft);}
.hidden{display:none !important;}
.lang-toggle{display:inline-flex;border:1px solid var(--rule);border-radius:999px;overflow:hidden;font-size:13px;background:var(--card);}
.lang-toggle button{border:none;background:transparent;padding:5px 11px;cursor:pointer;color:var(--ink-soft);font-family:var(--sans);font-weight:600;font-size:12.5px;}
.lang-toggle button.active{background:var(--ink);color:var(--paper-2);}
</style>
</head>
<body>
<nav>
<div class="nav-in">
<a class="brand" href="index.html"><img src="logo.svg" alt="">brooks-lint</a>
<div class="nav-links">
<a href="index.html" data-en="← Home" data-zh="← 首页" data-zhtw="← 首頁" data-ja="← ホーム" data-ko="← 홈" data-es="← Inicio">← Home</a>
<a href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener">GitHub ↗</a>
<span class="lang-toggle">
<button data-set-lang="en">EN</button>
<button data-set-lang="zh"></button>
<button data-set-lang="zhtw"></button>
<button data-set-lang="ja"></button>
<button data-set-lang="ko"></button>
<button data-set-lang="es">ES</button>
</span>
</div>
</div>
</nav>
<header class="hero">
<div class="wrap">
<h1 data-en="The Gallery" data-zh="画廊" data-zhtw="範例展示" data-ja="ギャラリー" data-ko="갤러리" data-es="La Galería">The Gallery</h1>
<p data-en="Real diagnostic output from brooks-lint — generated by running the skill, then lightly abridged. Four languages, four review modes, every finding in the Iron Law form." data-zh="brooks-lint 的真实诊断输出——通过运行该技能生成,再略作精简。四种语言、四种审查模式,每条发现都遵循铁律形式。" data-zhtw="brooks-lint 的真實診斷輸出——透過執行該技能產生,再略作精簡。四種語言、四種審查模式,每條發現都遵循鐵律形式。" data-ja="brooks-lint の実際の診断出力——スキルを実行して生成し、軽く要約したものです。4 つの言語、4 つのレビューモード、すべての指摘が鉄則の形式に従っています。" data-ko="brooks-lint의 실제 진단 출력—스킬을 실행하여 생성한 뒤 약간 다듬었습니다. 4개 언어, 4개 리뷰 모드, 모든 발견이 철칙 형식을 따릅니다." data-es="Salida diagnóstica real de brooks-lint — generada ejecutando la skill y luego ligeramente abreviada. Cuatro lenguajes, cuatro modos de revisión, cada hallazgo en la forma de la Ley de Hierro.">Real diagnostic output from brooks-lint — generated by running the skill, then lightly abridged. Four languages, four review modes, every finding in the Iron Law form.</p>
<div class="meta-row">
<span class="meta-pill" data-en="<b>8</b> worked examples" data-zh="<b>8</b> 个示例" data-zhtw="<b>8</b> 個範例" data-ja="<b>8</b> 件の実例" data-ko="<b>8</b>개 예제" data-es="<b>8</b> ejemplos prácticos"><b>8</b> worked examples</span>
<span class="meta-pill" data-en="<b>4</b> review modes" data-zh="<b>4</b> 种审查模式" data-zhtw="<b>4</b> 種審查模式" data-ja="<b>4</b> つのレビューモード" data-ko="<b>4</b>개 리뷰 모드" data-es="<b>4</b> modos de revisión"><b>4</b> review modes</span>
<span class="meta-pill" data-en="<b>4</b> languages" data-zh="<b>4</b> 种语言" data-zhtw="<b>4</b> 種語言" data-ja="<b>4</b> つの言語" data-ko="<b>4</b>개 언어" data-es="<b>4</b> lenguajes"><b>4</b> languages</span>
<span class="meta-pill">Python · TypeScript · Go · Java</span>
</div>
</div>
</header>
<div class="wrap">
<div class="filters" id="filters">
<button data-filter="all" class="active" data-en="All" data-zh="全部" data-zhtw="全部" data-ja="すべて" data-ko="전체" data-es="Todo">All</button>
<button data-filter="review" data-en="PR Review" data-zh="PR 审查" data-zhtw="PR 審查" data-ja="PR レビュー" data-ko="PR 리뷰" data-es="Revisión de PR">PR Review</button>
<button data-filter="audit" data-en="Architecture" data-zh="架构审计" data-zhtw="架構審計" data-ja="アーキテクチャ監査" data-ko="아키텍처 감사" data-es="Auditoría de Arquitectura">Architecture</button>
<button data-filter="debt" data-en="Tech Debt" data-zh="技术债" data-zhtw="技術債" data-ja="技術的負債" data-ko="기술 부채" data-es="Deuda Técnica">Tech Debt</button>
<button data-filter="test" data-en="Test Quality" data-zh="测试质量" data-zhtw="測試品質" data-ja="テスト品質" data-ko="테스트 품질" data-es="Calidad de Pruebas">Test Quality</button>
</div>
</div>
<main class="wrap">
<!-- ============ PR REVIEW ============ -->
<section class="mode-group" data-mode="review">
<h2 class="mode-title"><span data-en="PR Review" data-zh="PR 审查" data-zhtw="PR 審查" data-ja="PR レビュー" data-ko="PR 리뷰" data-es="Revisión de PR">PR Review</span> <span class="badge">Mode 1</span></h2>
<p class="mode-sub" data-en="Diagnoses a diff against the six production decay risks (R1R6)." data-zh="对照六种生产衰退风险(R1–R6)诊断一份 diff。" data-zhtw="對照六種生產衰退風險(R1–R6)診斷一份 diff。" data-ja="6 つの本番劣化リスク(R1–R6)に照らして diff を診断します。" data-ko="여섯 가지 운영 부패 위험(R1–R6)에 대해 diff를 진단합니다." data-es="Diagnostica un diff frente a los seis riesgos de decadencia en producción (R1R6).">Diagnoses a diff against the six production decay risks (R1R6).</p>
<article class="case">
<div class="case-head">
<span class="lang ts">TypeScript</span>
<h3 data-en="Seven-service payment processor" data-zh="七服务支付处理器" data-zhtw="七服務支付處理器" data-ja="7 サービスの決済プロセッサ" data-ko="일곱 개 서비스 결제 처리기" data-es="Procesador de pagos con siete servicios">Seven-service payment processor</h3>
<span class="hs mid">55 / 100</span>
</div>
<p class="case-summary" data-en="A single method orchestrates seven services, creating a structural coupling trap where any change to payment, fraud, inventory, or notification touches the same method." data-zh="单个方法编排七个服务,形成结构性耦合陷阱——对支付、欺诈、库存或通知的任何改动都会触及同一个方法。" data-zhtw="單個方法編排七個服務,形成結構性耦合陷阱——對支付、詐騙、庫存或通知的任何改動都會觸及同一個方法。" data-ja="単一のメソッドが 7 つのサービスを統括し、決済・不正検知・在庫・通知のいずれを変更しても同じメソッドに手を入れることになる構造的結合の罠を生んでいます。" data-ko="단일 메서드가 일곱 개의 서비스를 조율하여, 결제·사기·재고·알림 중 무엇을 변경하든 같은 메서드를 건드리게 되는 구조적 결합의 함정을 만듭니다." data-es="Un solo método orquesta siete servicios, creando una trampa de acoplamiento estructural en la que cualquier cambio en pago, fraude, inventario o notificación toca el mismo método.">A single method orchestrates seven services, creating a structural coupling trap where any change to payment, fraud, inventory, or notification touches the same method.</p>
<details>
<summary data-en="▸ Input code" data-zh="▸ 输入代码" data-zhtw="▸ 輸入程式碼" data-ja="▸ 入力コード" data-ko="▸ 입력 코드" data-es="▸ Código de entrada">▸ Input code</summary>
<pre class="code">class PaymentProcessor {
constructor(
private db, private stripe, private mailer, private inventory,
private analytics, private taxCalc, private fraudDetection
) {}
async processPayment(orderId, cardToken) {
const order = await this.db.orders.findById(orderId);
const tax = this.taxCalc.calculate(order.items, order.shippingAddress.state);
order.tax = tax;
const fraudScore = await this.fraudDetection.evaluate({ amount: order.total + tax, card: cardToken, ... });
if (fraudScore > 0.8) { await this.mailer.send(...); await this.analytics.track('fraud_hold', ...); return {...}; }
const charge = await this.stripe.charges.create({ amount: Math.round((order.total + tax) * 100), ... });
for (const item of order.items) {
await this.inventory.decrement(item.sku, item.quantity);
if (await this.inventory.getStock(item.sku) &lt; 10) { await this.mailer.send('warehouse@company.com', ...); }
}
order.status = 'paid'; order.chargeId = charge.id; await this.db.orders.save(order);
await this.mailer.send(order.customerEmail, 'Payment Received', `Charge: $${order.total + tax}`);
await this.analytics.track('payment_success', { orderId, amount: order.total + tax });
return { status: 'paid', chargeId: charge.id };
}
}</pre>
</details>
<div class="finding">
<h4 data-en="🔴 Change Propagation — Seven-service constructor signals a God Class" data-zh="🔴 变更扩散 — 七服务构造函数暴露出上帝类" data-zhtw="🔴 變更擴散 — 七服務建構子暴露出上帝類別" data-ja="🔴 変更の波及 — 7 サービスのコンストラクタが神クラスを示唆" data-ko="🔴 변경 전파 — 일곱 서비스 생성자가 갓 클래스를 드러냄" data-es="🔴 Propagación de cambios — El constructor de siete servicios delata una God Class">🔴 Change Propagation — Seven-service constructor signals a God Class</h4>
<div class="ln sy" data-en="<b>Symptom:</b> One class injects seven dependencies; one method orchestrates all of them." data-zh="<b>症状:</b> 一个类注入了七个依赖;一个方法编排了全部七个。" data-zhtw="<b>症狀:</b> 一個類別注入了七個依賴;一個方法編排了全部七個。" data-ja="<b>症状:</b> 一つのクラスが 7 つの依存を注入し、一つのメソッドがそのすべてを統括しています。" data-ko="<b>증상:</b> 한 클래스가 일곱 개의 의존성을 주입하고, 한 메서드가 그 전부를 조율합니다." data-es="<b>Síntoma:</b> Una clase inyecta siete dependencias; un método las orquesta todas."><b>Symptom:</b> One class injects seven dependencies; one method orchestrates all of them.</div>
<div class="ln sr" data-en="<b>Source:</b> Fowler — Refactoring — Divergent Change; Martin — Clean Architecture — SRP" data-zh="<b>根源:</b> Fowler — Refactoring — 发散式变更(Divergent Change);Martin — Clean Architecture — SRP" data-zhtw="<b>根源:</b> Fowler — Refactoring — 發散式變更(Divergent Change);Martin — Clean Architecture — SRP" data-ja="<b>根源:</b> Fowler — Refactoring — 発散的変更(Divergent Change);Martin — Clean Architecture — SRP" data-ko="<b>근원:</b> Fowler — Refactoring — 발산적 변경(Divergent Change); Martin — Clean Architecture — SRP" data-es="<b>Origen:</b> Fowler — Refactoring — Divergent Change (Cambio divergente); Martin — Clean Architecture — SRP"><b>Source:</b> Fowler — Refactoring — Divergent Change; Martin — Clean Architecture — SRP</div>
<div class="ln rx" data-en="<b>Remedy:</b> Decompose into FraudCheckService, InventoryDeductionService, PaymentNotifier — inject 3, not 7." data-zh="<b>修复:</b> 拆分为 FraudCheckService、InventoryDeductionService、PaymentNotifier——注入 3 个,而非 7 个。" data-zhtw="<b>對策:</b> 拆分為 FraudCheckService、InventoryDeductionService、PaymentNotifier——注入 3 個,而非 7 個。" data-ja="<b>対策:</b> FraudCheckService、InventoryDeductionService、PaymentNotifier に分解し、7 つではなく 3 つを注入します。" data-ko="<b>처방:</b> FraudCheckService, InventoryDeductionService, PaymentNotifier로 분해하여 7개가 아닌 3개를 주입하세요." data-es="<b>Remedio:</b> Descompón en FraudCheckService, InventoryDeductionService, PaymentNotifier — inyecta 3, no 7."><b>Remedy:</b> Decompose into FraudCheckService, InventoryDeductionService, PaymentNotifier — inject 3, not 7.</div>
</div>
<div class="finding">
<h4 data-en="🔴 Change Propagation — Inventory loop embeds warehouse notification policy" data-zh="🔴 变更扩散 — 库存循环里嵌入了仓库通知策略" data-zhtw="🔴 變更擴散 — 庫存迴圈裡嵌入了倉庫通知策略" data-ja="🔴 変更の波及 — 在庫ループに倉庫通知ポリシーが埋め込まれている" data-ko="🔴 변경 전파 — 재고 루프에 창고 알림 정책이 박혀 있음" data-es="🔴 Propagación de cambios — El bucle de inventario incrusta la política de notificación del almacén">🔴 Change Propagation — Inventory loop embeds warehouse notification policy</h4>
<div class="ln sy" data-en="<b>Symptom:</b> A low-stock check (<code>&lt; 10</code>) fires a warehouse email with hardcoded recipient inside the payment loop." data-zh="<b>症状:</b> 一处低库存检查(<code>&lt; 10</code>)在支付循环内部发出收件人写死的仓库邮件。" data-zhtw="<b>症狀:</b> 一處低庫存檢查(<code>&lt; 10</code>)在支付迴圈內部發出收件人寫死的倉庫郵件。" data-ja="<b>症状:</b> 低在庫チェック(<code>&lt; 10</code>)が決済ループの内側で、宛先をハードコードした倉庫メールを送信しています。" data-ko="<b>증상:</b> 저재고 검사(<code>&lt; 10</code>)가 결제 루프 내부에서 수신자가 하드코딩된 창고 이메일을 발송합니다." data-es="<b>Síntoma:</b> Una comprobación de bajo stock (<code>&lt; 10</code>) envía un correo al almacén con destinatario codificado dentro del bucle de pago."><b>Symptom:</b> A low-stock check (<code>&lt; 10</code>) fires a warehouse email with hardcoded recipient inside the payment loop.</div>
<div class="ln sr" data-en="<b>Source:</b> Fowler — Refactoring — Shotgun Surgery; Hunt &amp; Thomas — Orthogonality" data-zh="<b>根源:</b> Fowler — Refactoring — 霰弹式修改(Shotgun Surgery);Hunt &amp; Thomas — 正交性(Orthogonality" data-zhtw="<b>根源:</b> Fowler — Refactoring — 散彈式修改(Shotgun Surgery);Hunt &amp; Thomas — 正交性(Orthogonality" data-ja="<b>根源:</b> Fowler — Refactoring — ショットガン手術(Shotgun Surgery);Hunt &amp; Thomas — 直交性(Orthogonality" data-ko="<b>근원:</b> Fowler — Refactoring — 산탄총 수술(Shotgun Surgery); Hunt &amp; Thomas — 직교성(Orthogonality)" data-es="<b>Origen:</b> Fowler — Refactoring — Shotgun Surgery (Cirugía con escopeta); Hunt &amp; Thomas — Orthogonality (Ortogonalidad)"><b>Source:</b> Fowler — Refactoring — Shotgun Surgery; Hunt &amp; Thomas — Orthogonality</div>
<div class="ln rx" data-en="<b>Remedy:</b> Publish a <code>StockLevelChanged</code> domain event a separate notifier subscribes to." data-zh="<b>修复:</b> 发布一个 <code>StockLevelChanged</code> 领域事件,由独立的通知器订阅。" data-zhtw="<b>對策:</b> 發布一個 <code>StockLevelChanged</code> 領域事件,由獨立的通知器訂閱。" data-ja="<b>対策:</b> <code>StockLevelChanged</code> ドメインイベントを発行し、別の通知コンポーネントが購読するようにします。" data-ko="<b>처방:</b> <code>StockLevelChanged</code> 도메인 이벤트를 발행하고 별도의 알림 컴포넌트가 구독하게 하세요." data-es="<b>Remedio:</b> Publica un evento de dominio <code>StockLevelChanged</code> al que se suscriba un notificador independiente."><b>Remedy:</b> Publish a <code>StockLevelChanged</code> domain event a separate notifier subscribes to.</div>
</div>
<div class="finding">
<h4 data-en="🟡 Knowledge Duplication — <code>order.total + tax</code> computed three times" data-zh="🟡 知识重复 — <code>order.total + tax</code> 被计算了三次" data-zhtw="🟡 知識重複 — <code>order.total + tax</code> 被計算了三次" data-ja="🟡 知識の重複 — <code>order.total + tax</code> が 3 回計算されている" data-ko="🟡 지식 중복 — <code>order.total + tax</code>가 세 번 계산됨" data-es="🟡 Duplicación de conocimiento — <code>order.total + tax</code> se calcula tres veces">🟡 Knowledge Duplication — <code>order.total + tax</code> computed three times</h4>
<div class="ln sy" data-en="<b>Symptom:</b> The same expression appears on three lines with no shared name." data-zh="<b>症状:</b> 同一个表达式出现在三行里,没有共享的命名。" data-zhtw="<b>症狀:</b> 同一個運算式出現在三行裡,沒有共享的命名。" data-ja="<b>症状:</b> 同じ式が共有された名前を持たないまま 3 行に現れています。" data-ko="<b>증상:</b> 같은 표현식이 공유된 이름 없이 세 줄에 나타납니다." data-es="<b>Síntoma:</b> La misma expresión aparece en tres líneas sin un nombre compartido."><b>Symptom:</b> The same expression appears on three lines with no shared name.</div>
<div class="ln sr" data-en="<b>Source:</b> Hunt &amp; Thomas — DRY; Fowler — Refactoring — Duplicate Code" data-zh="<b>根源:</b> Hunt &amp; Thomas — DRYFowler — Refactoring — 重复代码(Duplicate Code" data-zhtw="<b>根源:</b> Hunt &amp; Thomas — DRYFowler — Refactoring — 重複程式碼(Duplicate Code" data-ja="<b>根源:</b> Hunt &amp; Thomas — DRYFowler — Refactoring — 重複コード(Duplicate Code" data-ko="<b>근원:</b> Hunt &amp; Thomas — DRY; Fowler — Refactoring — 중복 코드(Duplicate Code)" data-es="<b>Origen:</b> Hunt &amp; Thomas — DRY; Fowler — Refactoring — Duplicate Code (Código duplicado)"><b>Source:</b> Hunt &amp; Thomas — DRY; Fowler — Refactoring — Duplicate Code</div>
<div class="ln rx" data-en="<b>Remedy:</b> Expose a computed <code>order.grandTotal</code>." data-zh="<b>修复:</b> 暴露一个计算得出的 <code>order.grandTotal</code>。" data-zhtw="<b>對策:</b> 暴露一個計算得出的 <code>order.grandTotal</code>。" data-ja="<b>対策:</b> 計算済みの <code>order.grandTotal</code> を公開します。" data-ko="<b>처방:</b> 계산된 <code>order.grandTotal</code>을 노출하세요." data-es="<b>Remedio:</b> Expón un <code>order.grandTotal</code> calculado."><b>Remedy:</b> Expose a computed <code>order.grandTotal</code>.</div>
</div>
<div class="finding">
<h4 data-en="🟡 Domain Model Distortion — <code>Order</code> is a mutable data bag" data-zh="🟡 领域模型失真 — <code>Order</code> 是一个可变的数据袋" data-zhtw="🟡 領域模型失真 — <code>Order</code> 是一個可變的資料袋" data-ja="🟡 ドメインモデルの歪み — <code>Order</code> が可変のデータ袋になっている" data-ko="🟡 도메인 모델 왜곡 — <code>Order</code>가 가변 데이터 가방임" data-es="🟡 Distorsión del modelo de dominio — <code>Order</code> es un saco de datos mutable">🟡 Domain Model Distortion — <code>Order</code> is a mutable data bag</h4>
<div class="ln sy" data-en="<b>Symptom:</b> <code>order.tax</code>, <code>order.status</code>, <code>order.chargeId</code> are all set externally; the object holds no behaviour." data-zh="<b>症状:</b> <code>order.tax</code>、<code>order.status</code>、<code>order.chargeId</code> 全部由外部赋值;该对象不持有任何行为。" data-zhtw="<b>症狀:</b> <code>order.tax</code>、<code>order.status</code>、<code>order.chargeId</code> 全部由外部賦值;該物件不持有任何行為。" data-ja="<b>症状:</b> <code>order.tax</code>、<code>order.status</code>、<code>order.chargeId</code> はすべて外部から設定され、このオブジェクトは振る舞いを一切持ちません。" data-ko="<b>증상:</b> <code>order.tax</code>, <code>order.status</code>, <code>order.chargeId</code>가 모두 외부에서 설정되며, 객체는 어떤 행위도 갖지 않습니다." data-es="<b>Síntoma:</b> <code>order.tax</code>, <code>order.status</code>, <code>order.chargeId</code> se asignan todos externamente; el objeto no contiene comportamiento."><b>Symptom:</b> <code>order.tax</code>, <code>order.status</code>, <code>order.chargeId</code> are all set externally; the object holds no behaviour.</div>
<div class="ln rx" data-en="<b>Remedy:</b> Give <code>Order</code> state-transition methods: <code>order.recordPayment(chargeId)</code>." data-zh="<b>修复:</b> 为 <code>Order</code> 赋予状态转换方法:<code>order.recordPayment(chargeId)</code>。" data-zhtw="<b>對策:</b> 為 <code>Order</code> 賦予狀態轉換方法:<code>order.recordPayment(chargeId)</code>。" data-ja="<b>対策:</b> <code>Order</code> に状態遷移メソッドを与えます:<code>order.recordPayment(chargeId)</code>。" data-ko="<b>처방:</b> <code>Order</code>에 상태 전이 메서드를 부여하세요: <code>order.recordPayment(chargeId)</code>." data-es="<b>Remedio:</b> Dota a <code>Order</code> de métodos de transición de estado: <code>order.recordPayment(chargeId)</code>."><b>Remedy:</b> Give <code>Order</code> state-transition methods: <code>order.recordPayment(chargeId)</code>.</div>
</div>
</article>
</section>
<!-- ============ ARCHITECTURE ============ -->
<section class="mode-group" data-mode="audit">
<h2 class="mode-title"><span data-en="Architecture Audit" data-zh="架构审计" data-zhtw="架構審計" data-ja="アーキテクチャ監査" data-ko="아키텍처 감사" data-es="Auditoría de Arquitectura">Architecture Audit</span> <span class="badge">Mode 2</span></h2>
<p class="mode-sub" data-en="Maps module dependencies, renders a colour-coded graph, and flags layering and cycle violations." data-zh="梳理模块依赖,渲染一张颜色编码的依赖图,并标出分层与环路违规。" data-zhtw="梳理模組依賴,渲染一張顏色編碼的依賴圖,並標出分層與環路違規。" data-ja="モジュールの依存関係をマッピングし、色分けしたグラフを描画して、レイヤリングと循環の違反を指摘します。" data-ko="모듈 의존성을 매핑하고, 색상으로 구분된 그래프를 렌더링하며, 계층 및 순환 위반을 표시합니다." data-es="Mapea las dependencias entre módulos, genera un grafo codificado por colores y señala violaciones de capas y ciclos.">Maps module dependencies, renders a colour-coded graph, and flags layering and cycle violations.</p>
<article class="case">
<div class="case-head">
<span class="lang ts">TypeScript</span>
<h3 data-en="Dependency Inversion violation" data-zh="依赖倒置违规" data-zhtw="依賴反轉違規" data-ja="依存性逆転の違反" data-ko="의존성 역전 위반" data-es="Violación de la inversión de dependencias">Dependency Inversion violation</h3>
<span class="hs mid">50 / 100</span>
</div>
<p class="case-summary" data-en="Domain entities import infrastructure directly, and a near-cycle forms between Product and PricingService." data-zh="领域实体直接导入基础设施,且 Product 与 PricingService 之间形成了一个近乎环路的依赖。" data-zhtw="領域實體直接匯入基礎設施,且 Product 與 PricingService 之間形成了一個近乎環路的依賴。" data-ja="ドメインエンティティがインフラを直接インポートし、Product と PricingService の間にほぼ循環が形成されています。" data-ko="도메인 엔티티가 인프라를 직접 임포트하며, Product와 PricingService 사이에 거의 순환에 가까운 의존이 형성됩니다." data-es="Las entidades de dominio importan la infraestructura directamente, y se forma un casi-ciclo entre Product y PricingService.">Domain entities import infrastructure directly, and a near-cycle forms between Product and PricingService.</p>
<pre class="mermaid">graph TD
subgraph API["API Layer"]
OrderController; UserController
end
subgraph Services["Service Layer"]
OrderService; PricingService; UserService
end
subgraph Domain["Domain Layer"]
Order; User; Product
end
subgraph Infra["Infrastructure Layer"]
PostgresClient; RedisCache; StripeClient
end
OrderController --> OrderService
UserController --> UserService
UserService --> OrderService
OrderService --> Order
OrderService --> PostgresClient
PricingService --> Product
PricingService --> StripeClient
Order --> PostgresClient
User --> RedisCache
Product --> PricingService
classDef critical fill:#ff6b6b,stroke:#c92a2a,color:#fff
classDef warning fill:#ffd43b,stroke:#e67700
classDef clean fill:#51cf66,stroke:#2b8a3e,color:#fff
class Order,User,Product critical
class OrderService,PricingService,UserService warning
class OrderController,UserController,PostgresClient,RedisCache,StripeClient clean</pre>
<div class="finding">
<h4 data-en="🔴 Dependency Disorder — Domain layer directly imports infrastructure" data-zh="🔴 依赖失序 — 领域层直接导入基础设施" data-zhtw="🔴 依賴失序 — 領域層直接匯入基礎設施" data-ja="🔴 依存関係の無秩序 — ドメイン層がインフラを直接インポートしている" data-ko="🔴 의존성 무질서 — 도메인 계층이 인프라를 직접 임포트함" data-es="🔴 Desorden de dependencias — La capa de dominio importa la infraestructura directamente">🔴 Dependency Disorder — Domain layer directly imports infrastructure</h4>
<div class="ln sy" data-en="<b>Symptom:</b> <code>Order.ts</code> imports <code>PostgresClient</code>; <code>User.ts</code> imports <code>RedisCache</code>." data-zh="<b>症状:</b> <code>Order.ts</code> 导入了 <code>PostgresClient</code><code>User.ts</code> 导入了 <code>RedisCache</code>。" data-zhtw="<b>症狀:</b> <code>Order.ts</code> 匯入了 <code>PostgresClient</code><code>User.ts</code> 匯入了 <code>RedisCache</code>。" data-ja="<b>症状:</b> <code>Order.ts</code> が <code>PostgresClient</code> を、<code>User.ts</code> が <code>RedisCache</code> をインポートしています。" data-ko="<b>증상:</b> <code>Order.ts</code>가 <code>PostgresClient</code>를, <code>User.ts</code>가 <code>RedisCache</code>를 임포트합니다." data-es="<b>Síntoma:</b> <code>Order.ts</code> importa <code>PostgresClient</code>; <code>User.ts</code> importa <code>RedisCache</code>."><b>Symptom:</b> <code>Order.ts</code> imports <code>PostgresClient</code>; <code>User.ts</code> imports <code>RedisCache</code>.</div>
<div class="ln sr" data-en="<b>Source:</b> Martin — Clean Architecture — Dependency Inversion Principle" data-zh="<b>根源:</b> Martin — Clean Architecture — 依赖倒置原则(Dependency Inversion Principle" data-zhtw="<b>根源:</b> Martin — Clean Architecture — 依賴反轉原則(Dependency Inversion Principle" data-ja="<b>根源:</b> Martin — Clean Architecture — 依存性逆転の原則(Dependency Inversion Principle" data-ko="<b>근원:</b> Martin — Clean Architecture — 의존성 역전 원칙(Dependency Inversion Principle)" data-es="<b>Origen:</b> Martin — Clean Architecture — Dependency Inversion Principle (Principio de inversión de dependencias)"><b>Source:</b> Martin — Clean Architecture — Dependency Inversion Principle</div>
<div class="ln rx" data-en="<b>Remedy:</b> Define <code>IOrderRepository</code>/<code>IUserRepository</code> in the domain; move infra refs to <code>infra/</code>." data-zh="<b>修复:</b> 在领域层定义 <code>IOrderRepository</code>/<code>IUserRepository</code>;将基础设施引用移到 <code>infra/</code>。" data-zhtw="<b>對策:</b> 在領域層定義 <code>IOrderRepository</code>/<code>IUserRepository</code>;將基礎設施引用移到 <code>infra/</code>。" data-ja="<b>対策:</b> ドメインに <code>IOrderRepository</code>/<code>IUserRepository</code> を定義し、インフラ参照を <code>infra/</code> へ移します。" data-ko="<b>처방:</b> 도메인에 <code>IOrderRepository</code>/<code>IUserRepository</code>를 정의하고 인프라 참조를 <code>infra/</code>로 옮기세요." data-es="<b>Remedio:</b> Define <code>IOrderRepository</code>/<code>IUserRepository</code> en el dominio; mueve las referencias de infraestructura a <code>infra/</code>."><b>Remedy:</b> Define <code>IOrderRepository</code>/<code>IUserRepository</code> in the domain; move infra refs to <code>infra/</code>.</div>
</div>
<div class="finding">
<h4 data-en="🔴 Dependency Disorder — Product → PricingService (upward dependency)" data-zh="🔴 依赖失序 — Product → PricingService(向上依赖)" data-zhtw="🔴 依賴失序 — Product → PricingService(向上依賴)" data-ja="🔴 依存関係の無秩序 — Product → PricingService(上向きの依存)" data-ko="🔴 의존성 무질서 — Product → PricingService(상향 의존)" data-es="🔴 Desorden de dependencias — Product → PricingService (dependencia ascendente)">🔴 Dependency Disorder — Product → PricingService (upward dependency)</h4>
<div class="ln sy" data-en="<b>Symptom:</b> Near-cycle <code>PricingService → Product → PricingService</code>." data-zh="<b>症状:</b> 近乎环路 <code>PricingService → Product → PricingService</code>。" data-zhtw="<b>症狀:</b> 近乎環路 <code>PricingService → Product → PricingService</code>。" data-ja="<b>症状:</b> ほぼ循環 <code>PricingService → Product → PricingService</code>。" data-ko="<b>증상:</b> 거의 순환 <code>PricingService → Product → PricingService</code>." data-es="<b>Síntoma:</b> Casi-ciclo <code>PricingService → Product → PricingService</code>."><b>Symptom:</b> Near-cycle <code>PricingService → Product → PricingService</code>.</div>
<div class="ln sr" data-en="<b>Source:</b> Martin — Clean Architecture — Acyclic Dependencies Principle" data-zh="<b>根源:</b> Martin — Clean Architecture — 无环依赖原则(Acyclic Dependencies Principle" data-zhtw="<b>根源:</b> Martin — Clean Architecture — 無環依賴原則(Acyclic Dependencies Principle" data-ja="<b>根源:</b> Martin — Clean Architecture — 非循環依存の原則(Acyclic Dependencies Principle" data-ko="<b>근원:</b> Martin — Clean Architecture — 비순환 의존성 원칙(Acyclic Dependencies Principle)" data-es="<b>Origen:</b> Martin — Clean Architecture — Acyclic Dependencies Principle (Principio de dependencias acíclicas)"><b>Source:</b> Martin — Clean Architecture — Acyclic Dependencies Principle</div>
<div class="ln rx" data-en="<b>Remedy:</b> Pass pricing as a value object or define <code>IPricingPolicy</code> in the domain." data-zh="<b>修复:</b> 将定价作为值对象传入,或在领域层定义 <code>IPricingPolicy</code>。" data-zhtw="<b>對策:</b> 將定價作為值物件傳入,或在領域層定義 <code>IPricingPolicy</code>。" data-ja="<b>対策:</b> 価格を値オブジェクトとして渡すか、ドメインに <code>IPricingPolicy</code> を定義します。" data-ko="<b>처방:</b> 가격을 값 객체로 전달하거나 도메인에 <code>IPricingPolicy</code>를 정의하세요." data-es="<b>Remedio:</b> Pasa el precio como objeto de valor o define <code>IPricingPolicy</code> en el dominio."><b>Remedy:</b> Pass pricing as a value object or define <code>IPricingPolicy</code> in the domain.</div>
</div>
</article>
<article class="case">
<div class="case-head">
<span class="lang go">Go</span>
<h3 data-en="Circular dependency across packages" data-zh="跨包的循环依赖" data-zhtw="跨套件的循環依賴" data-ja="パッケージをまたぐ循環依存" data-ko="패키지 간 순환 의존성" data-es="Dependencia circular entre paquetes">Circular dependency across packages</h3>
<span class="hs bad">45 / 100</span>
</div>
<p class="case-summary" data-en="<code>auth → user → notification → auth</code> forms a strongly connected component — Go refuses to compile it." data-zh="<code>auth → user → notification → auth</code> 构成一个强连通分量——Go 拒绝编译它。" data-zhtw="<code>auth → user → notification → auth</code> 構成一個強連通分量——Go 拒絕編譯它。" data-ja="<code>auth → user → notification → auth</code> が強連結成分を形成しており、Go はコンパイルを拒否します。" data-ko="<code>auth → user → notification → auth</code>가 강한 연결 요소를 형성하여 Go가 컴파일을 거부합니다." data-es="<code>auth → user → notification → auth</code> forma un componente fuertemente conexo — Go se niega a compilarlo."><code>auth → user → notification → auth</code> forms a strongly connected component — Go refuses to compile it.</p>
<pre class="mermaid">graph TD
subgraph pkg["pkg/"]
auth["auth"]; user["user"]; notification["notification"]; billing["billing"]
end
auth --> user
user --> notification
notification -.->|circular| auth
billing --> user
classDef critical fill:#ff6b6b,stroke:#c92a2a,color:#fff
classDef warning fill:#ffd43b,stroke:#e67700
class auth,user,notification critical
class billing warning</pre>
<div class="finding">
<h4 data-en="🔴 Dependency Disorder — Circular dependency auth → user → notification → auth" data-zh="🔴 依赖失序 — 循环依赖 auth → user → notification → auth" data-zhtw="🔴 依賴失序 — 循環依賴 auth → user → notification → auth" data-ja="🔴 依存関係の無秩序 — 循環依存 auth → user → notification → auth" data-ko="🔴 의존성 무질서 — 순환 의존 auth → user → notification → auth" data-es="🔴 Desorden de dependencias — Dependencia circular auth → user → notification → auth">🔴 Dependency Disorder — Circular dependency auth → user → notification → auth</h4>
<div class="ln sy" data-en="<b>Symptom:</b> Three packages form a cycle; none compile, test, or deploy independently." data-zh="<b>症状:</b> 三个包构成一个环;没有一个能独立编译、测试或部署。" data-zhtw="<b>症狀:</b> 三個套件構成一個環;沒有一個能獨立編譯、測試或部署。" data-ja="<b>症状:</b> 3 つのパッケージが循環を形成し、どれも独立してコンパイル・テスト・デプロイできません。" data-ko="<b>증상:</b> 세 패키지가 순환을 형성하여 어느 것도 독립적으로 컴파일·테스트·배포되지 않습니다." data-es="<b>Síntoma:</b> Tres paquetes forman un ciclo; ninguno compila, prueba ni despliega de forma independiente."><b>Symptom:</b> Three packages form a cycle; none compile, test, or deploy independently.</div>
<div class="ln sr" data-en="<b>Source:</b> Martin — Clean Architecture — Acyclic Dependencies Principle" data-zh="<b>根源:</b> Martin — Clean Architecture — 无环依赖原则(Acyclic Dependencies Principle" data-zhtw="<b>根源:</b> Martin — Clean Architecture — 無環依賴原則(Acyclic Dependencies Principle" data-ja="<b>根源:</b> Martin — Clean Architecture — 非循環依存の原則(Acyclic Dependencies Principle" data-ko="<b>근원:</b> Martin — Clean Architecture — 비순환 의존성 원칙(Acyclic Dependencies Principle)" data-es="<b>Origen:</b> Martin — Clean Architecture — Acyclic Dependencies Principle (Principio de dependencias acíclicas)"><b>Source:</b> Martin — Clean Architecture — Acyclic Dependencies Principle</div>
<div class="ln rx" data-en="<b>Remedy:</b> Extract interfaces into <code>pkg/contracts</code>; each package implements the interface its consumer defines." data-zh="<b>修复:</b> 将接口抽取到 <code>pkg/contracts</code>;每个包实现由其消费者定义的接口。" data-zhtw="<b>對策:</b> 將介面抽取到 <code>pkg/contracts</code>;每個套件實作由其消費者定義的介面。" data-ja="<b>対策:</b> インターフェースを <code>pkg/contracts</code> に抽出し、各パッケージは消費側が定義したインターフェースを実装します。" data-ko="<b>처방:</b> 인터페이스를 <code>pkg/contracts</code>로 추출하고, 각 패키지는 소비자가 정의한 인터페이스를 구현하게 하세요." data-es="<b>Remedio:</b> Extrae las interfaces a <code>pkg/contracts</code>; cada paquete implementa la interfaz que define su consumidor."><b>Remedy:</b> Extract interfaces into <code>pkg/contracts</code>; each package implements the interface its consumer defines.</div>
</div>
<div class="finding">
<h4 data-en="🟡 Domain Model Distortion — Bounded contexts crossed with no anti-corruption layer" data-zh="🟡 领域模型失真 — 跨越限界上下文却没有防腐层" data-zhtw="🟡 領域模型失真 — 跨越界限上下文卻沒有防腐層" data-ja="🟡 ドメインモデルの歪み — 腐敗防止層なしに境界づけられたコンテキストを越境" data-ko="🟡 도메인 모델 왜곡 — 부패 방지 계층 없이 경계 컨텍스트를 넘나듦" data-es="🟡 Distorsión del modelo de dominio — Se cruzan contextos delimitados sin capa anticorrupción">🟡 Domain Model Distortion — Bounded contexts crossed with no anti-corruption layer</h4>
<div class="ln sy" data-en="<b>Symptom:</b> Identity, profile, and notification contexts import each other with no translation layer." data-zh="<b>症状:</b> 身份、资料和通知三个上下文相互导入,没有任何转换层。" data-zhtw="<b>症狀:</b> 身分、資料和通知三個上下文相互匯入,沒有任何轉換層。" data-ja="<b>症状:</b> アイデンティティ・プロフィール・通知の各コンテキストが、変換層なしに相互にインポートしています。" data-ko="<b>증상:</b> 신원, 프로필, 알림 컨텍스트가 변환 계층 없이 서로를 임포트합니다." data-es="<b>Síntoma:</b> Los contextos de identidad, perfil y notificación se importan entre sí sin capa de traducción."><b>Symptom:</b> Identity, profile, and notification contexts import each other with no translation layer.</div>
<div class="ln sr" data-en="<b>Source:</b> Evans — DDD — Bounded Context; Anti-Corruption Layer" data-zh="<b>根源:</b> Evans — DDD — 限界上下文(Bounded Context);防腐层(Anti-Corruption Layer" data-zhtw="<b>根源:</b> Evans — DDD — 界限上下文(Bounded Context);防腐層(Anti-Corruption Layer" data-ja="<b>根源:</b> Evans — DDD — 境界づけられたコンテキスト(Bounded Context);腐敗防止層(Anti-Corruption Layer" data-ko="<b>근원:</b> Evans — DDD — 경계 컨텍스트(Bounded Context); 부패 방지 계층(Anti-Corruption Layer)" data-es="<b>Origen:</b> Evans — DDD — Bounded Context (Contexto delimitado); Anti-Corruption Layer (Capa anticorrupción)"><b>Source:</b> Evans — DDD — Bounded Context; Anti-Corruption Layer</div>
<div class="ln rx" data-en="<b>Remedy:</b> Define thin adapters at each context boundary." data-zh="<b>修复:</b> 在每个上下文边界处定义轻量适配器。" data-zhtw="<b>對策:</b> 在每個上下文邊界處定義輕量轉接器。" data-ja="<b>対策:</b> 各コンテキスト境界に薄いアダプタを定義します。" data-ko="<b>처방:</b> 각 컨텍스트 경계에 얇은 어댑터를 정의하세요." data-es="<b>Remedio:</b> Define adaptadores delgados en cada límite de contexto."><b>Remedy:</b> Define thin adapters at each context boundary.</div>
</div>
</article>
<article class="case">
<div class="case-head">
<span class="lang java">Java</span>
<h3 data-en="Textbook Clean Architecture" data-zh="教科书级的整洁架构" data-zhtw="教科書級的整潔架構" data-ja="教科書どおりのクリーンアーキテクチャ" data-ko="교과서적인 클린 아키텍처" data-es="Clean Architecture de manual">Textbook Clean Architecture</h3>
<span class="hs good">98 / 100</span>
</div>
<p class="case-summary" data-en="Dependencies flow inward, infra implements domain ports, no cycles. brooks-lint reports clean code as clean — and still offers one forward-looking suggestion." data-zh="依赖向内流动,基础设施实现领域端口,没有环路。brooks-lint 将整洁代码如实评为整洁——并仍给出一条前瞻性建议。" data-zhtw="依賴向內流動,基礎設施實作領域埠,沒有環路。brooks-lint 將整潔程式碼如實評為整潔——並仍給出一條前瞻性建議。" data-ja="依存は内側へ流れ、インフラがドメインのポートを実装し、循環はありません。brooks-lint はクリーンなコードをクリーンと報告し——それでも先を見据えた提案を一つ示します。" data-ko="의존성이 안쪽으로 흐르고, 인프라가 도메인 포트를 구현하며, 순환이 없습니다. brooks-lint는 깨끗한 코드를 깨끗하다고 보고하면서도 미래를 내다보는 제안 하나를 제시합니다." data-es="Las dependencias fluyen hacia dentro, la infraestructura implementa los puertos del dominio, sin ciclos. brooks-lint informa que el código limpio es limpio — y aun así ofrece una sugerencia de cara al futuro.">Dependencies flow inward, infra implements domain ports, no cycles. brooks-lint reports clean code as clean — and still offers one forward-looking suggestion.</p>
<pre class="mermaid">graph TD
subgraph API["API Layer"]
OrderController; UserController
end
subgraph Application["Application Layer"]
OrderService; UserService
end
subgraph Domain["Domain Layer"]
OrderModel["Order"]; UserModel["User"]; OrderRepository["OrderRepository (interface)"]; UserRepository["UserRepository (interface)"]
end
subgraph Infra["Infrastructure Layer"]
JpaOrderRepository; JpaUserRepository
end
OrderController --> OrderService
UserController --> UserService
OrderService --> OrderModel
OrderService --> OrderRepository
UserService --> UserModel
UserService --> UserRepository
JpaOrderRepository --> OrderRepository
JpaUserRepository --> UserRepository
classDef clean fill:#51cf66,stroke:#2b8a3e,color:#fff
class OrderController,UserController,OrderService,UserService,OrderModel,UserModel,OrderRepository,UserRepository,JpaOrderRepository,JpaUserRepository clean</pre>
<div class="finding">
<h4 data-en="🟢 Suggestion — Monitor application service growth" data-zh="🟢 建议 — 监控应用服务的膨胀" data-zhtw="🟢 建議 — 監控應用服務的膨脹" data-ja="🟢 提案 — アプリケーションサービスの肥大化を監視する" data-ko="🟢 제안 — 애플리케이션 서비스의 비대화를 모니터링하라" data-es="🟢 Sugerencia — Vigila el crecimiento de los servicios de aplicación">🟢 Suggestion — Monitor application service growth</h4>
<div class="ln sy" data-en="<b>Symptom:</b> <code>OrderService</code> and <code>UserService</code> are symmetric siblings that may accrue responsibilities without a split policy." data-zh="<b>症状:</b> <code>OrderService</code> 与 <code>UserService</code> 是对称的同级,若无拆分策略可能不断累积职责。" data-zhtw="<b>症狀:</b> <code>OrderService</code> 與 <code>UserService</code> 是對稱的同級,若無拆分策略可能不斷累積職責。" data-ja="<b>症状:</b> <code>OrderService</code> と <code>UserService</code> は対称的な兄弟で、分割の方針がないと責務を蓄積しかねません。" data-ko="<b>증상:</b> <code>OrderService</code>와 <code>UserService</code>는 대칭적인 형제로, 분할 정책이 없으면 책임이 쌓일 수 있습니다." data-es="<b>Síntoma:</b> <code>OrderService</code> y <code>UserService</code> son hermanos simétricos que pueden acumular responsabilidades sin una política de división."><b>Symptom:</b> <code>OrderService</code> and <code>UserService</code> are symmetric siblings that may accrue responsibilities without a split policy.</div>
<div class="ln sr" data-en="<b>Source:</b> Brooks — The Mythical Man-Month — Conceptual Integrity" data-zh="<b>根源:</b> Brooks — The Mythical Man-Month — 概念完整性(Conceptual Integrity" data-zhtw="<b>根源:</b> Brooks — The Mythical Man-Month — 概念完整性(Conceptual Integrity" data-ja="<b>根源:</b> Brooks — The Mythical Man-Month — 概念的完全性(Conceptual Integrity" data-ko="<b>근원:</b> Brooks — The Mythical Man-Month — 개념적 무결성(Conceptual Integrity)" data-es="<b>Origen:</b> Brooks — The Mythical Man-Month — Conceptual Integrity (Integridad conceptual)"><b>Source:</b> Brooks — The Mythical Man-Month — Conceptual Integrity</div>
<div class="ln rx" data-en="<b>Remedy:</b> Document a &quot;one service per use-case cluster&quot; rule now, before the pattern calcifies." data-zh="<b>修复:</b> 趁该模式尚未固化,现在就把“每个用例簇一个服务”的规则记录下来。" data-zhtw="<b>對策:</b> 趁該模式尚未固化,現在就把「每個用例叢集一個服務」的規則記錄下來。" data-ja="<b>対策:</b> パターンが固定化する前に、いま「ユースケースのまとまりごとに 1 サービス」という規則を文書化します。" data-ko="<b>처방:</b> 패턴이 굳어지기 전에 지금 “유스케이스 클러스터당 서비스 하나” 규칙을 문서화하세요." data-es="<b>Remedio:</b> Documenta ahora una regla de &quot;un servicio por clúster de casos de uso&quot;, antes de que el patrón se calcifique."><b>Remedy:</b> Document a "one service per use-case cluster" rule now, before the pattern calcifies.</div>
</div>
</article>
</section>
<!-- ============ TECH DEBT ============ -->
<section class="mode-group" data-mode="debt">
<h2 class="mode-title"><span data-en="Tech Debt Assessment" data-zh="技术债评估" data-zhtw="技術債評估" data-ja="技術的負債の評価" data-ko="기술 부채 평가" data-es="Evaluación de Deuda Técnica">Tech Debt Assessment</span> <span class="badge">Mode 3</span></h2>
<p class="mode-sub" data-en="Classifies debt across the decay risks and scores each finding by Pain × Spread priority." data-zh="按各类衰退风险对债务分类,并以 Pain × Spread 优先级为每条发现评分。" data-zhtw="按各類衰退風險對債務分類,並以 Pain × Spread 優先級為每條發現評分。" data-ja="債務を各劣化リスクに分類し、各指摘を Pain × Spread の優先度でスコア付けします。" data-ko="부채를 각 부패 위험별로 분류하고, 각 발견을 Pain × Spread 우선순위로 점수화합니다." data-es="Clasifica la deuda según los riesgos de decadencia y puntúa cada hallazgo por prioridad Pain × Spread.">Classifies debt across the decay risks and scores each finding by Pain × Spread priority.</p>
<article class="case">
<div class="case-head">
<span class="lang java">Java</span>
<h3 data-en="Shotgun Surgery across six files" data-zh="散落六个文件的霰弹式修改" data-zhtw="散落六個檔案的散彈式修改" data-ja="6 ファイルにまたがるショットガン手術" data-ko="여섯 파일에 흩어진 산탄총 수술" data-es="Cirugía con escopeta en seis archivos">Shotgun Surgery across six files</h3>
<span class="hs mid">56 / 100</span>
</div>
<p class="case-summary" data-en="Adding a currency means editing six files in six unrelated layers — three Critical findings that share one root cause." data-zh="新增一种货币意味着要在六个不相关的层里修改六个文件——三条共享同一根因的 Critical 发现。" data-zhtw="新增一種貨幣意味著要在六個不相關的層裡修改六個檔案——三條共享同一根因的 Critical 發現。" data-ja="通貨を 1 つ追加するには、無関係な 6 つのレイヤーで 6 ファイルを編集することになります——同じ根本原因を共有する 3 件の Critical 指摘です。" data-ko="통화 하나를 추가하려면 서로 무관한 여섯 계층에서 여섯 파일을 수정해야 합니다 — 하나의 근본 원인을 공유하는 세 건의 Critical 발견입니다." data-es="Añadir una moneda implica editar seis archivos en seis capas no relacionadas — tres hallazgos Critical que comparten una sola causa raíz.">Adding a currency means editing six files in six unrelated layers — three Critical findings that share one root cause.</p>
<table class="dbt">
<thead><tr>
<th data-en="Risk" data-zh="风险" data-zhtw="風險" data-ja="リスク" data-ko="위험" data-es="Riesgo">Risk</th>
<th data-en="Findings" data-zh="发现数" data-zhtw="發現數" data-ja="指摘数" data-ko="발견 수" data-es="Hallazgos">Findings</th>
<th data-en="Avg Priority" data-zh="平均优先级" data-zhtw="平均優先級" data-ja="平均優先度" data-ko="평균 우선순위" data-es="Prioridad media">Avg Priority</th>
<th data-en="Classification" data-zh="分类" data-zhtw="分類" data-ja="分類" data-ko="분류" data-es="Clasificación">Classification</th>
</tr></thead>
<tbody>
<tr><td data-en="Change Propagation" data-zh="变更扩散" data-zhtw="變更擴散" data-ja="変更の波及" data-ko="변경 전파" data-es="Propagación de cambios">Change Propagation</td><td>2</td><td>6.5</td><td data-en="Mixed (1 Critical + 1 Scheduled)" data-zh="混合(1 严重 + 1 计划处理)" data-zhtw="混合(1 嚴重 + 1 計畫處理)" data-ja="混在(重大 1 + 計画対応 1" data-ko="혼합 (심각 1 + 예정 1)" data-es="Mixto (1 Crítico + 1 Programado)">Mixed (1 Critical + 1 Scheduled)</td></tr>
<tr><td data-en="Knowledge Duplication" data-zh="知识重复" data-zhtw="知識重複" data-ja="知識の重複" data-ko="지식 중복" data-es="Duplicación de conocimiento">Knowledge Duplication</td><td>1</td><td>9.0</td><td data-en="Critical" data-zh="严重" data-zhtw="嚴重" data-ja="重大" data-ko="심각" data-es="Crítico">Critical</td></tr>
<tr><td data-en="Domain Model Distortion" data-zh="领域模型失真" data-zhtw="領域模型失真" data-ja="ドメインモデルの歪み" data-ko="도메인 모델 왜곡" data-es="Distorsión del modelo de dominio">Domain Model Distortion</td><td>1</td><td>9.0</td><td data-en="Critical" data-zh="严重" data-zhtw="嚴重" data-ja="重大" data-ko="심각" data-es="Crítico">Critical</td></tr>
<tr><td data-en="Cognitive Overload" data-zh="认知过载" data-zhtw="認知過載" data-ja="認知的過負荷" data-ko="인지 과부하" data-es="Sobrecarga cognitiva">Cognitive Overload</td><td>1</td><td>6.0</td><td data-en="Scheduled" data-zh="计划处理" data-zhtw="計畫處理" data-ja="計画対応" data-ko="예정" data-es="Programado">Scheduled</td></tr>
</tbody>
</table>
<div class="finding">
<h4 data-en="🔴 Change Propagation — Shotgun Surgery across six modules <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-zh="🔴 变更扩散 — 散落六个模块的霰弹式修改 <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-zhtw="🔴 變更擴散 — 散落六個模組的散彈式修改 <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-ja="🔴 変更の波及 — 6 モジュールにまたがるショットガン手術 <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-ko="🔴 변경 전파 — 여섯 모듈에 흩어진 산탄총 수술 <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-es="🔴 Propagación de cambios — Cirugía con escopeta en seis módulos <span class=&quot;pri&quot;>Pain × Spread: 9</span>">🔴 Change Propagation — Shotgun Surgery across six modules <span class="pri">Pain × Spread: 9</span></h4>
<div class="ln sy" data-en="<b>Symptom:</b> Adding EUR requires editing 6 files in 6 distinct layers with no architectural relationship." data-zh="<b>症状:</b> 新增 EUR 需要在 6 个互不相关的层里修改 6 个文件。" data-zhtw="<b>症狀:</b> 新增 EUR 需要在 6 個互不相關的層裡修改 6 個檔案。" data-ja="<b>症状:</b> EUR の追加には、アーキテクチャ上の関連がない 6 つのレイヤーで 6 ファイルを編集する必要があります。" data-ko="<b>증상:</b> EUR를 추가하려면 아키텍처상 관련이 없는 여섯 계층에서 여섯 파일을 수정해야 합니다." data-es="<b>Síntoma:</b> Añadir EUR requiere editar 6 archivos en 6 capas distintas sin relación arquitectónica."><b>Symptom:</b> Adding EUR requires editing 6 files in 6 distinct layers with no architectural relationship.</div>
<div class="ln sr" data-en="<b>Source:</b> Fowler — Refactoring — Shotgun Surgery; Hunt &amp; Thomas — Orthogonality" data-zh="<b>根源:</b> Fowler — Refactoring — 霰弹式修改(Shotgun Surgery);Hunt &amp; Thomas — 正交性(Orthogonality" data-zhtw="<b>根源:</b> Fowler — Refactoring — 散彈式修改(Shotgun Surgery);Hunt &amp; Thomas — 正交性(Orthogonality" data-ja="<b>根源:</b> Fowler — Refactoring — ショットガン手術(Shotgun Surgery);Hunt &amp; Thomas — 直交性(Orthogonality" data-ko="<b>근원:</b> Fowler — Refactoring — 산탄총 수술(Shotgun Surgery); Hunt &amp; Thomas — 직교성(Orthogonality)" data-es="<b>Origen:</b> Fowler — Refactoring — Shotgun Surgery (Cirugía con escopeta); Hunt &amp; Thomas — Orthogonality (Ortogonalidad)"><b>Source:</b> Fowler — Refactoring — Shotgun Surgery; Hunt &amp; Thomas — Orthogonality</div>
<div class="ln rx" data-en="<b>Remedy:</b> Introduce a <code>Money</code> value object and a <code>MoneyFormatter</code> service." data-zh="<b>修复:</b> 引入一个 <code>Money</code> 值对象和一个 <code>MoneyFormatter</code> 服务。" data-zhtw="<b>對策:</b> 引入一個 <code>Money</code> 值物件和一個 <code>MoneyFormatter</code> 服務。" data-ja="<b>対策:</b> <code>Money</code> 値オブジェクトと <code>MoneyFormatter</code> サービスを導入します。" data-ko="<b>처방:</b> <code>Money</code> 값 객체와 <code>MoneyFormatter</code> 서비스를 도입하세요." data-es="<b>Remedio:</b> Introduce un objeto de valor <code>Money</code> y un servicio <code>MoneyFormatter</code>."><b>Remedy:</b> Introduce a <code>Money</code> value object and a <code>MoneyFormatter</code> service.</div>
</div>
<div class="finding">
<h4 data-en="🔴 Knowledge Duplication — <code>$</code> as a magic literal in five files <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-zh="🔴 知识重复 — <code>$</code> 作为魔法字面量散落在五个文件 <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-zhtw="🔴 知識重複 — <code>$</code> 作為魔法字面量散落在五個檔案 <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-ja="🔴 知識の重複 — <code>$</code> がマジックリテラルとして 5 ファイルに散在 <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-ko="🔴 지식 중복 — <code>$</code>가 매직 리터럴로 다섯 파일에 흩어짐 <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-es="🔴 Duplicación de conocimiento — <code>$</code> como literal mágico en cinco archivos <span class=&quot;pri&quot;>Pain × Spread: 9</span>">🔴 Knowledge Duplication — <code>$</code> as a magic literal in five files <span class="pri">Pain × Spread: 9</span></h4>
<div class="ln sy" data-en="<b>Symptom:</b> The string <code>&quot;$&quot;</code> appears in 5 independent locations with no shared constant." data-zh="<b>症状:</b> 字符串 <code>&quot;$&quot;</code> 出现在 5 个互不相关的位置,没有共享常量。" data-zhtw="<b>症狀:</b> 字串 <code>&quot;$&quot;</code> 出現在 5 個互不相關的位置,沒有共享常數。" data-ja="<b>症状:</b> 文字列 <code>&quot;$&quot;</code> が共有定数を持たないまま 5 つの独立した箇所に現れています。" data-ko="<b>증상:</b> 문자열 <code>&quot;$&quot;</code>가 공유 상수 없이 다섯 개의 독립된 위치에 나타납니다." data-es="<b>Síntoma:</b> La cadena <code>&quot;$&quot;</code> aparece en 5 ubicaciones independientes sin una constante compartida."><b>Symptom:</b> The string <code>"$"</code> appears in 5 independent locations with no shared constant.</div>
<div class="ln sr" data-en="<b>Source:</b> Hunt &amp; Thomas — DRY; McConnell — Code Complete — Ch. 12" data-zh="<b>根源:</b> Hunt &amp; Thomas — DRYMcConnell — Code Complete — 第 12 章" data-zhtw="<b>根源:</b> Hunt &amp; Thomas — DRYMcConnell — Code Complete — 第 12 章" data-ja="<b>根源:</b> Hunt &amp; Thomas — DRYMcConnell — Code Complete — 第 12 章" data-ko="<b>근원:</b> Hunt &amp; Thomas — DRY; McConnell — Code Complete — 12장" data-es="<b>Origen:</b> Hunt &amp; Thomas — DRY; McConnell — Code Complete — Cap. 12"><b>Source:</b> Hunt &amp; Thomas — DRY; McConnell — Code Complete — Ch. 12</div>
<div class="ln rx" data-en="<b>Remedy:</b> Use <code>Currency.getSymbol(Locale)</code> in <code>MoneyFormatter</code>; remove all <code>&quot;$&quot;</code> literals." data-zh="<b>修复:</b> 在 <code>MoneyFormatter</code> 中使用 <code>Currency.getSymbol(Locale)</code>;移除所有 <code>&quot;$&quot;</code> 字面量。" data-zhtw="<b>對策:</b> 在 <code>MoneyFormatter</code> 中使用 <code>Currency.getSymbol(Locale)</code>;移除所有 <code>&quot;$&quot;</code> 字面量。" data-ja="<b>対策:</b> <code>MoneyFormatter</code> で <code>Currency.getSymbol(Locale)</code> を使い、すべての <code>&quot;$&quot;</code> リテラルを削除します。" data-ko="<b>처방:</b> <code>MoneyFormatter</code>에서 <code>Currency.getSymbol(Locale)</code>를 사용하고 모든 <code>&quot;$&quot;</code> 리터럴을 제거하세요." data-es="<b>Remedio:</b> Usa <code>Currency.getSymbol(Locale)</code> en <code>MoneyFormatter</code>; elimina todos los literales <code>&quot;$&quot;</code>."><b>Remedy:</b> Use <code>Currency.getSymbol(Locale)</code> in <code>MoneyFormatter</code>; remove all <code>"$"</code> literals.</div>
</div>
<div class="finding">
<h4 data-en="🔴 Domain Model Distortion — No <code>Money</code> type exists <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-zh="🔴 领域模型失真 — 不存在 <code>Money</code> 类型 <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-zhtw="🔴 領域模型失真 — 不存在 <code>Money</code> 型別 <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-ja="🔴 ドメインモデルの歪み — <code>Money</code> 型が存在しない <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-ko="🔴 도메인 모델 왜곡 — <code>Money</code> 타입이 존재하지 않음 <span class=&quot;pri&quot;>Pain × Spread: 9</span>" data-es="🔴 Distorsión del modelo de dominio — No existe un tipo <code>Money</code> <span class=&quot;pri&quot;>Pain × Spread: 9</span>">🔴 Domain Model Distortion — No <code>Money</code> type exists <span class="pri">Pain × Spread: 9</span></h4>
<div class="ln sy" data-en="<b>Symptom:</b> All price/amount fields are raw <code>double</code>." data-zh="<b>症状:</b> 所有价格/金额字段都是裸 <code>double</code>。" data-zhtw="<b>症狀:</b> 所有價格/金額欄位都是裸 <code>double</code>。" data-ja="<b>症状:</b> すべての価格・金額フィールドが生の <code>double</code> です。" data-ko="<b>증상:</b> 모든 가격/금액 필드가 원시 <code>double</code>입니다." data-es="<b>Síntoma:</b> Todos los campos de precio/importe son <code>double</code> sin envolver."><b>Symptom:</b> All price/amount fields are raw <code>double</code>.</div>
<div class="ln sr" data-en="<b>Source:</b> Evans — DDD — Domain Model; Fowler — Refactoring — Data Class" data-zh="<b>根源:</b> Evans — DDD — 领域模型(Domain Model);Fowler — Refactoring — 数据类(Data Class" data-zhtw="<b>根源:</b> Evans — DDD — 領域模型(Domain Model);Fowler — Refactoring — 資料類別(Data Class" data-ja="<b>根源:</b> Evans — DDD — ドメインモデル(Domain Model);Fowler — Refactoring — データクラス(Data Class" data-ko="<b>근원:</b> Evans — DDD — 도메인 모델(Domain Model); Fowler — Refactoring — 데이터 클래스(Data Class)" data-es="<b>Origen:</b> Evans — DDD — Domain Model (Modelo de dominio); Fowler — Refactoring — Data Class (Clase de datos)"><b>Source:</b> Evans — DDD — Domain Model; Fowler — Refactoring — Data Class</div>
<div class="ln rx" data-en="<b>Remedy:</b> Introduce <code>record Money(BigDecimal amount, Currency currency)</code>." data-zh="<b>修复:</b> 引入 <code>record Money(BigDecimal amount, Currency currency)</code>。" data-zhtw="<b>對策:</b> 引入 <code>record Money(BigDecimal amount, Currency currency)</code>。" data-ja="<b>対策:</b> <code>record Money(BigDecimal amount, Currency currency)</code> を導入します。" data-ko="<b>처방:</b> <code>record Money(BigDecimal amount, Currency currency)</code>를 도입하세요." data-es="<b>Remedio:</b> Introduce <code>record Money(BigDecimal amount, Currency currency)</code>."><b>Remedy:</b> Introduce <code>record Money(BigDecimal amount, Currency currency)</code>.</div>
</div>
<div class="note" data-en="<b>Recommended focus:</b> All three Critical findings share one root cause — the absence of a <code>Money</code> value object. One intervention collapses three findings." data-zh="<b>建议聚焦:</b> 三条 Critical 发现共享同一根因——缺少 <code>Money</code> 值对象。一次干预即可消解三条发现。" data-zhtw="<b>建議聚焦:</b> 三條 Critical 發現共享同一根因——缺少 <code>Money</code> 值物件。一次介入即可消解三條發現。" data-ja="<b>推奨フォーカス:</b> 3 件の Critical 指摘は同じ根本原因——<code>Money</code> 値オブジェクトの欠如——を共有しています。一度の介入で 3 件の指摘が解消されます。" data-ko="<b>권장 초점:</b> 세 건의 Critical 발견은 하나의 근본 원인—<code>Money</code> 값 객체의 부재—을 공유합니다. 한 번의 개입으로 세 발견이 한꺼번에 해소됩니다." data-es="<b>Foco recomendado:</b> Los tres hallazgos Critical comparten una sola causa raíz — la ausencia de un objeto de valor <code>Money</code>. Una sola intervención colapsa tres hallazgos."><b>Recommended focus:</b> All three Critical findings share one root cause — the absence of a <code>Money</code> value object. One intervention collapses three findings.</div>
</article>
</section>
<!-- ============ TEST QUALITY ============ -->
<section class="mode-group" data-mode="test">
<h2 class="mode-title"><span data-en="Test Quality Review" data-zh="测试质量审查" data-zhtw="測試品質審查" data-ja="テスト品質レビュー" data-ko="테스트 품질 리뷰" data-es="Revisión de Calidad de Pruebas">Test Quality Review</span> <span class="badge">Mode 4</span></h2>
<p class="mode-sub" data-en="Audits an existing suite against six test-space decay risks (T1T6)." data-zh="对照六种测试空间衰退风险(T1–T6)审计现有测试套件。" data-zhtw="對照六種測試空間衰退風險(T1–T6)審計現有測試套件。" data-ja="6 つのテスト領域の劣化リスク(T1–T6)に照らして既存のスイートを監査します。" data-ko="여섯 가지 테스트 영역 부패 위험(T1–T6)에 대해 기존 스위트를 감사합니다." data-es="Audita una suite existente frente a seis riesgos de decadencia del espacio de pruebas (T1T6).">Audits an existing suite against six test-space decay risks (T1T6).</p>
<article class="case">
<div class="case-head">
<span class="lang ts">TypeScript</span>
<h3 data-en="Mock abuse" data-zh="Mock 滥用" data-zhtw="Mock 濫用" data-ja="モックの乱用" data-ko="목 남용" data-es="Abuso de mocks">Mock abuse</h3>
<span class="hs mid">60 / 100</span>
</div>
<p class="case-summary" data-en="Seven mocks per test, 14 lines of setup vs 6 of assertions — the service is never tested against a real collaborator, and the return value is never checked." data-zh="每个测试七个 mock14 行 setup 对 6 行断言——该服务从未对真实协作者做过测试,返回值也从未被检查。" data-zhtw="每個測試七個 mock14 行 setup 對 6 行斷言——該服務從未對真實協作者做過測試,回傳值也從未被檢查。" data-ja="テストごとに 7 つのモック、14 行のセットアップに対して 6 行のアサーション——このサービスは実際の協力オブジェクトに対して一度もテストされず、戻り値も検証されていません。" data-ko="테스트당 일곱 개의 목, 설정 14줄 대 단언 6줄 — 이 서비스는 실제 협력자에 대해 한 번도 테스트되지 않으며 반환값도 검사되지 않습니다." data-es="Siete mocks por prueba, 14 líneas de preparación frente a 6 de aserciones — el servicio nunca se prueba contra un colaborador real y el valor de retorno nunca se comprueba.">Seven mocks per test, 14 lines of setup vs 6 of assertions — the service is never tested against a real collaborator, and the return value is never checked.</p>
<details>
<summary data-en="▸ Input test" data-zh="▸ 输入测试" data-zhtw="▸ 輸入測試" data-ja="▸ 入力テスト" data-ko="▸ 입력 테스트" data-es="▸ Prueba de entrada">▸ Input test</summary>
<pre class="code">it('should place an order successfully', () => {
const mockDb = mock&lt;Database&gt;();
const mockPayment = mock&lt;PaymentGateway&gt;();
const mockInventory = mock&lt;InventoryService&gt;();
const mockMailer = mock&lt;MailService&gt;();
const mockAudit = mock&lt;AuditLogger&gt;();
const mockCache = mock&lt;CacheService&gt;();
const mockMetrics = mock&lt;MetricsCollector&gt;();
const service = new OrderService(mockDb, mockPayment, mockInventory, mockMailer, mockAudit, mockCache, mockMetrics);
const result = await service.placeOrder('1', 'item-1', 2);
expect(mockPayment.charge).toHaveBeenCalledWith('ch_1', 2000);
expect(mockInventory.check).toHaveBeenCalledWith('item-1', 2);
expect(mockMailer.send).toHaveBeenCalled();
expect(mockAudit.log).toHaveBeenCalledWith('ORDER_PLACED', expect.anything());
expect(mockCache.invalidate).toHaveBeenCalledWith('orders:1');
expect(mockMetrics.increment).toHaveBeenCalledWith('orders.placed');
});</pre>
</details>
<div class="finding">
<h4 data-en="🔴 Mock Abuse — Seven mocks per test; setup dominates logic" data-zh="🔴 Mock 滥用 — 每个测试七个 mocksetup 盖过了逻辑" data-zhtw="🔴 Mock 濫用 — 每個測試七個 mocksetup 蓋過了邏輯" data-ja="🔴 モックの乱用 — テストごとに 7 つのモック;セットアップがロジックを圧倒" data-ko="🔴 목 남용 — 테스트당 일곱 개의 목; 설정이 로직을 압도함" data-es="🔴 Abuso de mocks — Siete mocks por prueba; la preparación domina la lógica">🔴 Mock Abuse — Seven mocks per test; setup dominates logic</h4>
<div class="ln sy" data-en="<b>Symptom:</b> 7 mock objects; 14 lines of setup vs 6 of assertions. No real collaborator is ever exercised." data-zh="<b>症状:</b> 7 个 mock 对象;14 行 setup 对 6 行断言。从未运行任何真实协作者。" data-zhtw="<b>症狀:</b> 7 個 mock 物件;14 行 setup 對 6 行斷言。從未執行任何真實協作者。" data-ja="<b>症状:</b> 7 つのモックオブジェクト;14 行のセットアップに対して 6 行のアサーション。実際の協力オブジェクトは一度も動かされません。" data-ko="<b>증상:</b> 일곱 개의 목 객체; 설정 14줄 대 단언 6줄. 실제 협력자는 한 번도 실행되지 않습니다." data-es="<b>Síntoma:</b> 7 objetos mock; 14 líneas de preparación frente a 6 de aserciones. Nunca se ejercita un colaborador real."><b>Symptom:</b> 7 mock objects; 14 lines of setup vs 6 of assertions. No real collaborator is ever exercised.</div>
<div class="ln sr" data-en="<b>Source:</b> Osherove — The Art of Unit Testing (mock count > 3); Meszaros — xUnit Test Patterns" data-zh="<b>根源:</b> Osherove — The Art of Unit Testingmock 数量 > 3);Meszaros — xUnit Test Patterns" data-zhtw="<b>根源:</b> Osherove — The Art of Unit Testingmock 數量 > 3);Meszaros — xUnit Test Patterns" data-ja="<b>根源:</b> Osherove — The Art of Unit Testing(モック数 > 3);Meszaros — xUnit Test Patterns" data-ko="<b>근원:</b> Osherove — The Art of Unit Testing(목 개수 > 3); Meszaros — xUnit Test Patterns" data-es="<b>Origen:</b> Osherove — The Art of Unit Testing (número de mocks > 3); Meszaros — xUnit Test Patterns"><b>Source:</b> Osherove — The Art of Unit Testing (mock count > 3); Meszaros — xUnit Test Patterns</div>
<div class="ln rx" data-en="<b>Remedy:</b> Reduce mocks to ≤ 3, use in-memory fakes, assert on <code>result</code> first." data-zh="<b>修复:</b> 将 mock 减少到 ≤ 3,使用内存级 fake,先对 <code>result</code> 断言。" data-zhtw="<b>對策:</b> 將 mock 減少到 ≤ 3,使用記憶體級 fake,先對 <code>result</code> 斷言。" data-ja="<b>対策:</b> モックを 3 つ以下に減らし、インメモリの fake を使い、まず <code>result</code> をアサートします。" data-ko="<b>처방:</b> 목을 3개 이하로 줄이고, 인메모리 fake를 사용하며, 먼저 <code>result</code>를 단언하세요." data-es="<b>Remedio:</b> Reduce los mocks a ≤ 3, usa fakes en memoria y verifica primero <code>result</code>."><b>Remedy:</b> Reduce mocks to ≤ 3, use in-memory fakes, assert on <code>result</code> first.</div>
</div>
<div class="finding">
<h4 data-en="🔴 Mock Abuse — All six assertions verify mock calls, not behaviour" data-zh="🔴 Mock 滥用 — 全部六条断言验证的是 mock 调用,而非行为" data-zhtw="🔴 Mock 濫用 — 全部六條斷言驗證的是 mock 呼叫,而非行為" data-ja="🔴 モックの乱用 — 6 つのアサーションはすべて振る舞いではなくモック呼び出しを検証" data-ko="🔴 목 남용 — 여섯 단언 모두 행위가 아니라 목 호출을 검증함" data-es="🔴 Abuso de mocks — Las seis aserciones verifican llamadas a mocks, no comportamiento">🔴 Mock Abuse — All six assertions verify mock calls, not behaviour</h4>
<div class="ln sy" data-en="<b>Symptom:</b> Every assertion is <code>toHaveBeenCalledWith</code>; <code>result</code> is captured but never asserted." data-zh="<b>症状:</b> 每条断言都是 <code>toHaveBeenCalledWith</code><code>result</code> 被捕获却从未被断言。" data-zhtw="<b>症狀:</b> 每條斷言都是 <code>toHaveBeenCalledWith</code><code>result</code> 被捕獲卻從未被斷言。" data-ja="<b>症状:</b> アサーションはすべて <code>toHaveBeenCalledWith</code> で、<code>result</code> は受け取られるものの一度もアサートされません。" data-ko="<b>증상:</b> 모든 단언이 <code>toHaveBeenCalledWith</code>이며, <code>result</code>는 받아두지만 한 번도 단언되지 않습니다." data-es="<b>Síntoma:</b> Cada aserción es <code>toHaveBeenCalledWith</code>; <code>result</code> se captura pero nunca se verifica."><b>Symptom:</b> Every assertion is <code>toHaveBeenCalledWith</code>; <code>result</code> is captured but never asserted.</div>
<div class="ln cq" data-en="<b>Consequence:</b> A <code>placeOrder</code> that calls every mock yet returns <code>null</code> or double-charges still passes." data-zh="<b>后果:</b> 一个调用了每个 mock 却返回 <code>null</code> 或重复扣款的 <code>placeOrder</code> 依然能通过测试。" data-zhtw="<b>後果:</b> 一個呼叫了每個 mock 卻回傳 <code>null</code> 或重複扣款的 <code>placeOrder</code> 依然能通過測試。" data-ja="<b>結果:</b> すべてのモックを呼び出しつつ <code>null</code> を返したり二重請求したりする <code>placeOrder</code> でもテストが通ってしまいます。" data-ko="<b>결과:</b> 모든 목을 호출하면서도 <code>null</code>을 반환하거나 이중 청구하는 <code>placeOrder</code>도 여전히 통과합니다." data-es="<b>Consecuencia:</b> Un <code>placeOrder</code> que llama a todos los mocks pero devuelve <code>null</code> o cobra dos veces sigue pasando."><b>Consequence:</b> A <code>placeOrder</code> that calls every mock yet returns <code>null</code> or double-charges still passes.</div>
<div class="ln rx" data-en="<b>Remedy:</b> Assert observable output: <code>expect(result.status).toBe('confirmed')</code>." data-zh="<b>修复:</b> 断言可观察的输出:<code>expect(result.status).toBe('confirmed')</code>。" data-zhtw="<b>對策:</b> 斷言可觀察的輸出:<code>expect(result.status).toBe('confirmed')</code>。" data-ja="<b>対策:</b> 観測可能な出力をアサートします:<code>expect(result.status).toBe('confirmed')</code>。" data-ko="<b>처방:</b> 관찰 가능한 출력을 단언하세요: <code>expect(result.status).toBe('confirmed')</code>." data-es="<b>Remedio:</b> Verifica la salida observable: <code>expect(result.status).toBe('confirmed')</code>."><b>Remedy:</b> Assert observable output: <code>expect(result.status).toBe('confirmed')</code>.</div>
</div>
</article>
<article class="case">
<div class="case-head">
<span class="lang py">Python</span>
<h3 data-en="Inverted test pyramid" data-zh="倒置的测试金字塔" data-zhtw="倒置的測試金字塔" data-ja="逆さまのテストピラミッド" data-ko="역전된 테스트 피라미드" data-es="Pirámide de pruebas invertida">Inverted test pyramid</h3>
<span class="hs mid">55 / 100</span>
</div>
<p class="case-summary" data-en="Only 16% of tests are unit tests; an E2E-heavy suite takes ~9 minutes and blocks fast CI feedback." data-zh="只有 16% 的测试是单元测试;偏重 E2E 的套件耗时约 9 分钟,阻碍了 CI 的快速反馈。" data-zhtw="只有 16% 的測試是單元測試;偏重 E2E 的套件耗時約 9 分鐘,阻礙了 CI 的快速回饋。" data-ja="ユニットテストは全体のわずか 16%。E2E に偏ったスイートは約 9 分かかり、CI の高速なフィードバックを妨げます。" data-ko="단위 테스트는 전체의 16%에 불과합니다; E2E에 치우친 스위트는 약 9분이 걸려 CI의 빠른 피드백을 가로막습니다." data-es="Solo el 16% de las pruebas son unitarias; una suite cargada de E2E tarda ~9 minutos y bloquea la retroalimentación rápida de CI.">Only 16% of tests are unit tests; an E2E-heavy suite takes ~9 minutes and blocks fast CI feedback.</p>
<details>
<summary data-en="▸ Suite overview" data-zh="▸ 套件概览" data-zhtw="▸ 套件概覽" data-ja="▸ スイート概要" data-ko="▸ 스위트 개요" data-es="▸ Resumen de la suite">▸ Suite overview</summary>
<pre class="code">tests/
├── e2e/ 47 tests, avg 8s each (~6 min)
├── integration/ 83 tests, avg 2s each (~3 min)
└── unit/ 24 tests, avg 10ms each (~0.2s)
Total: 154 tests, ~9 min
Actual ratio: Unit 16% : Integration 54% : E2E 30%
Target ratio: Unit 70% : Integration 20% : E2E 10%</pre>
</details>
<div class="finding">
<h4 data-en="🔴 Architecture Mismatch — Fully inverted test pyramid" data-zh="🔴 架构错配 — 完全倒置的测试金字塔" data-zhtw="🔴 架構錯配 — 完全倒置的測試金字塔" data-ja="🔴 アーキテクチャの不一致 — 完全に逆さまのテストピラミッド" data-ko="🔴 아키텍처 불일치 — 완전히 역전된 테스트 피라미드" data-es="🔴 Desajuste de arquitectura — Pirámide de pruebas totalmente invertida">🔴 Architecture Mismatch — Fully inverted test pyramid</h4>
<div class="ln sy" data-en="<b>Symptom:</b> Only 24 of 154 tests (16%) are unit tests; E2E + integration = 84%." data-zh="<b>症状:</b> 154 个测试中只有 24 个(16%)是单元测试;E2E + 集成测试占 84%。" data-zhtw="<b>症狀:</b> 154 個測試中只有 24 個(16%)是單元測試;E2E + 整合測試佔 84%。" data-ja="<b>症状:</b> 154 件のテストのうちユニットテストはわずか 24 件(16%);E2E + 統合テストで 84% です。" data-ko="<b>증상:</b> 154개 테스트 중 단위 테스트는 24개(16%)뿐이며, E2E + 통합이 84%입니다." data-es="<b>Síntoma:</b> Solo 24 de 154 pruebas (16%) son unitarias; E2E + integración = 84%."><b>Symptom:</b> Only 24 of 154 tests (16%) are unit tests; E2E + integration = 84%.</div>
<div class="ln sr" data-en="<b>Source:</b> Google — How Google Tests Software — 70:20:10; Meszaros — xUnit Test Patterns" data-zh="<b>根源:</b> Google — How Google Tests Software — 70:20:10Meszaros — xUnit Test Patterns" data-zhtw="<b>根源:</b> Google — How Google Tests Software — 70:20:10Meszaros — xUnit Test Patterns" data-ja="<b>根源:</b> Google — How Google Tests Software — 70:20:10Meszaros — xUnit Test Patterns" data-ko="<b>근원:</b> Google — How Google Tests Software — 70:20:10; Meszaros — xUnit Test Patterns" data-es="<b>Origen:</b> Google — How Google Tests Software — 70:20:10; Meszaros — xUnit Test Patterns"><b>Source:</b> Google — How Google Tests Software — 70:20:10; Meszaros — xUnit Test Patterns</div>
<div class="ln rx" data-en="<b>Remedy:</b> Target 70% unit; reduce E2E to 58 critical smoke tests." data-zh="<b>修复:</b> 目标 70% 单元测试;将 E2E 缩减到 5–8 个关键冒烟测试。" data-zhtw="<b>對策:</b> 目標 70% 單元測試;將 E2E 縮減到 5–8 個關鍵冒煙測試。" data-ja="<b>対策:</b> ユニットテスト 70% を目標にし、E2E を 5~8 件の重要なスモークテストに削減します。" data-ko="<b>처방:</b> 단위 테스트 70%를 목표로 하고, E2E를 핵심 스모크 테스트 5~8개로 줄이세요." data-es="<b>Remedio:</b> Apunta a un 70% de pruebas unitarias; reduce E2E a 58 pruebas de humo críticas."><b>Remedy:</b> Target 70% unit; reduce E2E to 58 critical smoke tests.</div>
</div>
<div class="finding">
<h4 data-en="🔴 Architecture Mismatch — 9-minute suite blocks CI fast-feedback" data-zh="🔴 架构错配 — 9 分钟的套件阻碍了 CI 快速反馈" data-zhtw="🔴 架構錯配 — 9 分鐘的套件阻礙了 CI 快速回饋" data-ja="🔴 アーキテクチャの不一致 — 9 分のスイートが CI の高速フィードバックを妨げる" data-ko="🔴 아키텍처 불일치 — 9분짜리 스위트가 CI 빠른 피드백을 가로막음" data-es="🔴 Desajuste de arquitectura — La suite de 9 minutos bloquea la retroalimentación rápida de CI">🔴 Architecture Mismatch — 9-minute suite blocks CI fast-feedback</h4>
<div class="ln sy" data-en="<b>Symptom:</b> Full suite ~542s, dominated by 8s E2E tests." data-zh="<b>症状:</b> 完整套件约 542 秒,由耗时 8 秒的 E2E 测试主导。" data-zhtw="<b>症狀:</b> 完整套件約 542 秒,由耗時 8 秒的 E2E 測試主導。" data-ja="<b>症状:</b> スイート全体で約 542 秒、8 秒かかる E2E テストが大半を占めます。" data-ko="<b>증상:</b> 전체 스위트 약 542초로, 8초짜리 E2E 테스트가 대부분을 차지합니다." data-es="<b>Síntoma:</b> Suite completa ~542s, dominada por pruebas E2E de 8s."><b>Symptom:</b> Full suite ~542s, dominated by 8s E2E tests.</div>
<div class="ln rx" data-en="<b>Remedy:</b> Split CI: (1) unit only, &lt; 60s, blocks merge; (2) integration + E2E async, non-blocking." data-zh="<b>修复:</b> 拆分 CI(1) 仅单元测试,&lt; 60 秒,阻塞合并;(2) 集成 + E2E 异步运行,不阻塞。" data-zhtw="<b>對策:</b> 拆分 CI(1) 僅單元測試,&lt; 60 秒,阻塞合併;(2) 整合 + E2E 非同步執行,不阻塞。" data-ja="<b>対策:</b> CI を分割します:(1) ユニットのみ、&lt; 60 秒、マージをブロック;(2) 統合 + E2E は非同期で非ブロッキング。" data-ko="<b>처방:</b> CI를 분리하세요: (1) 단위만, &lt; 60초, 병합 차단; (2) 통합 + E2E 비동기, 비차단." data-es="<b>Remedio:</b> Divide el CI: (1) solo unitarias, &lt; 60s, bloquea el merge; (2) integración + E2E asíncronas, sin bloquear."><b>Remedy:</b> Split CI: (1) unit only, &lt; 60s, blocks merge; (2) integration + E2E async, non-blocking.</div>
</div>
<div class="finding">
<h4 data-en="🟡 Coverage Illusion — Core domain untested at unit level" data-zh="🟡 覆盖率幻觉 — 核心领域在单元层面未被测试" data-zhtw="🟡 覆蓋率幻覺 — 核心領域在單元層面未被測試" data-ja="🟡 カバレッジの錯覚 — コアドメインがユニットレベルで未テスト" data-ko="🟡 커버리지 착시 — 핵심 도메인이 단위 수준에서 미검증" data-es="🟡 Ilusión de cobertura — El dominio central no se prueba a nivel unitario">🟡 Coverage Illusion — Core domain untested at unit level</h4>
<div class="ln sy" data-en="<b>Symptom:</b> <code>tests/unit/</code> covers only validators and formatters — no checkout, login, order, or payment." data-zh="<b>症状:</b> <code>tests/unit/</code> 只覆盖了校验器和格式化器——没有结账、登录、订单或支付。" data-zhtw="<b>症狀:</b> <code>tests/unit/</code> 只覆蓋了驗證器和格式化器——沒有結帳、登入、訂單或支付。" data-ja="<b>症状:</b> <code>tests/unit/</code> はバリデータとフォーマッタしかカバーしておらず、チェックアウト・ログイン・注文・決済はありません。" data-ko="<b>증상:</b> <code>tests/unit/</code>는 검증기와 포매터만 커버하며 결제, 로그인, 주문, 결제 처리는 없습니다." data-es="<b>Síntoma:</b> <code>tests/unit/</code> solo cubre validadores y formateadores — nada de checkout, login, pedidos ni pagos."><b>Symptom:</b> <code>tests/unit/</code> covers only validators and formatters — no checkout, login, order, or payment.</div>
<div class="ln sr" data-en="<b>Source:</b> Feathers — Working Effectively with Legacy Code" data-zh="<b>根源:</b> Feathers — Working Effectively with Legacy Code" data-zhtw="<b>根源:</b> Feathers — Working Effectively with Legacy Code" data-ja="<b>根源:</b> Feathers — Working Effectively with Legacy Code" data-ko="<b>근원:</b> Feathers — Working Effectively with Legacy Code" data-es="<b>Origen:</b> Feathers — Working Effectively with Legacy Code"><b>Source:</b> Feathers — Working Effectively with Legacy Code</div>
<div class="ln rx" data-en="<b>Remedy:</b> Start with <code>test_checkout_flow.py</code> and <code>test_payment_api.py</code>." data-zh="<b>修复:</b> 从 <code>test_checkout_flow.py</code> 和 <code>test_payment_api.py</code> 开始。" data-zhtw="<b>對策:</b> 從 <code>test_checkout_flow.py</code> 和 <code>test_payment_api.py</code> 開始。" data-ja="<b>対策:</b> <code>test_checkout_flow.py</code> と <code>test_payment_api.py</code> から始めます。" data-ko="<b>처방:</b> <code>test_checkout_flow.py</code>와 <code>test_payment_api.py</code>부터 시작하세요." data-es="<b>Remedio:</b> Empieza con <code>test_checkout_flow.py</code> y <code>test_payment_api.py</code>."><b>Remedy:</b> Start with <code>test_checkout_flow.py</code> and <code>test_payment_api.py</code>.</div>
</div>
</article>
</section>
</main>
<footer>
<div class="foot-in">
<div class="foot-links">
<a href="index.html" data-en="Home" data-zh="首页" data-zhtw="首頁" data-ja="ホーム" data-ko="홈" data-es="Inicio">Home</a>
<a href="https://github.com/hyhmrright/brooks-lint" target="_blank" rel="noopener">GitHub</a>
<a href="https://github.com/hyhmrright/brooks-lint/blob/main/docs/gallery.md" target="_blank" rel="noopener" data-en="Gallery source (Markdown)" data-zh="画廊源文件(Markdown" data-zhtw="範例展示原始檔(Markdown" data-ja="ギャラリーのソース(Markdown" data-ko="갤러리 소스 (Markdown)" data-es="Fuente de la galería (Markdown)">Gallery source (Markdown)</a>
</div>
<div class="foot-note" data-en="MIT License — Built by hyhmrright" data-zh="MIT 许可证 — 由 hyhmrright 打造" data-zhtw="MIT 授權 — 由 hyhmrright 打造" data-ja="MIT ライセンス — hyhmrright が制作" data-ko="MIT 라이선스 — hyhmrright 제작" data-es="Licencia MIT — Creado por hyhmrright">MIT License — Built by hyhmrright</div>
</div>
</footer>
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({
startOnLoad: true,
theme: 'base',
themeVariables: {
fontFamily: 'ui-monospace, Menlo, monospace',
primaryColor: '#fffdf8', primaryBorderColor: '#e3d8c2', primaryTextColor: '#2a2520',
lineColor: '#9c8f74', clusterBkg: '#f3ead8', clusterBorder: '#e3d8c2'
}
});
</script>
<script>
(function () {
var btns = document.querySelectorAll('#filters button');
var groups = document.querySelectorAll('.mode-group');
btns.forEach(function (b) {
b.addEventListener('click', function () {
btns.forEach(function (x) { x.classList.remove('active'); });
b.classList.add('active');
var f = b.getAttribute('data-filter');
groups.forEach(function (g) {
g.classList.toggle('hidden', f !== 'all' && g.getAttribute('data-mode') !== f);
});
});
});
})();
</script>
<script>
(function () {
var STORE = "brooks-lint-lang";
var HTML_LANG = { en:"en", zh:"zh-CN", zhtw:"zh-TW", ja:"ja", ko:"ko", es:"es" };
function apply(lang) {
document.documentElement.lang = HTML_LANG[lang] || "en";
document.querySelectorAll("[data-en]").forEach(function (el) {
var v = el.getAttribute("data-" + lang);
if (v != null) el.innerHTML = v;
});
document.querySelectorAll(".lang-toggle button").forEach(function (b) {
b.classList.toggle("active", b.getAttribute("data-set-lang") === lang);
});
try { localStorage.setItem(STORE, lang); } catch (e) {}
}
document.querySelectorAll("[data-set-lang]").forEach(function (b) {
b.addEventListener("click", function () { apply(b.getAttribute("data-set-lang")); });
});
var saved;
try { saved = localStorage.getItem(STORE); } catch (e) {}
if (!saved) {
var nav = (navigator.language || "").toLowerCase();
saved = (nav.indexOf("zh-tw")===0||nav.indexOf("zh-hant")===0||nav.indexOf("zh-hk")===0||nav.indexOf("zh-mo")===0) ? "zhtw"
: nav.indexOf("zh")===0 ? "zh"
: nav.indexOf("ja")===0 ? "ja"
: nav.indexOf("ko")===0 ? "ko"
: nav.indexOf("es")===0 ? "es"
: "en";
}
apply(saved);
})();
</script>
</body>
</html>