feat(appendix): 重构工程实践章节,添加交互式演示组件

## 新增组件 (14个)
- CodeSmellDemo.vue: 代码异味识别演示
- DecisionMatrixDemo.vue: 决策矩阵工具
- DesignPatternCatalogDemo.vue: 设计模式目录
- DocStructureDemo.vue: 文档结构示例
- LicenseComparisonDemo.vue: 开源许可证对比
- OpenSourceWorkflowDemo.vue: 开源协作流程
- PatternPlaygroundDemo.vue: 设计模式演练场
- RefactoringDemo.vue: 重构实战演示
- SecurityChecklistDemo.vue: 安全检查清单
- TDDCycleDemo.vue: TDD 循环演示
- TechRadarDemo.vue: 技术雷达图
- TechWritingPracticeDemo.vue: 技术写作实践
- TestPyramidDemo.vue: 测试金字塔
- WebSecurityDemo.vue: Web 安全演示

## 文档更新 (7篇)
- code-quality-refactoring.md: 代码质量与重构
- design-patterns.md: 设计模式
- open-source-collaboration.md: 开源协作
- security-thinking.md: 安全思维
- technical-writing.md: 技术写作
- technology-selection.md: 技术选型
- testing-strategies.md: 测试策略

## 其他变更
- 将 browser-as-os.md 内容合并到 computer-networks.md
- 更新 .gitignore 和 theme/index.js
This commit is contained in:
sanbuphy
2026-02-24 12:54:06 +08:00
parent baba96e7ed
commit f35cddeb8b
28 changed files with 5149 additions and 1080 deletions
@@ -0,0 +1,267 @@
<template>
<div class="refactoring-demo">
<div class="demo-label">重构手法对比演示 选择一种手法查看前后对比</div>
<div class="tabs">
<button
v-for="(item, i) in techniques"
:key="i"
:class="['tab-btn', { active: activeTab === i }]"
@click="selectTab(i)"
>
{{ item.name }}
</button>
</div>
<div class="desc">{{ current.description }}</div>
<div class="compare-area">
<div class="compare-panel before">
<div class="panel-header">
<span class="dot red"></span> 重构前
</div>
<pre class="code-block"><template
v-for="(seg, j) in current.before"
:key="'b'+j"
><span :class="{ highlight: showHighlight && seg.changed }">{{ seg.text }}</span></template></pre>
</div>
<div class="arrow-col">
<span class="arrow-icon"></span>
</div>
<div class="compare-panel after">
<div class="panel-header">
<span class="dot green"></span> 重构后
</div>
<pre class="code-block"><template
v-for="(seg, j) in current.after"
:key="'a'+j"
><span :class="{ highlight: showHighlight && seg.changed }">{{ seg.text }}</span></template></pre>
</div>
</div>
<div class="tip-box">
<strong>要点</strong>{{ current.tip }}
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const activeTab = ref(0)
const showHighlight = ref(false)
function selectTab(i) {
activeTab.value = i
showHighlight.value = false
setTimeout(() => { showHighlight.value = true }, 300)
}
// 初始化高亮
setTimeout(() => { showHighlight.value = true }, 500)
const techniques = [
{
name: '提炼函数',
description: 'Extract Function:将一段代码从大函数中提取出来,放入一个命名清晰的新函数中。',
before: [
{ text: 'function printReport(invoice) {\n console.log("=== 账单 ===")\n' },
{ text: ' // 计算总额\n let total = 0\n for (let item of invoice.items) {\n total += item.price * item.qty\n }\n', changed: true },
{ text: ' console.log(`总计: ${total}`)\n}' }
],
after: [
{ text: 'function printReport(invoice) {\n console.log("=== 账单 ===")\n' },
{ text: ' const total = calcTotal(invoice.items)\n', changed: true },
{ text: ' console.log(`总计: ${total}`)\n}\n\n' },
{ text: 'function calcTotal(items) {\n return items.reduce(\n (s, i) => s + i.price * i.qty, 0\n )\n}', changed: true }
],
tip: '提炼函数是最常用的重构手法。好的函数名就是最好的注释——如果你需要写注释解释一段代码在做什么,那它就该被提炼成函数。'
},
{
name: '重命名变量',
description: 'Rename Variable:用清晰、有意义的名称替换含糊的变量名,让代码自解释。',
before: [
{ text: 'function calc(', changed: true },
{ text: 'a, b, c', changed: true },
{ text: ') {\n' },
{ text: ' const d = a * b\n const e = d * (1 - c)\n return e\n}', changed: true }
],
after: [
{ text: 'function calcOrderTotal(', changed: true },
{ text: 'price, quantity, discountRate', changed: true },
{ text: ') {\n' },
{ text: ' const subtotal = price * quantity\n const total = subtotal * (1 - discountRate)\n return total\n}', changed: true }
],
tip: '变量命名是程序员最重要的基本功之一。好的命名让代码像散文一样可读,差的命名让代码像密码一样难解。'
},
{
name: '消除重复',
description: 'Remove Duplication:将重复的逻辑抽取为共享函数或模板,遵循 DRY 原则。',
before: [
{ text: '// 员工报表\nfunction empReport(emp) {\n' },
{ text: ' return `${emp.name} | ${emp.dept} | ${emp.salary}`', changed: true },
{ text: '\n}\n\n// 经理报表\nfunction mgrReport(mgr) {\n' },
{ text: ' return `${mgr.name} | ${mgr.dept} | ${mgr.salary}`', changed: true },
{ text: '\n}' }
],
after: [
{ text: '' },
{ text: 'function formatReport(person) {\n return `${person.name} | ${person.dept} | ${person.salary}`\n}', changed: true },
{ text: '\n\n// 统一调用\n' },
{ text: 'formatReport(employee)\nformatReport(manager)', changed: true }
],
tip: 'DRYDon\'t Repeat Yourself)是软件工程的基本原则。每一处重复都是未来 bug 的温床——改了一处忘了另一处,就是典型的重复代码事故。'
},
{
name: '简化条件',
description: 'Simplify Conditional:用卫语句、策略模式等手法替代深层嵌套的 if-else,降低圈复杂度。',
before: [
{ text: 'function getDiscount(user) {\n' },
{ text: ' if (user.type === "vip") {\n if (user.years > 5) {\n return 0.3\n } else {\n return 0.2\n }\n } else {\n if (user.years > 3) {\n return 0.1\n } else {\n return 0\n }\n }', changed: true },
{ text: '\n}' }
],
after: [
{ text: 'function getDiscount(user) {\n' },
{ text: ' if (user.type === "vip" && user.years > 5) return 0.3\n if (user.type === "vip") return 0.2\n if (user.years > 3) return 0.1\n return 0', changed: true },
{ text: '\n}' }
],
tip: '卫语句(Guard Clause)通过提前返回来消除嵌套。扁平的代码结构比深层嵌套更容易理解和维护。'
}
]
const current = computed(() => techniques[activeTab.value])
</script>
<style scoped>
.refactoring-demo {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
background: var(--vp-c-bg-soft);
padding: 1rem 1.2rem;
margin: 1rem 0;
}
.demo-label {
font-size: 0.78rem;
font-weight: bold;
color: var(--vp-c-text-2);
margin-bottom: 0.75rem;
}
.tabs {
display: flex;
gap: 0.5rem;
margin-bottom: 0.6rem;
flex-wrap: wrap;
}
.tab-btn {
padding: 0.35rem 0.8rem;
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
background: var(--vp-c-bg);
color: var(--vp-c-text-2);
cursor: pointer;
font-size: 0.82rem;
transition: all 0.2s;
}
.tab-btn.active {
background: var(--vp-c-brand-1);
color: #fff;
border-color: var(--vp-c-brand-1);
}
.desc {
font-size: 0.82rem;
color: var(--vp-c-text-2);
margin-bottom: 0.8rem;
line-height: 1.5;
}
.compare-area {
display: flex;
gap: 0.5rem;
align-items: stretch;
}
@media (max-width: 640px) {
.compare-area {
flex-direction: column;
}
.arrow-col {
transform: rotate(90deg);
}
}
.compare-panel {
flex: 1;
min-width: 0;
}
.panel-header {
font-size: 0.75rem;
font-weight: 600;
color: var(--vp-c-text-2);
margin-bottom: 0.3rem;
display: flex;
align-items: center;
gap: 0.4rem;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
}
.dot.red { background: #ef4444; }
.dot.green { background: #22c55e; }
.code-block {
background: var(--vp-c-bg);
border: 1px solid var(--vp-c-divider);
border-radius: 6px;
padding: 0.8rem;
font-size: 0.75rem;
line-height: 1.6;
overflow-x: auto;
margin: 0;
white-space: pre;
font-family: 'Fira Code', 'Consolas', monospace;
min-height: 140px;
}
.highlight {
background: rgba(34, 197, 94, 0.15);
border-radius: 2px;
transition: background 0.6s ease;
}
.before .highlight {
background: rgba(239, 68, 68, 0.12);
}
.arrow-col {
display: flex;
align-items: center;
justify-content: center;
font-size: 1.4rem;
color: var(--vp-c-text-3);
padding: 0 0.2rem;
}
.tip-box {
margin-top: 0.8rem;
padding: 0.6rem 0.8rem;
background: rgba(59, 130, 246, 0.08);
border-left: 3px solid var(--vp-c-brand-1);
border-radius: 0 6px 6px 0;
font-size: 0.8rem;
color: var(--vp-c-text-2);
line-height: 1.6;
}
</style>