关于 kent-beck-style
这项代码审查 Skill 运用 Kent Beck 的重构理念和简单设计原则评估可读性与可维护性。它识别代码异味并提出重构方法,强调小步、可测试的改动,避免臆测需求带来的复杂性。
主要功能
- 01识别常见代码异味
- 02运用简单设计四项规则
- 03检查 YAGNI 和 KISS
- 04提出针对性的重构方法
- 05强调小步且可测试的改动
适用场景
- 01在代码审查中发现代码异味
- 02改善命名和可读性
- 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 按优先级排列的规则:
- 通过测试——代码必须正确运行
- 揭示意图——代码应清晰地表达其用途
- 消除重复——DRY(不要重复自己)
- 元素最少——尽量减少类、方法和代码
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.
小步迭代
重构的节奏
- 进行一项小改动
- 运行测试
- 测试通过则提交
- 重复以上步骤
安全的重构步骤
- 一次只改一件事
- 始终保持代码可运行
- 如果测试失败,撤销改动并尝试更小的步骤
- 频繁提交
输出格式
请按以下格式提供审查结果:
# 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