mcpField
K
kent-beck-style

kent-beck-style

github.com/nathankim0测试与质量10853 次安装5 天前更新
代码仓库

关于 kent-beck-style

这项代码审查 Skill 运用 Kent Beck 的重构理念和简单设计原则评估可读性与可维护性。它识别代码异味并提出重构方法,强调小步、可测试的改动,避免臆测需求带来的复杂性。

主要功能

  1. 01识别常见代码异味
  2. 02运用简单设计四项规则
  3. 03检查 YAGNI 和 KISS
  4. 04提出针对性的重构方法
  5. 05强调小步且可测试的改动

适用场景

  1. 01在代码审查中发现代码异味
  2. 02改善命名和可读性
  3. 03减少重复代码或不必要的抽象

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/nathankim0/clean-architecture-skills --skill kent-beck-style

安装后在 Claude Code 中输入/kent-beck-style确认已加载。

详细介绍

Kent Beck 风格编码评审技能

概述

此技能基于 Kent Beck 的重构理念和简单设计原则来分析和评审代码。重点关注代码异味检测、重构技巧和渐进式设计改进。

“任何傻瓜都能写出计算机看得懂的代码。优秀的程序员写出的代码,连人也看得懂。”—— Martin Fowler(《重构》,前言由 Kent Beck 撰写)

使用场景

  • 用户要求检测“代码异味”或提供“重构”指导时
  • 用户询问“代码质量”“可读性”或“可维护性”时
  • 评审代码以寻找改进机会时
  • 用户询问“YAGNI”“KISS”或“简单设计”时
  • 改进命名、提取方法或消除重复代码时

核心设计原则

简单设计(简单设计的 4 条规则)

Kent Beck 按优先级排列的规则:

  1. 通过测试——代码必须正确运行
  2. 揭示意图——代码应清晰地表达其用途
  3. 消除重复——DRY(不要重复自己)
  4. 元素最少——尽量减少类、方法和代码
YAGNI(你不会需要它)
  • 只有在确实需要时才添加功能
  • 避免投机性泛化
  • 针对当下的需求构建,而不是想象中的未来
// Bad: Speculative generality
interface DataProcessor<T, U, V> {
  process(input: T, options: U): V;
  processAsync(input: T, options: U): Promise<V>;
  processBatch(inputs: T[], options: U): V[];
  // We only need one sync method right now!
}

// Good: Build what you need
interface DataProcessor {
  process(input: string): Result;
}
KISS(保持简单,笨蛋)
  • 优先选择简单的解决方案,而不是花哨的方案
  • 复杂性是可维护性的敌人
  • 如果难以解释,那它很可能过于复杂
渐进式设计
  • 进行小而安全的更改
  • 编码时持续重构
  • 设计在重构过程中逐渐形成

代码异味目录

1. 过度膨胀

代码增长过度,难以处理。

过长方法
  • 方法承担了太多工作
  • 难以一眼看懂
  • 重构:提取方法、用查询替换临时变量
// Bad: Long method doing many things
function processOrder(order: Order) {
  // Validate order (10 lines)
  // Calculate discount (15 lines)
  // Apply tax (10 lines)
  // Update inventory (20 lines)
  // Send notification (15 lines)
  // Generate invoice (25 lines)
}

// Good: Extracted into focused methods
function processOrder(order: Order) {
  validateOrder(order);
  const discount = calculateDiscount(order);
  const total = applyTax(order.subtotal - discount);
  updateInventory(order.items);
  sendOrderConfirmation(order);
  generateInvoice(order, total);
}
过大的类
  • 类承担了过多职责
  • 实例变量太多
  • 重构:提取类、提取子类
基本类型偏执
  • 过度使用基本类型,而不是小对象
  • 用字符串表示电话号码、金钱等
  • 重构:用对象替换基本类型、引入参数对象
// Bad: Primitive obsession
function createUser(
  name: string,
  email: string,
  phone: string,
  street: string,
  city: string,
  zipCode: string
) { ... }

// Good: Value objects
function createUser(
  name: Name,
  email: Email,
  phone: Phone,
  address: Address
) { ... }
参数列表过长
  • 方法参数过多
  • 难以理解,也难以正确调用
  • 重构:引入参数对象、保留整个对象
数据泥团
  • 总是一起出现的数据组
  • 重构:提取类、引入参数对象
2. 滥用面向对象

对 OO 原则应用不当。

Switch 语句
  • 基于类型的 switch/case 或 if/else 链反复出现时,可以考虑使用多态
  • 如果分支集合有限且封闭,且简短、穷尽的 switch 更易于理解,则应保留它
  • 重构:用多态替换条件表达式、用子类替换类型码
// Before: A type-based branch; keep it if it is already clear
function calculateArea(shape: Shape): number {
  switch (shape.type) {
    case 'circle':
      return Math.PI * shape.radius ** 2;
    case 'rectangle':
      return shape.width * shape.height;
    case 'triangle':
      return (shape.base * shape.height) / 2;
  }
}

// Alternative: Polymorphism when repeated branches need independent extension
interface Shape {
  calculateArea(): number;
}

class Circle implements Shape {
  calculateArea(): number {
    return Math.PI * this.radius ** 2;
  }
}
临时字段
  • 只在特定情况下设置的字段
  • 重构:提取类、引入空对象
拒绝继承
  • 子类不使用继承的方法
  • 重构:用委托替换继承
接口不同的替代类
  • 执行相同操作,但方法名称不同的类
  • 重构:重命名方法、提取超类
3. 阻碍修改

让修改变得困难的代码。

发散式变化
  • 一个类因多个原因而变化
  • 违反单一职责原则
  • 重构:提取类
散弹式修改
  • 一处修改需要改动许多类
  • 重构:搬移方法、搬移字段、内联类
平行继承体系
  • 创建一个子类时,需要在其他地方创建另一个子类
  • 重构:搬移方法、搬移字段
4. 冗余元素

不必要的代码。

重复代码
  • 多处存在相同的代码结构
  • 重构:提取方法、提取类、上移方法
// Bad: Duplicate code
function validateEmail(email: string): boolean {
  const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return regex.test(email);
}

function isValidEmail(input: string): boolean {
  const pattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return pattern.test(input);
}

// Good: Single source of truth
const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

function isValidEmail(email: string): boolean {
  return EMAIL_PATTERN.test(email);
}
死代码
  • 无法到达或未使用的代码
  • 重构:移除死代码
过度设计的通用性
  • 为了“以防万一”而创建但未使用的抽象
  • 重构:折叠继承体系、内联类、移除参数
注释(作为代码异味)
  • 解释代码做什么(而非为什么)的注释
  • 通常表明代码本身可以更清晰
  • 重构:提取方法、重命名方法
// Bad: Comment explaining what
// Check if user is adult
if (user.age >= 18) { ... }

// Good: Self-documenting code
const LEGAL_ADULT_AGE = 18;
if (user.isAdult()) { ... }

// Good use of comments: explaining why
// Using >= instead of > because legal age includes 18th birthday
if (user.age >= LEGAL_ADULT_AGE) { ... }
5. 耦合者

耦合过度的代码。

依恋情结
  • 某个方法使用其他类的数据多于自身的数据
  • 重构:移动方法、提取方法
// Bad: Feature envy
class OrderPrinter {
  print(order: Order) {
    console.log(`Order: ${order.id}`);
    console.log(`Customer: ${order.customer.name}`);
    console.log(`Total: ${order.items.reduce((sum, item) =>
      sum + item.price * item.quantity, 0)}`);
  }
}

// Good: Move calculation to Order
class Order {
  getTotal(): number {
    return this.items.reduce((sum, item) =>
      sum + item.price * item.quantity, 0);
  }
}
过度亲密
  • 类之间过度依赖彼此的内部实现
  • 重构:移动方法、移动字段、将双向关系改为单向关系
消息链
  • 过长的方法调用链(a.b().c().d())
  • 重构:隐藏委托、提取方法
中间人
  • 只负责将调用委托给其他类的类
  • 重构:移除中间人、内联方法

重构技巧

组合方法
提取方法

最重要的重构。将代码片段转换为能体现意图的方法。

// Before
function printOwing() {
  printBanner();

  // Print details
  console.log(`name: ${name}`);
  console.log(`amount: ${getOutstanding()}`);
}

// After
function printOwing() {
  printBanner();
  printDetails();
}

function printDetails() {
  console.log(`name: ${name}`);
  console.log(`amount: ${getOutstanding()}`);
}
内联方法

当方法体与方法名一样清晰时使用。

用查询替换临时变量

用方法调用替换临时变量。

// Before
const basePrice = quantity * itemPrice;
if (basePrice > 1000) {
  return basePrice * 0.95;
}

// After
if (getBasePrice() > 1000) {
  return getBasePrice() * 0.95;
}

function getBasePrice() {
  return quantity * itemPrice;
}
移动特性
移动方法

将方法移到最常使用它的类中。

移动字段

将字段移到最常使用它的位置。

提取类

当一个类承担了本应由两个类完成的工作时使用。

内联类

当一个类没有承担多少工作时使用。

整理数据
封装字段

将字段设为私有,并提供访问器。

用对象替换数据值

当某项数据需要额外行为时使用。

简化条件表达式
分解条件表达式

将条件和分支提取为方法。

// Before
if (date.before(SUMMER_START) || date.after(SUMMER_END)) {
  charge = quantity * winterRate + winterServiceCharge;
} else {
  charge = quantity * summerRate;
}

// After
if (isWinter(date)) {
  charge = winterCharge(quantity);
} else {
  charge = summerCharge(quantity);
}
合并条件表达式

合并结果相同的条件表达式。

用守卫子句替换嵌套条件表达式

使用提前返回来减少嵌套。

// Before
function getPayAmount() {
  let result;
  if (isDead) {
    result = deadAmount();
  } else {
    if (isSeparated) {
      result = separatedAmount();
    } else {
      if (isRetired) {
        result = retiredAmount();
      } else {
        result = normalPayAmount();
      }
    }
  }
  return result;
}

// After
function getPayAmount() {
  if (isDead) return deadAmount();
  if (isSeparated) return separatedAmount();
  if (isRetired) return retiredAmount();
  return normalPayAmount();
}
简化方法调用
重命名方法

方法名应体现意图。

// Bad names
function calc(a, b) { ... }
function doIt() { ... }
function process(data) { ... }

// Good names
function calculateTotalPrice(items, taxRate) { ... }
function sendOrderConfirmation() { ... }
function validateUserCredentials(credentials) { ... }
添加/移除参数

让参数列表尽可能简洁且有意义。

引入参数对象

将相关参数组合起来。

保留整个对象

传递对象,而不是提取其中的值。

意图明确的代码

魔法数字和字面量

为不明显的字面量和业务规则命名。重复使用现有的平台或领域常量;如果常规字面量的含义已经明确,则直接内联。

// Bad
if (response.status === 200) { ... }
if (password.length < 8) { ... }
const timeout = 30000;

// Good
const HTTP_OK = 200;
const MIN_PASSWORD_LENGTH = 8;
const DEFAULT_TIMEOUT_MS = 30000;

if (response.status === HTTP_OK) { ... }
if (password.length < MIN_PASSWORD_LENGTH) { ... }
const timeout = DEFAULT_TIMEOUT_MS;
命名指南
  • 变量:用名词描述其保存的内容
  • 函数:用动词描述其执行的操作
  • 布尔值:用可回答“是/否”的问题命名
  • 类:用名词描述其代表的事物
// Bad naming
const d = new Date();
const flag = true;
function calc() { ... }

// Good naming
const currentDate = new Date();
const isUserAuthenticated = true;
function calculateShippingCost() { ... }
自解释代码

代码应在没有注释的情况下表达其意图。

// Bad: Needs comment to explain
// Check if user can access premium content
if (user.subscription !== null &&
    user.subscription.endDate > new Date() &&
    user.subscription.tier === 'premium') { ... }
// Good: Self-documenting
class User {
  constructor(private subscription: { endDate: Date; tier: string } | null) {}

  hasPremiumAccess(): boolean {
    return this.subscription !== null &&
           this.subscription.endDate > new Date() &&
           this.subscription.tier === 'premium';
  }
}

// Call user.hasPremiumAccess() from the condition.

小步迭代

重构的节奏
  1. 进行一项小改动
  2. 运行测试
  3. 测试通过则提交
  4. 重复以上步骤
安全的重构步骤
  • 一次只改一件事
  • 始终保持代码可运行
  • 如果测试失败,撤销改动并尝试更小的步骤
  • 频繁提交

输出格式

请按以下格式提供审查结果:

# Kent Beck Style Code Review Report

## Summary
- Overall Code Health: [Good/Needs Improvement/Critical]
- Key findings summary

## Code Smells Detected

### [Smell Category]: [Specific Smell]
- **Location**: file:line
- **Description**: What the problem is
- **Impact**: Why it matters
- **Suggested Refactoring**: How to fix it

## Refactoring Recommendations

### High Priority
1. [Most impactful improvements]

### Medium Priority
1. [Moderate improvements]

### Low Priority (Nice to Have)
1. [Minor improvements]

## Positive Aspects
- What the code does well

特定语言示例

TypeScript/JavaScript
// Code Smell: Primitive Obsession + Magic Numbers
function createDiscount(percent: number, startDate: string, endDate: string) {
  if (!Number.isFinite(percent) || percent < 0 || percent > 100) {
    throw new RangeError('Discount percent must be between 0 and 100');
  }
  // ...
}
// Refactored
const MIN_DISCOUNT_PERCENT = 0;
const MAX_DISCOUNT_PERCENT = 100;

class DiscountPercent {
  constructor(private value: number) {
    if (!Number.isFinite(value) || value < MIN_DISCOUNT_PERCENT || value > MAX_DISCOUNT_PERCENT) {
      throw new RangeError('Discount percent must be between 0 and 100');
    }
  }
}

class DateRange {
  constructor(
    private start: Date,
    private end: Date
  ) {
    if (!Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime()) || start > end) {
      throw new RangeError('Expected valid dates with start <= end');
    }
  }
}

function createDiscount(percent: DiscountPercent, period: DateRange) { /* ... */ }
Python
# Code Smell: Long Method + Feature Envy
def process_order(order):
    # Validation (should be in Order)
    if order['items'] is None or len(order['items']) == 0:
        raise ValueError('Empty order')

    # Calculation (should be in Order)
    total = 0
    for item in order['items']:
        total += item['price'] * item['quantity']

    # More processing...

# Refactored
class Order:
    def __init__(self, items: list[OrderItem]):
        self._validate_items(items)
        self.items = items

    def _validate_items(self, items):
        if not items:
            raise EmptyOrderError()

    def calculate_total(self) -> Money:
        return sum(item.subtotal for item in self.items)

def process_order(order: Order):
    total = order.calculate_total()
    # Processing with clear responsibilities...
Java/Kotlin
// Code Smell: Switch Statement + Primitive Obsession
fun calculateShipping(type: String, weight: Double): Double {
    return when (type) {
        "standard" -> weight * 1.5
        "express" -> weight * 3.0
        "overnight" -> weight * 5.0
        else -> throw IllegalArgumentException()
    }
}

// Refactored
sealed class ShippingMethod {
    abstract fun calculateCost(weight: Weight): Money

    object Standard : ShippingMethod() {
        private const val RATE_PER_KG = 1.5
        override fun calculateCost(weight: Weight) =
            Money(weight.kilograms * RATE_PER_KG)
    }

    object Express : ShippingMethod() {
        private const val RATE_PER_KG = 3.0
        override fun calculateCost(weight: Weight) =
            Money(weight.kilograms * RATE_PER_KG)
    }

    object Overnight : ShippingMethod() {
        private const val RATE_PER_KG = 5.0
        override fun calculateCost(weight: Weight) =
            Money(weight.kilograms * RATE_PER_KG)
    }
}

参考资料

评论

支持 Markdown