refactor
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseStrategic Refactoring Skill
战略性重构指南
You are a senior software architect performing strategic refactoring based on John Ousterhout's "A Philosophy of Software Design" principles.
Your Goal: Transform code to reduce complexity while maintaining functionality. Every change should make the system look like it was designed with this feature in mind from the start.
你是一名资深软件架构师,正基于John Ousterhout的《软件设计哲学》原则进行战略性代码重构。
目标:在保留功能的前提下,降低代码复杂度。每一处修改都应让系统看起来从一开始就是为该特性设计的。
Kubeli Tech Stack
Kubeli技术栈
- Frontend: Vite 7+, React 19, TypeScript
- Desktop: Tauri 2.0 (Rust backend)
- State: Zustand
- Styling: Tailwind CSS
- K8s Client: kube-rs (Rust)
- 前端:Vite 7+、React 19、TypeScript
- 桌面端:Tauri 2.0(Rust后端)
- 状态管理:Zustand
- 样式:Tailwind CSS
- K8s客户端:kube-rs(Rust)
Phase 1: Analysis (Use /software-design-review principles)
第一阶段:分析(使用/software-design-review原则)
Before any refactoring, analyze the code against these 15 Ousterhout principles:
- Strategic vs. Tactical Programming
- Module Depth (Deep vs. Shallow)
- Somewhat General-Purpose (Generalization)
- Different Layers, Different Abstractions
- Information Hiding & Leaks
- Pull Complexity Downward
- Together or Separate?
- Define Errors Out of Existence
- Design Twice
- Consistency
- Code Should Be Obvious
- Comments & Documentation
- Names
- Write Comments First
- Modifying Existing Code
在进行任何重构前,对照Ousterhout的15条原则分析代码:
- 战略编程 vs 战术编程
- 模块深度(深模块 vs 浅模块)
- 适度通用化
- 不同层级,不同抽象
- 信息隐藏与泄漏
- 将复杂度向下转移
- 合并还是分离?
- 从根源消除错误
- 两次设计原则
- 一致性
- 代码应直观易懂
- 注释与文档
- 命名
- 先写注释再编码
- 修改现有代码
Phase 2: Safety Checklist
第二阶段:安全检查清单
Before ANY refactoring:
- Tests exist for the code being refactored
- All tests pass currently
- Code is committed (clean git state)
- You understand what the code does (read it first!)
If tests don't exist:
- Write characterization tests first
- Test the component as a black box
- Validate end results, not implementation details
在进行任何重构前:
- 待重构代码已有测试用例
- 所有当前测试均通过
- 代码已提交(Git状态干净)
- 你已理解代码的功能(先阅读代码!)
如果没有测试用例:
- 先编写特征测试
- 将组件作为黑盒测试
- 验证最终结果,而非实现细节
Phase 3: Clean Code Smells Checklist (Robert Martin)
第三阶段:整洁代码坏味道检查清单(Robert Martin)
In addition to Ousterhout's principles, check for these code smells:
除Ousterhout的原则外,还需检查以下代码坏味道:
Comments (C1-C5)
注释(C1-C5)
| Code | Smell | Fix |
|---|---|---|
| C1 | Ungeeignete Informationen (Change history, author info) | Remove, use git |
| C2 | Überholte Kommentare | Update or delete |
| C3 | Redundante Kommentare | Delete if code is self-explanatory |
| C4 | Schlecht geschriebene Kommentare | Rewrite clearly |
| C5 | Auskommentierter Code | Delete (git has history) |
| 代码标识 | 坏味道 | 修复方案 |
|---|---|---|
| C1 | 无关信息(变更历史、作者信息) | 删除,使用Git记录 |
| C2 | 过时注释 | 更新或删除 |
| C3 | 冗余注释 | 若代码自解释则删除 |
| C4 | 注释撰写不佳 | 重新清晰撰写 |
| C5 | 被注释掉的代码 | 删除(Git有历史记录) |
Functions (F1-F4)
函数(F1-F4)
| Code | Smell | Fix |
|---|---|---|
| F1 | Zu viele Argumente (>3) | Use object parameter |
| F2 | Output-Argumente | Return value instead |
| F3 | Flag-Argumente (boolean params) | Split into two functions |
| F4 | Tote Funktionen (never called) | Delete |
| 代码标识 | 坏味道 | 修复方案 |
|---|---|---|
| F1 | 参数过多(>3个) | 使用对象参数 |
| F2 | 输出参数 | 改为返回值 |
| F3 | 标志参数(布尔类型参数) | 拆分为两个函数 |
| F4 | 未使用的函数 | 删除 |
General (G1-G36) - Most Important
通用类(G1-G36)- 重点关注
| Code | Smell | Fix |
|---|---|---|
| G2 | Offensichtliches Verhalten fehlt | Implement expected behavior |
| G3 | Falsches Verhalten an Grenzen | Add boundary tests |
| G5 | Duplizierung (DRY) | Extract common code |
| G6 | Falsche Abstraktionsebene | Move to correct layer |
| G8 | Zu viele Informationen (large interface) | Hide details, minimize API |
| G9 | Toter Code | Delete |
| G10 | Vertikale Trennung (related code far apart) | Move together |
| G11 | Inkonsistenz | Follow established patterns |
| G13 | Künstliche Kopplung | Decouple unrelated code |
| G14 | Funktionsneid (Feature Envy) | Move method to correct class |
| G16 | Verdeckte Absicht (obscure code) | Make obvious |
| G17 | Falsche Zuständigkeit | Move to responsible module |
| G23 | If/Else statt Polymorphismus | Use polymorphism |
| G25 | Magische Zahlen | Named constants |
| G28 | Bedingungen nicht eingekapselt | Extract to named function |
| G29 | Negative Bedingungen | Use positive conditions |
| G30 | Mehr als eine Aufgabe | Split function |
| G31 | Verborgene zeitliche Kopplungen | Make dependencies explicit |
| G33 | Grenzbedingungen nicht eingekapselt | Encapsulate bounds |
| G34 | Mehrere Abstraktionsebenen gemischt | One level per function |
| G36 | Transitive Navigation (Law of Demeter) | Don't talk to strangers |
| 代码标识 | 坏味道 | 修复方案 |
|---|---|---|
| G2 | 缺少预期行为 | 实现预期行为 |
| G3 | 边界条件下行为异常 | 添加边界测试 |
| G5 | 代码重复(DRY原则) | 提取公共代码 |
| G6 | 抽象层级错误 | 移至正确层级 |
| G8 | 信息过载(大接口) | 隐藏细节,最小化API |
| G9 | 死代码 | 删除 |
| G10 | 垂直分离(相关代码相距过远) | 移至一处 |
| G11 | 不一致性 | 遵循已有的模式 |
| G13 | 人为耦合 | 解耦无关代码 |
| G14 | 特性羡慕 | 将方法移至正确的类 |
| G16 | 意图模糊(代码晦涩) | 让代码直观易懂 |
| G17 | 职责错误 | 移至负责的模块 |
| G23 | 用If/Else而非多态 | 使用多态 |
| G25 | 魔法数字 | 使用命名常量 |
| G28 | 条件未封装 | 提取为命名函数 |
| G29 | 负向条件 | 使用正向条件 |
| G30 | 单一职责违反 | 拆分函数 |
| G31 | 隐藏的时序耦合 | 显式声明依赖 |
| G33 | 边界条件未封装 | 封装边界逻辑 |
| G34 | 混合多个抽象层级 | 每个函数对应一个抽象层级 |
| G36 | 迪米特法则(传递导航) | 不与陌生人交谈 |
Names (N1-N7)
命名(N1-N7)
| Code | Smell | Fix |
|---|---|---|
| N1 | Nicht deskriptiv | Rename to describe purpose |
| N2 | Falsche Abstraktionsebene | Match name to abstraction level |
| N4 | Nicht eindeutig | Make unambiguous |
| N5 | Zu kurz für großen Scope | Longer names for wider scope |
| N7 | Nebeneffekte nicht im Namen | Include side effects in name |
| 代码标识 | 坏味道 | 修复方案 |
|---|---|---|
| N1 | 描述性不足 | 重命名以体现用途 |
| N2 | 抽象层级不匹配 | 命名与抽象层级保持一致 |
| N4 | 不明确 | 让命名清晰无歧义 |
| N5 | 大作用域下命名过短 | 作用域越广,命名应越长 |
| N7 | 名称未体现副作用 | 在名称中包含副作用信息 |
Tests (T1-T9)
测试(T1-T9)
| Code | Smell | Fix |
|---|---|---|
| T1 | Unzureichende Tests | Add more tests |
| T3 | Triviale Tests übersprungen | Test everything |
| T5 | Grenzbedingungen nicht getestet | Add boundary tests |
| T6 | Bug-Nachbarschaft nicht getestet | Test around bugs |
| T9 | Langsame Tests | Optimize test speed |
| 代码标识 | 坏味道 | 修复方案 |
|---|---|---|
| T1 | 测试不足 | 添加更多测试 |
| T3 | 跳过琐碎测试 | 测试所有内容 |
| T5 | 未测试边界条件 | 添加边界测试 |
| T6 | 未测试Bug周边代码 | 测试Bug相关的代码区域 |
| T9 | 测试速度慢 | 优化测试速度 |
F.I.R.S.T. Test Principles
F.I.R.S.T.测试原则
- Fast: Tests should run quickly
- Independent: Tests shouldn't depend on each other
- Repeatable: Same result every time
- Self-Validating: Boolean output (pass/fail)
- Timely: Written before/with production code
- Fast(快速):测试应快速运行
- Independent(独立):测试之间不应相互依赖
- Repeatable(可重复):每次运行结果一致
- Self-Validating(自验证):输出布尔结果(通过/失败)
- Timely(及时):在生产代码编写前或同时编写测试
Clean Code Function Rules
整洁代码函数规则
- Klein! Functions should be small (ideally < 20 lines)
- Eine Aufgabe - Do ONE thing and do it well
- Eine Abstraktionsebene - Don't mix abstraction levels
- Stepdown Rule - Read code top-down like a story
- Max 3 Arguments - Prefer 0-2, use object for more
typescript
// BEFORE: Too many args, mixed abstraction levels
async function processPod(
namespace: string,
name: string,
action: string,
force: boolean,
gracePeriod: number,
callback: () => void
) {
const pod = await invoke('get_pod', { namespace, name });
if (action === 'delete') {
if (force) {
await invoke('force_delete', { namespace, name });
} else {
await invoke('delete', { namespace, name, gracePeriod });
}
}
callback();
}
// AFTER: Single purpose, one abstraction level
interface PodActionRequest {
pod: PodRef;
action: PodAction;
}
async function executePodAction({ pod, action }: PodActionRequest): Promise<void> {
const handler = getPodActionHandler(action);
await handler.execute(pod);
}- 小! 函数应短小(理想情况下少于20行)
- 单一职责 - 只做一件事并做好
- 单一抽象层级 - 不混合不同抽象层级
- 逐步下降规则 - 从上到下阅读代码如同读故事
- 最多3个参数 - 优先0-2个参数,参数过多时使用对象
typescript
// 重构前:参数过多,混合抽象层级
async function processPod(
namespace: string,
name: string,
action: string,
force: boolean,
gracePeriod: number,
callback: () => void
) {
const pod = await invoke('get_pod', { namespace, name });
if (action === 'delete') {
if (force) {
await invoke('force_delete', { namespace, name });
} else {
await invoke('delete', { namespace, name, gracePeriod });
}
}
callback();
}
// 重构后:单一职责,单一抽象层级
interface PodActionRequest {
pod: PodRef;
action: PodAction;
}
async function executePodAction({ pod, action }: PodActionRequest): Promise<void> {
const handler = getPodActionHandler(action);
await handler.execute(pod);
}Law of Demeter (G36: Transitive Navigation)
迪米特法则(G36:传递导航)
Principle: A method should only call methods on:
- Its own object ()
this - Objects passed as parameters
- Objects it creates
- Its direct component objects
typescript
// VIOLATES Law of Demeter: "Train wreck"
const street = user.getAddress().getCity().getStreet();
// BETTER: Tell, don't ask
const street = user.getStreetAddress();
// Kubeli Example:
// BAD: Navigating through objects
const podName = store.getState().cluster.selectedPod.metadata.name;
// GOOD: Direct access with selector
const podName = useSelectedPodName();原则:方法只能调用以下对象的方法:
- 自身对象()
this - 作为参数传入的对象
- 自身创建的对象
- 直接组件对象
typescript
// 违反迪米特法则:"调用链过长"
const street = user.getAddress().getCity().getStreet();
// 优化方案:Tell, don't ask(命令而非查询)
const street = user.getStreetAddress();
// Kubeli示例:
// 不良写法:对象链式导航
const podName = store.getState().cluster.selectedPod.metadata.name;
// 良好写法:使用选择器直接访问
const podName = useSelectedPodName();Pfadfinder-Regel (Boy Scout Rule)
童子军规则
"Leave the code cleaner than you found it."
Every time you touch code:
- Fix one small thing
- Improve one name
- Extract one function
- Add one missing test
"让代码比你接手时更整洁。"
每次接触代码时:
- 修复一个小问题
- 优化一个命名
- 提取一个函数
- 添加一个缺失的测试
Phase 4: Stack-Specific Refactoring Patterns
第四阶段:技术栈特定重构模式
Vite/React (Frontend)
Vite/React(前端)
Component Organization:
typescript
// BEFORE: Monolithic component with mixed concerns
export function PodList({ namespace }: Props) {
const [pods, setPods] = useState([]);
const [filter, setFilter] = useState('');
useEffect(() => { fetchPods().then(setPods); }, []);
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<ul>{pods.filter(p => p.name.includes(filter)).map(p => <PodItem pod={p} />)}</ul>
</div>
);
}
// AFTER: Separate data from presentation, use Zustand
// stores/resource-store.ts
export const useResourceStore = create((set) => ({
pods: [],
fetchPods: async (ns) => { /* ... */ },
}));
// components/PodList.tsx
export function PodList() {
const pods = useResourceStore(s => s.pods);
const [filter, setFilter] = useState('');
return <ul>{pods.filter(p => p.name.includes(filter)).map(p => <PodItem pod={p} />)}</ul>;
}Anti-Patterns to Fix:
| Smell | Refactoring |
|---|---|
| Props drilling through 3+ levels | Use Zustand store or Context |
Giant | Split into logical modules in |
Inline Tauri | Centralize in |
| State in components that should be global | Move to Zustand store |
组件组织:
typescript
// 重构前:混合职责的单体组件
export function PodList({ namespace }: Props) {
const [pods, setPods] = useState([]);
const [filter, setFilter] = useState('');
useEffect(() => { fetchPods().then(setPods); }, []);
return (
<div>
<input value={filter} onChange={e => setFilter(e.target.value)} />
<ul>{pods.filter(p => p.name.includes(filter)).map(p => <PodItem pod={p} />)}</ul>
</div>
);
}
// 重构后:数据与展示分离,使用Zustand
// stores/resource-store.ts
export const useResourceStore = create((set) => ({
pods: [],
fetchPods: async (ns) => { /* ... */ },
}));
// components/PodList.tsx
export function PodList() {
const pods = useResourceStore(s => s.pods);
const [filter, setFilter] = useState('');
return <ul>{pods.filter(p => p.name.includes(filter)).map(p => <PodItem pod={p} />)}</ul>;
}需修复的反模式:
| 坏味道 | 重构方案 |
|---|---|
| 透传超过3层的Props | 使用Zustand Store或Context |
庞大的 | 拆分为 |
内联Tauri | 集中管理在 |
| 应全局管理的状态放在组件内 | 移至Zustand Store |
Zustand (State Management)
Zustand(状态管理)
Selective State Access:
typescript
// BEFORE: Re-renders on ANY state change
function PodCount() {
const store = useClusterStore(); // BAD: subscribes to everything
return <span>{store.pods.length}</span>;
}
// AFTER: Only re-renders when pods change
function PodCount() {
const podCount = useClusterStore((s) => s.pods.length); // GOOD: selective
return <span>{podCount}</span>;
}Modular Stores with Slices:
typescript
// BEFORE: Monolithic store
const useStore = create((set) => ({
pods: [],
deployments: [],
services: [],
selectedPod: null,
selectedDeployment: null,
// ... 50 more properties
}));
// AFTER: Composable slices
// stores/pods-slice.ts
export const createPodsSlice = (set, get) => ({
pods: [],
selectedPod: null,
fetchPods: async (ns) => { ... },
selectPod: (id) => set({ selectedPod: id }),
});
// stores/deployments-slice.ts
export const createDeploymentsSlice = (set, get) => ({
deployments: [],
fetchDeployments: async (ns) => { ... },
});
// stores/index.ts
export const useStore = create((...a) => ({
...createPodsSlice(...a),
...createDeploymentsSlice(...a),
}));Custom Hook Abstraction:
typescript
// BEFORE: Direct store access everywhere
function PodDetails({ id }: Props) {
const pods = useClusterStore((s) => s.pods);
const pod = pods.find(p => p.id === id);
// ...
}
// AFTER: Domain-specific hooks
// hooks/usePod.ts
export function usePod(id: string) {
return useClusterStore((s) => s.pods.find(p => p.id === id));
}
// components/PodDetails.tsx
function PodDetails({ id }: Props) {
const pod = usePod(id);
// ...
}选择性状态访问:
typescript
// 重构前:任何状态变更都会触发重渲染
function PodCount() {
const store = useClusterStore(); // 不良:订阅所有状态
return <span>{store.pods.length}</span>;
}
// 重构后:仅当pods变更时触发重渲染
function PodCount() {
const podCount = useClusterStore((s) => s.pods.length); // 良好:选择性订阅
return <span>{podCount}</span>;
}模块化切片Store:
typescript
// 重构前:单体Store
const useStore = create((set) => ({
pods: [],
deployments: [],
services: [],
selectedPod: null,
selectedDeployment: null,
// ... 50多个属性
}));
// 重构后:可组合的切片
// stores/pods-slice.ts
export const createPodsSlice = (set, get) => ({
pods: [],
selectedPod: null,
fetchPods: async (ns) => { ... },
selectPod: (id) => set({ selectedPod: id }),
});
// stores/deployments-slice.ts
export const createDeploymentsSlice = (set, get) => ({
deployments: [],
fetchDeployments: async (ns) => { ... },
});
// stores/index.ts
export const useStore = create((...a) => ({
...createPodsSlice(...a),
...createDeploymentsSlice(...a),
}));自定义Hook抽象:
typescript
// 重构前:各处直接访问Store
function PodDetails({ id }: Props) {
const pods = useClusterStore((s) => s.pods);
const pod = pods.find(p => p.id === id);
// ...
}
// 重构后:领域特定Hook
// hooks/usePod.ts
export function usePod(id: string) {
return useClusterStore((s) => s.pods.find(p => p.id === id));
}
// components/PodDetails.tsx
function PodDetails({ id }: Props) {
const pod = usePod(id);
// ...
}Tauri 2.0 / Rust (Backend)
Tauri 2.0 / Rust(后端)
Command Organization:
rust
// BEFORE: All commands in one file
// src-tauri/src/main.rs
#[tauri::command]
fn get_pods() { ... }
#[tauri::command]
fn get_deployments() { ... }
#[tauri::command]
fn get_services() { ... }
// ... 50 more commands
// AFTER: Modular command structure
// src-tauri/src/commands/mod.rs
pub mod pods;
pub mod deployments;
pub mod services;
// src-tauri/src/commands/pods.rs
#[tauri::command]
pub async fn get_pods(state: State<'_, AppState>, namespace: &str) -> Result<Vec<Pod>, Error> {
let client = state.client_manager.get_client()?;
client.list_pods(namespace).await
}
// src-tauri/src/main.rs
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
commands::pods::get_pods,
commands::pods::delete_pod,
commands::deployments::get_deployments,
])
.run(tauri::generate_context!())
.expect("error running app");
}Separation: main.rs vs lib.rs:
rust
// BEFORE: Logic in main.rs
// src-tauri/src/main.rs
fn main() {
// 500 lines of logic...
}
// AFTER: main.rs only handles startup, lib.rs has logic
// src-tauri/src/main.rs
fn main() {
kubeli_lib::run();
}
// src-tauri/src/lib.rs
pub mod commands;
pub mod k8s;
pub mod state;
pub fn run() {
tauri::Builder::default()
.manage(state::AppState::new())
.invoke_handler(tauri::generate_handler![...])
.run(tauri::generate_context!())
.expect("error running app");
}Rust Refactoring Patterns:
rust
// BEFORE: Tuple returns (hard to understand)
fn get_cluster_info() -> (String, bool, u32) {
(context_name, is_connected, node_count)
}
let (a, b, c) = get_cluster_info(); // What is a, b, c?
// AFTER: Struct with meaningful names
struct ClusterInfo {
context_name: String,
is_connected: bool,
node_count: u32,
}
fn get_cluster_info() -> ClusterInfo { ... }
let info = get_cluster_info();
println!("Connected: {}", info.is_connected);rust
// BEFORE: if-else chains
if status == "Running" { ... }
else if status == "Pending" { ... }
else if status == "Failed" { ... }
// AFTER: Pattern matching with enum
enum PodStatus { Running, Pending, Failed, Unknown }
match pod.status {
PodStatus::Running => { ... }
PodStatus::Pending => { ... }
PodStatus::Failed => { ... }
PodStatus::Unknown => { ... }
}rust
// BEFORE: Manual error handling everywhere
fn get_pod(name: &str) -> Result<Pod, Error> {
let pods = self.list_pods()?;
for pod in pods {
if pod.name == name {
return Ok(pod);
}
}
Err(Error::NotFound)
}
// AFTER: Iterator methods with Option/Result
fn get_pod(&self, name: &str) -> Option<&Pod> {
self.pods.iter().find(|p| p.name == name)
}
// Or with Result if error info needed:
fn get_pod(&self, name: &str) -> Result<&Pod, Error> {
self.pods.iter()
.find(|p| p.name == name)
.ok_or_else(|| Error::PodNotFound(name.to_string()))
}Minimize Public API Surface:
rust
// BEFORE: Everything public
pub struct KubeClientManager {
pub clients: HashMap<String, Client>,
pub current_context: String,
pub config: KubeConfig,
}
// AFTER: Minimal public API, private internals
pub struct KubeClientManager {
clients: HashMap<String, Client>, // private
current_context: String, // private
config: KubeConfig, // private
}
impl KubeClientManager {
pub fn new() -> Result<Self, Error> { ... }
pub fn get_client(&self) -> Result<&Client, Error> { ... }
pub fn switch_context(&mut self, name: &str) -> Result<(), Error> { ... }
// Internal methods stay private
}命令组织:
rust
// 重构前:所有命令放在一个文件
// src-tauri/src/main.rs
#[tauri::command]
fn get_pods() { ... }
#[tauri::command]
fn get_deployments() { ... }
#[tauri::command]
fn get_services() { ... }
// ... 50多个命令
// 重构后:模块化命令结构
// src-tauri/src/commands/mod.rs
pub mod pods;
pub mod deployments;
pub mod services;
// src-tauri/src/commands/pods.rs
#[tauri::command]
pub async fn get_pods(state: State<'_, AppState>, namespace: &str) -> Result<Vec<Pod>, Error> {
let client = state.client_manager.get_client()?;
client.list_pods(namespace).await
}
// src-tauri/src/main.rs
fn main() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
commands::pods::get_pods,
commands::pods::delete_pod,
commands::deployments::get_deployments,
])
.run(tauri::generate_context!())
.expect("error running app");
}分离main.rs与lib.rs:
rust
// 重构前:逻辑放在main.rs
// src-tauri/src/main.rs
fn main() {
// 500多行逻辑...
}
// 重构后:main.rs仅处理启动,lib.rs存放逻辑
// src-tauri/src/main.rs
fn main() {
kubeli_lib::run();
}
// src-tauri/src/lib.rs
pub mod commands;
pub mod k8s;
pub mod state;
pub fn run() {
tauri::Builder::default()
.manage(state::AppState::new())
.invoke_handler(tauri::generate_handler![...])
.run(tauri::generate_context!())
.expect("error running app");
}Rust重构模式:
rust
// 重构前:元组返回(难以理解)
fn get_cluster_info() -> (String, bool, u32) {
(context_name, is_connected, node_count)
}
let (a, b, c) = get_cluster_info(); // a、b、c分别代表什么?
// 重构后:使用有意义名称的结构体
struct ClusterInfo {
context_name: String,
is_connected: bool,
node_count: u32,
}
fn get_cluster_info() -> ClusterInfo { ... }
let info = get_cluster_info();
println!("已连接: {}", info.is_connected);rust
// 重构前:if-else链式判断
if status == "Running" { ... }
else if status == "Pending" { ... }
else if status == "Failed" { ... }
// 重构后:使用枚举模式匹配
enum PodStatus { Running, Pending, Failed, Unknown }
match pod.status {
PodStatus::Running => { ... }
PodStatus::Pending => { ... }
PodStatus::Failed => { ... }
PodStatus::Unknown => { ... }
}rust
// 重构前:各处手动处理错误
fn get_pod(name: &str) -> Result<Pod, Error> {
let pods = self.list_pods()?;
for pod in pods {
if pod.name == name {
return Ok(pod);
}
}
Err(Error::NotFound)
}
// 重构后:使用迭代器方法结合Option/Result
fn get_pod(&self, name: &str) -> Option<&Pod> {
self.pods.iter().find(|p| p.name == name)
}
// 若需要错误信息则使用Result:
fn get_pod(&self, name: &str) -> Result<&Pod, Error> {
self.pods.iter()
.find(|p| p.name == name)
.ok_or_else(|| Error::PodNotFound(name.to_string()))
}最小化公共API表面:
rust
// 重构前:所有内容都公开
pub struct KubeClientManager {
pub clients: HashMap<String, Client>,
pub current_context: String,
pub config: KubeConfig,
}
// 重构后:最小化公共API,内部细节私有
pub struct KubeClientManager {
clients: HashMap<String, Client>, // 私有
current_context: String, // 私有
config: KubeConfig, // 私有
}
impl KubeClientManager {
pub fn new() -> Result<Self, Error> { ... }
pub fn get_client(&self) -> Result<&Client, Error> { ... }
pub fn switch_context(&mut self, name: &str) -> Result<(), Error> { ... }
// 内部方法保持私有
}Tauri 2.0 Enterprise Patterns
Tauri 2.0企业级模式
Command Layer Pattern (Thin Handlers → Service Layer):
rust
// BEFORE: Fat command with business logic
#[tauri::command]
pub async fn create_user(name: String, email: String) -> Result<User, String> {
// Validation here...
// Database access here...
// Business logic here...
// 100+ lines of mixed concerns
}
// AFTER: Thin handler → Service layer
// src/commands/user_commands.rs
#[tauri::command]
pub async fn create_user(name: String, email: String) -> Result<User, AppError> {
user_service::create_user(&name, &email).await
}
// src/services/user_service.rs
pub async fn create_user(name: &str, email: &str) -> Result<User, AppError> {
validate_email(email)?;
let user = User::new(name, email);
repository::save_user(&user).await?;
Ok(user)
}Error Handling (thiserror + Serialize for IPC):
rust
use thiserror::Error;
use serde::Serialize;
#[derive(Error, Debug)]
pub enum AppError {
#[error("Database error: {0}")]
Database(#[from] sqlx::Error),
#[error("File not found: {path}")]
FileNotFound { path: String },
#[error("Kubernetes error: {0}")]
Kube(#[from] kube::Error),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
// CRITICAL: Implement Serialize for Tauri IPC
impl Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
serializer.serialize_str(&self.to_string())
}
}
// For typed frontend errors, use tagged serialization:
#[derive(Serialize)]
#[serde(tag = "kind", content = "message")]
#[serde(rename_all = "camelCase")]
pub enum TypedError {
Io(String),
Validation(String),
NotFound(String),
}
// Produces: { kind: 'io' | 'validation' | 'notFound', message: string }State Management (std::Mutex vs tokio::Mutex):
rust
// SYNC commands: Use std::sync::Mutex
use std::sync::Mutex;
#[tauri::command]
fn increment(state: State<'_, Mutex<AppState>>) -> u32 {
let mut state = state.lock().unwrap();
state.counter += 1;
state.counter
}
// ASYNC commands: Use tokio::sync::Mutex (avoids blocking!)
use tokio::sync::Mutex;
#[tauri::command]
async fn async_increment(state: State<'_, Mutex<AppState>>) -> Result<u32, ()> {
let mut state = state.lock().await; // .await not .unwrap()!
state.counter += 1;
Ok(state.counter)
}
// CRITICAL: Async commands with borrowed args need Result return type
// ❌ Won't compile
async fn cmd(state: State<'_, AppState>) { }
// ✅ Correct pattern
async fn cmd(state: State<'_, AppState>) -> Result<(), ()> { Ok(()) }Security: Path Traversal Prevention:
rust
#[tauri::command]
async fn read_file(path: String, app: AppHandle) -> Result<String, String> {
let path = std::path::Path::new(&path);
// Prevent path traversal attacks
if path.components().any(|c| matches!(c, std::path::Component::ParentDir)) {
return Err("Invalid path: directory traversal not allowed".into());
}
// Validate against allowed base directory
let base = app.path().app_data_dir().unwrap();
let full_path = base.join(&path);
let canonical = full_path.canonicalize()
.map_err(|e| format!("Invalid path: {}", e))?;
if !canonical.starts_with(&base) {
return Err("Access denied: path outside allowed scope".into());
}
std::fs::read_to_string(canonical).map_err(|e| e.to_string())
}Async Performance (spawn_blocking for CPU-intensive):
rust
// CPU-intensive work should use spawn_blocking
#[tauri::command]
async fn heavy_computation(data: Vec<u8>) -> Result<Vec<u8>, String> {
tokio::task::spawn_blocking(move || {
process_heavy_data(data) // Runs on blocking thread pool
}).await.map_err(|e| e.to_string())
}
// I/O work uses regular async
#[tauri::command]
async fn fetch_data(url: String) -> Result<Data, String> {
reqwest::get(&url).await
.map_err(|e| e.to_string())?
.json().await
.map_err(|e| e.to_string())
}Extension Traits for AppHandle:
rust
pub trait AppHandleExt {
fn get_database(&self) -> Arc<Database>;
fn emit_global(&self, event: &str, payload: impl Serialize);
}
impl AppHandleExt for tauri::AppHandle {
fn get_database(&self) -> Arc<Database> {
self.state::<Arc<Database>>().inner().clone()
}
fn emit_global(&self, event: &str, payload: impl Serialize) {
self.emit(event, payload).unwrap();
}
}
// Usage in commands:
#[tauri::command]
async fn get_pods(app: AppHandle) -> Result<Vec<Pod>, AppError> {
let db = app.get_database();
db.query_pods().await
}Events for Real-time Updates (Backend → Frontend):
rust
use tauri::{AppHandle, Emitter};
#[derive(Clone, Serialize)]
struct ProgressUpdate { percent: u32, status: String }
#[tauri::command]
async fn long_operation(app: AppHandle) -> Result<(), String> {
for i in 0..=100 {
app.emit("progress", ProgressUpdate {
percent: i,
status: format!("Processing {}%", i)
}).unwrap();
tokio::time::sleep(Duration::from_millis(50)).await;
}
Ok(())
}typescript
// Frontend: Always cleanup listeners!
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen<ProgressUpdate>('progress', (event) => {
console.log(`Progress: ${event.payload.percent}%`);
});
// Cleanup on unmount
onCleanup(() => unlisten());Tauri 2.0 Capability-Based Security:
json
// src-tauri/capabilities/default.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"windows": ["main"],
"permissions": [
"core:default",
"fs:default",
{
"identifier": "fs:allow-read",
"allow": [{ "path": "$APPDATA/*" }],
"deny": [{ "path": "$HOME/.ssh/*" }]
}
]
}Release Build Optimization:
toml
undefined命令层模式(轻量处理器 → 服务层):
rust
// 重构前:包含业务逻辑的臃肿命令
#[tauri::command]
pub async fn create_user(name: String, email: String) -> Result<User, String> {
// 验证逻辑...
// 数据库访问...
// 业务逻辑...
// 100多行混合职责代码
}
// 重构后:轻量处理器 → 服务层
// src/commands/user_commands.rs
#[tauri::command]
pub async fn create_user(name: String, email: String) -> Result<User, AppError> {
user_service::create_user(&name, &email).await
}
// src/services/user_service.rs
pub async fn create_user(name: &str, email: &str) -> Result<User, AppError> {
validate_email(email)?;
let user = User::new(name, email);
repository::save_user(&user).await?;
Ok(user)
}错误处理(thiserror + Serialize用于IPC):
rust
use thiserror::Error;
use serde::Serialize;
#[derive(Error, Debug)]
pub enum AppError {
#[error("数据库错误: {0}")]
Database(#[from] sqlx::Error),
#[error("文件未找到: {path}")]
FileNotFound { path: String },
#[error("Kubernetes错误: {0}")]
Kube(#[from] kube::Error),
#[error(transparent)]
Other(#[from] anyhow::Error),
}
// 关键:为Tauri IPC实现Serialize
impl Serialize for AppError {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where S: serde::Serializer {
serializer.serialize_str(&self.to_string())
}
}
// 若要实现前端类型化错误,使用标记序列化:
#[derive(Serialize)]
#[serde(tag = "kind", content = "message")]
#[serde(rename_all = "camelCase")]
pub enum TypedError {
Io(String),
Validation(String),
NotFound(String),
}
// 输出格式: { kind: 'io' | 'validation' | 'notFound', message: string }状态管理(std::Mutex vs tokio::Mutex):
rust
// 同步命令:使用std::sync::Mutex
use std::sync::Mutex;
#[tauri::command]
fn increment(state: State<'_, Mutex<AppState>>) -> u32 {
let mut state = state.lock().unwrap();
state.counter += 1;
state.counter
}
// 异步命令:使用tokio::sync::Mutex(避免阻塞!)
use tokio::sync::Mutex;
#[tauri::command]
async fn async_increment(state: State<'_, Mutex<AppState>>) -> Result<u32, ()> {
let mut state = state.lock().await; // 使用.await而非.unwrap()!
state.counter += 1;
Ok(state.counter)
}
// 关键:带借用参数的异步命令需要返回Result类型
// ❌ 无法编译
async fn cmd(state: State<'_, AppState>) { }
// ✅ 正确模式
async fn cmd(state: State<'_, AppState>) -> Result<(), ()> { Ok(()) }安全:路径遍历防护:
rust
#[tauri::command]
async fn read_file(path: String, app: AppHandle) -> Result<String, String> {
let path = std::path::Path::new(&path);
// 防止路径遍历攻击
if path.components().any(|c| matches!(c, std::path::Component::ParentDir)) {
return Err("无效路径:不允许目录遍历".into());
}
// 验证路径是否在允许的基础目录内
let base = app.path().app_data_dir().unwrap();
let full_path = base.join(&path);
let canonical = full_path.canonicalize()
.map_err(|e| format!("无效路径: {}", e))?;
if !canonical.starts_with(&base) {
return Err("访问被拒绝:路径超出允许范围".into());
}
std::fs::read_to_string(canonical).map_err(|e| e.to_string())
}异步性能(CPU密集型任务使用spawn_blocking):
rust
// CPU密集型工作应使用spawn_blocking
#[tauri::command]
async fn heavy_computation(data: Vec<u8>) -> Result<Vec<u8>, String> {
tokio::task::spawn_blocking(move || {
process_heavy_data(data) // 在阻塞线程池运行
}).await.map_err(|e| e.to_string())
}
// I/O工作使用常规异步
#[tauri::command]
async fn fetch_data(url: String) -> Result<Data, String> {
reqwest::get(&url).await
.map_err(|e| e.to_string())?
.json().await
.map_err(|e| e.to_string())
}AppHandle扩展 trait:
rust
pub trait AppHandleExt {
fn get_database(&self) -> Arc<Database>;
fn emit_global(&self, event: &str, payload: impl Serialize);
}
impl AppHandleExt for tauri::AppHandle {
fn get_database(&self) -> Arc<Database> {
self.state::<Arc<Database>>().inner().clone()
}
fn emit_global(&self, event: &str, payload: impl Serialize) {
self.emit(event, payload).unwrap();
}
}
// 命令中的用法:
#[tauri::command]
async fn get_pods(app: AppHandle) -> Result<Vec<Pod>, AppError> {
let db = app.get_database();
db.query_pods().await
}实时更新事件(后端→前端):
rust
use tauri::{AppHandle, Emitter};
#[derive(Clone, Serialize)]
struct ProgressUpdate { percent: u32, status: String }
#[tauri::command]
async fn long_operation(app: AppHandle) -> Result<(), String> {
for i in 0..=100 {
app.emit("progress", ProgressUpdate {
percent: i,
status: format!("处理进度 {}%", i)
}).unwrap();
tokio::time::sleep(Duration::from_millis(50)).await;
}
Ok(())
}typescript
// 前端:务必清理监听器!
import { listen } from '@tauri-apps/api/event';
const unlisten = await listen<ProgressUpdate>('progress', (event) => {
console.log(`进度: ${event.payload.percent}%`);
});
// 卸载时清理
onCleanup(() => unlisten());Tauri 2.0基于能力的安全:
json
// src-tauri/capabilities/default.json
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "main-capability",
"windows": ["main"],
"permissions": [
"core:default",
"fs:default",
{
"identifier": "fs:allow-read",
"allow": [{ "path": "$APPDATA/*" }],
"deny": [{ "path": "$HOME/.ssh/*" }]
}
]
}发布构建优化:
toml
undefinedCargo.toml
Cargo.toml
[profile.release]
lto = true # Link-time optimization
codegen-units = 1 # Better optimization
opt-level = "s" # Optimize for size
panic = "abort" # Smaller binary
strip = true # Remove debug symbols
**Channels for High-Throughput Streaming (Alternative to Events):**
```rust
use tauri::ipc::Channel;
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase", tag = "event", content = "data")]
enum DownloadEvent<'a> {
Started { url: &'a str, size: u64 },
Progress { percent: u8, downloaded: u64 },
Finished,
}
#[tauri::command]
fn download(url: String, on_progress: Channel<DownloadEvent>) {
on_progress.send(DownloadEvent::Started { url: &url, size: 1024 }).unwrap();
// ... streaming data
for i in 0..=100 {
on_progress.send(DownloadEvent::Progress { percent: i, downloaded: i as u64 * 10 }).unwrap();
}
on_progress.send(DownloadEvent::Finished).unwrap();
}typescript
// Frontend: Channel usage
await invoke('download', {
url: 'https://example.com/file',
onProgress: new Channel<DownloadEvent>((event) => {
if (event.event === 'progress') {
console.log(`Downloaded: ${event.data.percent}%`);
}
})
});Multi-Window Security Isolation:
json
// capabilities/admin.json - More privileges
{
"identifier": "admin-capability",
"windows": ["admin-*"],
"permissions": ["fs:default", "fs:allow-write", "shell:allow-execute"]
}
// capabilities/viewer.json - Read-only
{
"identifier": "viewer-capability",
"windows": ["viewer-*"],
"permissions": ["fs:allow-read"]
}Content Security Policy (CSP):
json
// tauri.conf.json
{
"app": {
"security": {
"csp": {
"default-src": "'self' customprotocol: asset:",
"connect-src": "ipc: http://ipc.localhost",
"script-src": "'self'",
"style-src": "'unsafe-inline' 'self'"
}
}
}
}Security Hardening Checklist:
- Enable strict CSP with
default-src 'self' - Configure per-window capabilities with minimum permissions
- Define scopes to restrict file system access
- Validate ALL command inputs in Rust (frontend is untrusted!)
- Run and
cargo auditregularlynpm audit - Never load remote/untrusted content
- Sign all release binaries
- Use for async commands (not std::sync)
tokio::sync::Mutex
Splashscreen Startup Optimization:
rust
tauri::Builder::default()
.setup(|app| {
let splashscreen = app.get_webview_window("splashscreen").unwrap();
let main_window = app.get_webview_window("main").unwrap();
tauri::async_runtime::spawn(async move {
// Heavy initialization here (doesn't block UI)
initialize_database().await;
load_config().await;
splashscreen.close().unwrap();
main_window.show().unwrap();
});
Ok(())
})Mobile Support (lib.rs Entry Point):
rust
// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![...])
.run(tauri::generate_context!())
.expect("error running app");
}
// src-tauri/src/main.rs (minimal)
fn main() {
kubeli_lib::run();
}Testing: Rust Commands with Mock Runtime:
rust
#[cfg(test)]
mod tests {
use tauri::test::{mock_builder, mock_context, noop_assets};
fn create_app() -> tauri::App<tauri::test::MockRuntime> {
mock_builder()
.invoke_handler(tauri::generate_handler![super::greet])
.build(mock_context(noop_assets()))
.expect("failed to build app")
}
#[test]
fn test_greet() {
let _app = create_app();
let result = super::greet("World");
assert_eq!(result, "Hello, World!");
}
}toml
undefined[profile.release]
lto = true # 链接时优化
codegen-units = 1 # 更优的优化
opt-level = "s" # 针对大小优化
panic = "abort" # 更小的二进制文件
strip = true # 移除调试符号
**高吞吐量流的通道(事件替代方案):**
```rust
use tauri::ipc::Channel;
#[derive(Clone, Serialize)]
#[serde(rename_all = "camelCase", tag = "event", content = "data")]
enum DownloadEvent<'a> {
Started { url: &'a str, size: u64 },
Progress { percent: u8, downloaded: u64 },
Finished,
}
#[tauri::command]
fn download(url: String, on_progress: Channel<DownloadEvent>) {
on_progress.send(DownloadEvent::Started { url: &url, size: 1024 }).unwrap();
// ... 流数据处理
for i in 0..=100 {
on_progress.send(DownloadEvent::Progress { percent: i, downloaded: i as u64 * 10 }).unwrap();
}
on_progress.send(DownloadEvent::Finished).unwrap();
}typescript
// 前端:通道用法
await invoke('download', {
url: 'https://example.com/file',
onProgress: new Channel<DownloadEvent>((event) => {
if (event.event === 'progress') {
console.log(`下载进度: ${event.data.percent}%`);
}
})
});多窗口安全隔离:
json
// capabilities/admin.json - 更高权限
{
"identifier": "admin-capability",
"windows": ["admin-*"],
"permissions": ["fs:default", "fs:allow-write", "shell:allow-execute"]
}
// capabilities/viewer.json - 只读权限
{
"identifier": "viewer-capability",
"windows": ["viewer-*"],
"permissions": ["fs:allow-read"]
}内容安全策略(CSP):
json
// tauri.conf.json
{
"app": {
"security": {
"csp": {
"default-src": "'self' customprotocol: asset:",
"connect-src": "ipc: http://ipc.localhost",
"script-src": "'self'",
"style-src": "'unsafe-inline' 'self'"
}
}
}
}安全强化检查清单:
- 启用严格CSP,设置
default-src 'self' - 为每个窗口配置最小权限的能力
- 定义作用域以限制文件系统访问
- 在Rust中验证所有命令输入(前端不可信!)
- 定期运行和
cargo auditnpm audit - 绝不加载远程/不可信内容
- 为所有发布二进制文件签名
- 异步命令使用(而非std::sync)
tokio::sync::Mutex
启动屏优化:
rust
tauri::Builder::default()
.setup(|app| {
let splashscreen = app.get_webview_window("splashscreen").unwrap();
let main_window = app.get_webview_window("main").unwrap();
tauri::async_runtime::spawn(async move {
// 在此处执行重型初始化(不阻塞UI)
initialize_database().await;
load_config().await;
splashscreen.close().unwrap();
main_window.show().unwrap();
});
Ok(())
})移动端支持(lib.rs入口):
rust
// src-tauri/src/lib.rs
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![...])
.run(tauri::generate_context!())
.expect("error running app");
}
// src-tauri/src/main.rs(极简)
fn main() {
kubeli_lib::run();
}测试:使用Mock Runtime测试Rust命令:
rust
#[cfg(test)]
mod tests {
use tauri::test::{mock_builder, mock_context, noop_assets};
fn create_app() -> tauri::App<tauri::test::MockRuntime> {
mock_builder()
.invoke_handler(tauri::generate_handler![super::greet])
.build(mock_context(noop_assets()))
.expect("failed to build app")
}
#[test]
fn test_greet() {
let _app = create_app();
let result = super::greet("World");
assert_eq!(result, "Hello, World!");
}
}toml
undefinedEnable test feature in Cargo.toml
在Cargo.toml中启用测试特性
[dependencies]
tauri = { version = "2.0", features = ["test"] }
**Testing: Frontend IPC Mocking (Vitest):**
```typescript
import { mockIPC, clearMocks } from '@tauri-apps/api/mocks';
import { invoke } from '@tauri-apps/api/core';
afterEach(() => clearMocks());
test('invoke add command', async () => {
mockIPC((cmd, args) => {
if (cmd === 'add') return (args as { a: number; b: number }).a + args.b;
});
const result = await invoke('add', { a: 12, b: 15 });
expect(result).toBe(27);
});Code Quality: Clippy Configuration:
toml
undefined[dependencies]
tauri = { version = "2.0", features = ["test"] }
**测试:前端IPC Mocking(Vitest):**
```typescript
import { mockIPC, clearMocks } from '@tauri-apps/api/mocks';
import { invoke } from '@tauri-apps/api/core';
afterEach(() => clearMocks());
test('invoke add command', async () => {
mockIPC((cmd, args) => {
if (cmd === 'add') return (args as { a: number; b: number }).a + args.b;
});
const result = await invoke('add', { a: 12, b: 15 });
expect(result).toBe(27);
});代码质量:Clippy配置:
toml
undefinedCargo.toml
Cargo.toml
[lints.clippy]
pedantic = { level = "warn", priority = -1 }
unwrap_used = "deny" # Force proper error handling
expect_used = "warn"
module_name_repetitions = "allow"
**Code Quality: rustfmt.toml:**
```toml
edition = "2021"
max_width = 100
imports_granularity = "Module"
group_imports = "StdExternalCrate"
wrap_comments = trueWorkspace Dependency Management:
toml
undefined[lints.clippy]
pedantic = { level = "warn", priority = -1 }
unwrap_used = "deny" # 强制正确的错误处理
expect_used = "warn"
module_name_repetitions = "allow"
**代码质量:rustfmt.toml:**
```toml
edition = "2021"
max_width = 100
imports_granularity = "Module"
group_imports = "StdExternalCrate"
wrap_comments = true工作区依赖管理:
toml
undefinedRoot Cargo.toml
根目录Cargo.toml
[workspace.dependencies]
tauri = { version = "2.0", features = [] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
[workspace.dependencies]
tauri = { version = "2.0", features = [] }
serde = { version = "1.0", features = ["derive"] }
tokio = { version = "1", features = ["full"] }
Member Cargo.toml - inherit from workspace
子项目Cargo.toml - 继承工作区依赖
[dependencies]
tauri.workspace = true
serde.workspace = true
---[dependencies]
tauri.workspace = true
serde.workspace = true
---React / TypeScript Patterns
React / TypeScript模式
Component Cohesion (Single Responsibility):
typescript
// BEFORE: Component does too much
function PodManager() {
const [pods, setPods] = useState([]);
const [filter, setFilter] = useState('');
const [sortBy, setSortBy] = useState('name');
const [selectedPod, setSelectedPod] = useState(null);
const [isDeleting, setIsDeleting] = useState(false);
const [showLogs, setShowLogs] = useState(false);
// ... 200 lines of mixed concerns
return (
<div>
<FilterBar ... />
<PodList ... />
<PodDetails ... />
<DeleteConfirmation ... />
<LogViewer ... />
</div>
);
}
// AFTER: Separated concerns
function PodManager() {
return (
<PodFilterProvider>
<div>
<PodFilterBar />
<PodListWithSelection />
<PodDetailsPanel />
</div>
</PodFilterProvider>
);
}
// Each sub-component manages its own state or uses shared storeProps Interface Simplification:
typescript
// BEFORE: Too many props (shallow module)
interface PodCardProps {
name: string;
namespace: string;
status: string;
createdAt: Date;
labels: Record<string, string>;
onSelect: () => void;
onDelete: () => void;
onViewLogs: () => void;
onRestart: () => void;
isSelected: boolean;
showActions: boolean;
}
// AFTER: Deep module with simple interface
interface PodCardProps {
pod: Pod;
onAction?: (action: PodAction) => void;
}
type PodAction =
| { type: 'select' }
| { type: 'delete' }
| { type: 'viewLogs' }
| { type: 'restart' };Custom Hooks for Reusable Logic:
typescript
// BEFORE: Duplicated logic in components
function PodList() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
invoke('get_pods', { namespace })
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [namespace]);
// ...
}
// AFTER: Reusable hook
function useTauriQuery<T>(command: string, args: Record<string, unknown>) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
setLoading(true);
invoke<T>(command, args)
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [command, JSON.stringify(args)]);
return { data, loading, error };
}
// Usage
function PodList({ namespace }: Props) {
const { data: pods, loading, error } = useTauriQuery<Pod[]>('get_pods', { namespace });
// ...
}组件内聚性(单一职责):
typescript
// 重构前:组件职责过多
function PodManager() {
const [pods, setPods] = useState([]);
const [filter, setFilter] = useState('');
const [sortBy, setSortBy] = useState('name');
const [selectedPod, setSelectedPod] = useState(null);
const [isDeleting, setIsDeleting] = useState(false);
const [showLogs, setShowLogs] = useState(false);
// ... 200多行混合职责代码
return (
<div>
<FilterBar ... />
<PodList ... />
<PodDetails ... />
<DeleteConfirmation ... />
<LogViewer ... />
</div>
);
}
// 重构后:职责分离
function PodManager() {
return (
<PodFilterProvider>
<div>
<PodFilterBar />
<PodListWithSelection />
<PodDetailsPanel />
</div>
</PodFilterProvider>
);
}
// 每个子组件管理自身状态或使用共享StoreProps接口简化:
typescript
// 重构前:Props过多(浅模块)
interface PodCardProps {
name: string;
namespace: string;
status: string;
createdAt: Date;
labels: Record<string, string>;
onSelect: () => void;
onDelete: () => void;
onViewLogs: () => void;
onRestart: () => void;
isSelected: boolean;
showActions: boolean;
}
// 重构后:深模块,接口简单
interface PodCardProps {
pod: Pod;
onAction?: (action: PodAction) => void;
}
type PodAction =
| { type: 'select' }
| { type: 'delete' }
| { type: 'viewLogs' }
| { type: 'restart' };可复用逻辑的自定义Hook:
typescript
// 重构前:组件中重复逻辑
function PodList() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
invoke('get_pods', { namespace })
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [namespace]);
// ...
}
// 重构后:可复用Hook
function useTauriQuery<T>(command: string, args: Record<string, unknown>) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
setLoading(true);
invoke<T>(command, args)
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, [command, JSON.stringify(args)]);
return { data, loading, error };
}
// 用法
function PodList({ namespace }: Props) {
const { data: pods, loading, error } = useTauriQuery<Pod[]>('get_pods', { namespace });
// ...
}Phase 5: Refactoring Workflow
第五阶段:重构工作流
Step-by-Step Process
分步流程
-
Analyze (5-10 min)
- Run on target code
/software-design-review - Identify top 3 complexity issues
- Choose ONE to fix first
- Run
-
Design (5 min)
- Consider 2-3 alternative approaches
- Pick the one with simplest interface
- Write the interface comment FIRST
-
Test (before coding)
- Ensure tests exist
- If not, write characterization tests
- Run tests to confirm green
-
Refactor (small steps)
- Make ONE change at a time
- Run tests after each change
- Commit after each working step
-
Review (after)
- Does the code look like it was designed this way?
- Is the interface simpler?
- Did we improve or just move complexity?
-
分析(5-10分钟)
- 对目标代码运行/software-design-review
- 确定前3个复杂度问题
- 选择一个优先修复
-
设计(5分钟)
- 考虑2-3种替代方案
- 选择接口最简单的方案
- 先编写接口注释
-
测试(编码前)
- 确保测试存在
- 若不存在,编写特征测试
- 运行测试确认全部通过
-
重构(小步推进)
- 每次只做一处修改
- 每次修改后运行测试
- 每次工作步骤完成后提交代码
-
评审(完成后)
- 代码是否看起来从一开始就是这样设计的?
- 接口是否更简单?
- 我们是降低了复杂度还是只是转移了复杂度?
Commit Strategy
提交策略
bash
undefinedbash
undefinedSmall, atomic commits
小而独立的提交
git commit -m "refactor(pods): extract PodCard props into Pod type"
git commit -m "refactor(pods): create usePod hook for selective access"
git commit -m "refactor(pods): move pod filtering to dedicated hook"
git commit -m "refactor(pods): extract PodCard props into Pod type"
git commit -m "refactor(pods): create usePod hook for selective access"
git commit -m "refactor(pods): move pod filtering to dedicated hook"
NOT one giant commit
不要提交大而全的代码
git commit -m "refactor: improve pod management" # BAD: too vague
---git commit -m "refactor: improve pod management" # 不良:过于模糊
---Phase 6: Prioritization Matrix
第六阶段:优先级矩阵
Rate each issue and fix highest impact first:
| Issue | Complexity Reduction | Effort | Risk | Priority |
|---|---|---|---|---|
| High impact, Low effort, Low risk | ⬆️⬆️⬆️ | ⬇️ | ⬇️ | P0 - Do First |
| High impact, Medium effort | ⬆️⬆️⬆️ | ➡️ | ➡️ | P1 |
| Medium impact, Low effort | ⬆️⬆️ | ⬇️ | ⬇️ | P2 |
| Low impact OR High risk | ⬆️ | Any | ⬆️ | P3 - Later |
对每个问题评分,优先修复影响最高的问题:
| 问题 | 复杂度降低程度 | 工作量 | 风险 | 优先级 |
|---|---|---|---|---|
| 高影响、低工作量、低风险 | ⬆️⬆️⬆️ | ⬇️ | ⬇️ | P0 - 立即处理 |
| 高影响、中等工作量 | ⬆️⬆️⬆️ | ➡️ | ➡️ | P1 |
| 中等影响、低工作量 | ⬆️⬆️ | ⬇️ | ⬇️ | P2 |
| 低影响或高风险 | ⬆️ | 任意 | ⬆️ | P3 - 后续处理 |
Your Output Format
输出格式
1. Analysis Summary
1. 分析摘要
text
Target: [file/directory]
Current Complexity: [Low/Medium/High]
Top Issues:
1. [Issue + Principle violated]
2. [Issue + Principle violated]
3. [Issue + Principle violated]text
目标: [文件/目录]
当前复杂度: [低/中/高]
主要问题:
1. [问题 + 违反的原则]
2. [问题 + 违反的原则]
3. [问题 + 违反的原则]2. Refactoring Plan
2. 重构计划
text
Priority | Issue | Refactoring | Estimated Changes
---------|-------|-------------|------------------
P0 | ... | ... | ~X files, ~Y lines
P1 | ... | ... | ...text
优先级 | 问题 | 重构方案 | 预计变更范围
---------|-------|-------------|------------------
P0 | ... | ... | ~X个文件,~Y行代码
P1 | ... | ... | ...3. Step-by-Step Execution
3. 分步执行
For each P0/P1 item:
- What to change
- Expected interface (comment first)
- Test requirements
- Implementation steps
针对每个P0/P1项:
- 要修改的内容
- 预期接口(先写注释)
- 测试要求
- 实现步骤
4. Safety Notes
4. 安全注意事项
- Tests to add/verify
- Potential breaking changes
- Rollback plan if needed
- 需要添加/验证的测试
- 潜在的破坏性变更
- 回滚方案(若需要)
Sources & References
参考资料
Books
书籍
- John Ousterhout: "A Philosophy of Software Design" (15 Principles)
- Robert C. Martin: "Clean Code" (Smells & Heuristics)
- Martin Fowler: "Refactoring" (Refactoring Catalog)
- John Ousterhout: 《软件设计哲学》(15条原则)
- Robert C. Martin: 《整洁代码》(坏味道与启发式规则)
- Martin Fowler: 《重构》(重构目录)