simplicity-principles
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSimplicity Principles
简洁性原则
Write code that is simple, necessary, and unsurprising.
编写简洁、必要且符合预期的代码。
Three Core Principles
三大核心原则
1. KISS - Keep It Simple, Stupid
1. KISS - 保持简单就好
Simple solutions are better than clever ones
简单的解决方案优于精巧的方案
What Simple Means
什么是“简单”
- Readable by developers of varying skill levels
- Fewer moving parts and abstractions
- Direct and obvious implementation
- Easy to debug and test
- Minimal cognitive load
- 能被不同技能水平的开发者读懂
- 更少的组件和抽象层
- 直接且直观的实现方式
- 易于调试和测试
- 最小的认知负担
Elixir Examples
Elixir 示例
elixir
undefinedelixir
undefinedCOMPLEX - Over-engineered
COMPLEX - Over-engineered
defmodule PaymentCalculator do
use GenServer
def start_link(_), do: GenServer.start_link(MODULE, %{}, name: MODULE)
def calculate(items), do: GenServer.call(MODULE, {:calculate, items})
def handle_call({:calculate, items}, _from, state) do
result = Enum.reduce(items, Money.new(:USD, 0), &Money.add(&2, &1.price))
{:reply, result, state}
end
end
defmodule PaymentCalculator do
use GenServer
def start_link(_), do: GenServer.start_link(MODULE, %{}, name: MODULE)
def calculate(items), do: GenServer.call(MODULE, {:calculate, items})
def handle_call({:calculate, items}, _from, state) do
result = Enum.reduce(items, Money.new(:USD, 0), &Money.add(&2, &1.price))
{:reply, result, state}
end
end
SIMPLE - Just a function
SIMPLE - Just a function
defmodule PaymentCalculator do
def calculate(items) do
Enum.reduce(items, Money.new(:USD, 0), &Money.add(&2, &1.price))
end
end
defmodule PaymentCalculator do
def calculate(items) do
Enum.reduce(items, Money.new(:USD, 0), &Money.add(&2, &1.price))
end
end
Use GenServer only when you need state/concurrency
Use GenServer only when you need state/concurrency
```elixir
```elixirCOMPLEX - Unnecessary abstraction
COMPLEX - Unnecessary abstraction
defmodule UserQuery do
defmacro by_status(status) do
quote do
from u in User, where: u.status == ^unquote(status)
end
end
end
defmodule UserQuery do
defmacro by_status(status) do
quote do
from u in User, where: u.status == ^unquote(status)
end
end
end
SIMPLE - Direct query
SIMPLE - Direct query
def active_users do
from u in User, where: u.status == "active"
end
def inactive_users do
from u in User, where: u.status == "inactive"
end
def active_users do
from u in User, where: u.status == "active"
end
def inactive_users do
from u in User, where: u.status == "inactive"
end
Macros only when you need metaprogramming
Macros only when you need metaprogramming
undefinedundefinedTypeScript Examples
TypeScript 示例
typescript
// COMPLEX - Over-abstraction
class UserDataManager {
private dataSource: DataSource;
private cache: Cache;
private transformer: DataTransformer;
async getUser(id: string): Promise<User> {
const cached = await this.cache.get(id);
if (cached) return this.transformer.transform(cached);
const raw = await this.dataSource.fetch(id);
await this.cache.set(id, raw);
return this.transformer.transform(raw);
}
}
// SIMPLE - Direct approach
async function getUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// Add cache/transform only when performance demands ittypescript
// COMPLEX - Clever but confusing
const isValid = (x: number) => !!(x >= 0 && x <= 100);
const process = (items: number[]) =>
items.filter(isValid).reduce((a, b) => a + b, 0);
// SIMPLE - Clear intent
function calculateTotal(scores: number[]): number {
const validScores = scores.filter(score => score >= 0 && score <= 100);
return validScores.reduce((sum, score) => sum + score, 0);
}typescript
// COMPLEX - Over-abstraction
class UserDataManager {
private dataSource: DataSource;
private cache: Cache;
private transformer: DataTransformer;
async getUser(id: string): Promise<User> {
const cached = await this.cache.get(id);
if (cached) return this.transformer.transform(cached);
const raw = await this.dataSource.fetch(id);
await this.cache.set(id, raw);
return this.transformer.transform(raw);
}
}
// SIMPLE - Direct approach
async function getUser(id: string): Promise<User> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// Add cache/transform only when performance demands ittypescript
// COMPLEX - Clever but confusing
const isValid = (x: number) => !!(x >= 0 && x <= 100);
const process = (items: number[]) =>
items.filter(isValid).reduce((a, b) => a + b, 0);
// SIMPLE - Clear intent
function calculateTotal(scores: number[]): number {
const validScores = scores.filter(score => score >= 0 && score <= 100);
return validScores.reduce((sum, score) => sum + score, 0);
}KISS Guidelines
KISS 指导原则
- Prefer functions over classes (unless you need state)
- Prefer explicit over implicit
- Prefer boring over clever
- Prefer standard library over custom solutions
- Prefer clear names over short names
- Prefer straightforward logic over "elegant" one-liners
- 优先使用函数而非类(除非需要维护状态)
- 优先选择显式而非隐式
- 优先选择常规而非精巧
- 优先使用标准库而非自定义解决方案
- 优先选择清晰的名称而非简短的名称
- 优先选择直接的逻辑而非“优雅”的单行代码
When NOT to KISS
不需要遵循KISS的场景
- Performance-critical code (after profiling proves need)
- Preventing code duplication (after third instance)
- Enforcing constraints (types, validations)
- 性能关键代码(在性能分析证明有必要之后)
- 避免代码重复(出现第三次重复之后)
- 强制约束(类型、验证)
2. YAGNI - You Aren't Gonna Need It
2. YAGNI - 你不会需要它
Don't implement features until you actually need them
不要提前实现尚未真正需要的功能
Signs You're Violating YAGNI
违反YAGNI的迹象
- "We might need this someday"
- "Let me add flexibility for future use cases"
- "I'll build a generic solution"
- "This could be configurable"
- “我们某天可能会需要这个”
- “我来为未来的用例添加灵活性”
- “我要构建一个通用解决方案”
- “这个可以做成可配置的”
Elixir Examples (YAGNI)
Elixir 示例(YAGNI)
elixir
undefinedelixir
undefinedYAGNI VIOLATION - Premature abstraction
YAGNI VIOLATION - Premature abstraction
defmodule NotificationService do
def send(notification, opts \ []) do
provider = opts[:provider] || :default
priority = opts[:priority] || :normal
retry_strategy = opts[:retry_strategy] || :exponential
callback = opts[:callback]
# Complex routing logic for features we don't use yetend
end
defmodule NotificationService do
def send(notification, opts \ []) do
provider = opts[:provider] || :default
priority = opts[:priority] || :normal
retry_strategy = opts[:retry_strategy] || :exponential
callback = opts[:callback]
# Complex routing logic for features we don't use yetend
end
GOOD - Build what you need now
GOOD - Build what you need now
defmodule NotificationService do
def send_email(to, subject, body) do
Email.send(to, subject, body)
end
Add SMS when we actually need it
Add priorities when we have the requirement
Add retries when we see failures
end
```elixirdefmodule NotificationService do
def send_email(to, subject, body) do
Email.send(to, subject, body)
end
Add SMS when we actually need it
Add priorities when we have the requirement
Add retries when we see failures
end
```elixirYAGNI VIOLATION - Unused flexibility
YAGNI VIOLATION - Unused flexibility
defmodule User do
schema "users" do
field :email, :string
field :settings, :map # "For future configuration"
field :metadata, :map # "For anything we might need"
field :flags, {:array, :string} # "For feature flags"
# None of these are used yet!end
end
defmodule User do
schema "users" do
field :email, :string
field :settings, :map # "For future configuration"
field :metadata, :map # "For anything we might need"
field :flags, {:array, :string} # "For feature flags"
# None of these are used yet!end
end
GOOD - Add fields when needed
GOOD - Add fields when needed
defmodule User do
schema "users" do
field :email, :string
# Add fields when requirements emerge
end
end
undefineddefmodule User do
schema "users" do
field :email, :string
# Add fields when requirements emerge
end
end
undefinedTypeScript Examples (YAGNI)
TypeScript 示例(YAGNI)
typescript
// YAGNI VIOLATION - Premature generalization
interface DataFetcher<T, P extends object = object> {
fetch(params: P): Promise<T>;
cache?(params: P): Promise<void>;
invalidate?(key: string): Promise<void>;
prefetch?(params: P[]): Promise<void>;
// We don't use cache, invalidate, or prefetch yet!
}
// GOOD - Start simple
interface DataFetcher<T> {
fetch(params: object): Promise<T>;
// Add methods when we need caching
}typescript
// YAGNI VIOLATION - Configurable everything
interface ButtonProps {
onClick: () => void;
variant?: 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'outline';
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
shape?: 'rounded' | 'square' | 'pill';
shadow?: 'none' | 'sm' | 'md' | 'lg';
animation?: 'fade' | 'slide' | 'bounce';
// Design only uses 2 variants and 1 size!
}
// GOOD - Implement what designs require
interface ButtonProps {
onClick: () => void;
variant?: 'primary' | 'secondary'; // Only what we use
// Add options when design requires them
}typescript
// YAGNI VIOLATION - Premature generalization
interface DataFetcher<T, P extends object = object> {
fetch(params: P): Promise<T>;
cache?(params: P): Promise<void>;
invalidate?(key: string): Promise<void>;
prefetch?(params: P[]): Promise<void>;
// We don't use cache, invalidate, or prefetch yet!
}
// GOOD - Start simple
interface DataFetcher<T> {
fetch(params: object): Promise<T>;
// Add methods when we need caching
}typescript
// YAGNI VIOLATION - Configurable everything
interface ButtonProps {
onClick: () => void;
variant?: 'primary' | 'secondary' | 'tertiary' | 'ghost' | 'outline';
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
shape?: 'rounded' | 'square' | 'pill';
shadow?: 'none' | 'sm' | 'md' | 'lg';
animation?: 'fade' | 'slide' | 'bounce';
// Design only uses 2 variants and 1 size!
}
// GOOD - Implement what designs require
interface ButtonProps {
onClick: () => void;
variant?: 'primary' | 'secondary'; // Only what we use
// Add options when design requires them
}YAGNI Guidelines
YAGNI 指导原则
- Implement features when you have a concrete use case, not a hypothetical one
- Delete unused code immediately (it's in git)
- Start with hardcoded values, extract constants when they vary
- Build for today's requirements, refactor for tomorrow's
- Question every "nice to have" and "might need"
- 当有具体的使用场景时再实现功能,而非假设场景
- 立即删除未使用的代码(代码已在git中保存)
- 从硬编码值开始,当值需要变化时再提取为常量
- 针对当前需求构建,为未来需求重构
- 质疑每一个“锦上添花”和“可能需要”的功能
Exceptions to YAGNI
YAGNI 的例外情况
- Security features (implement defense in depth upfront)
- Data migrations (plan schema carefully)
- Public APIs (harder to change later)
- Accessibility (build in from start)
- 安全功能(提前实现纵深防御)
- 数据迁移(仔细规划 schema)
- 公共API(后期更难修改)
- 可访问性(从一开始就融入)
3. Principle of Least Astonishment (POLA)
3. 最少惊讶原则(POLA)
Code should behave the way users expect it to behave
代码的行为应符合用户的预期
What Makes Code Astonishing
哪些代码会让人感到惊讶
- Unexpected side effects
- Inconsistent naming
- Breaking conventions
- Hidden behavior
- Surprising return values
- 意外的副作用
- 不一致的命名
- 打破惯例
- 隐藏的行为
- 出乎意料的返回值
Elixir Examples (POLA)
Elixir 示例(POLA)
elixir
undefinedelixir
undefinedASTONISHING - Updates AND returns different thing
ASTONISHING - Updates AND returns different thing
def update_user(user, attrs) do
Repo.update!(User.changeset(user, attrs))
UserCache.invalidate(user.id) # Side effect!
:ok # Returns :ok instead of updated user!?
end
def update_user(user, attrs) do
Repo.update!(User.changeset(user, attrs))
UserCache.invalidate(user.id) # Side effect!
:ok # Returns :ok instead of updated user!?
end
EXPECTED - Clear behavior
EXPECTED - Clear behavior
def update_user(user, attrs) do
Repo.update(User.changeset(user, attrs))
Caller handles cache invalidation explicitly
end
```elixirdef update_user(user, attrs) do
Repo.update(User.changeset(user, attrs))
Caller handles cache invalidation explicitly
end
```elixirASTONISHING - Function name lies
ASTONISHING - Function name lies
def get_user(id) do
case Repo.get(User, id) do
nil ->
attrs = %{id: id, email: "#{id}@example.com"}
Repo.insert!(User.changeset(attrs)) # Created user in a getter!
user -> user
end
end
def get_user(id) do
case Repo.get(User, id) do
nil ->
attrs = %{id: id, email: "#{id}@example.com"}
Repo.insert!(User.changeset(attrs)) # Created user in a getter!
user -> user
end
end
EXPECTED - Name matches behavior
EXPECTED - Name matches behavior
def get_or_create_user(id) do
case Repo.get(User, id) do
nil -> create_default_user(id)
user -> user
end
end
undefineddef get_or_create_user(id) do
case Repo.get(User, id) do
nil -> create_default_user(id)
user -> user
end
end
undefinedTypeScript Examples (POLA)
TypeScript 示例(POLA)
typescript
// ASTONISHING - Function mutates input
function processTask(gig: Task): Task {
gig.status = 'processed'; // Mutates input!
gig.processedAt = new Date();
return gig;
}
// EXPECTED - Pure function
function processTask(gig: Task): Task {
return {
...gig,
status: 'processed',
processedAt: new Date(),
};
}typescript
// ASTONISHING - Inconsistent return types
async function getUser(id: string): Promise<User | null | undefined> {
// Returns null sometimes, undefined other times, no pattern
}
// EXPECTED - Consistent return
async function getUser(id: string): Promise<User | null> {
// Always null when not found
}typescript
// ASTONISHING - Breaking conventions
interface Props {
onPress?: () => void; // React convention: onX
clickHandler?: () => void; // Different convention in same interface!
onTapGesture?: () => void; // Yet another name for same thing!
}
// EXPECTED - Consistent conventions
interface Props {
onPress?: () => void;
onLongPress?: () => void;
onDoublePress?: () => void;
}typescript
// ASTONISHING - Function mutates input
function processTask(gig: Task): Task {
gig.status = 'processed'; // Mutates input!
gig.processedAt = new Date();
return gig;
}
// EXPECTED - Pure function
function processTask(gig: Task): Task {
return {
...gig,
status: 'processed',
processedAt: new Date(),
};
}typescript
// ASTONISHING - Inconsistent return types
async function getUser(id: string): Promise<User | null | undefined> {
// Returns null sometimes, undefined other times, no pattern
}
// EXPECTED - Consistent return
async function getUser(id: string): Promise<User | null> {
// Always null when not found
}typescript
// ASTONISHING - Breaking conventions
interface Props {
onPress?: () => void; // React convention: onX
clickHandler?: () => void; // Different convention in same interface!
onTapGesture?: () => void; // Yet another name for same thing!
}
// EXPECTED - Consistent conventions
interface Props {
onPress?: () => void;
onLongPress?: () => void;
onDoublePress?: () => void;
}POLA Guidelines
POLA 指导原则
- Follow framework conventions (Phoenix, React, Relay)
- Use clear, descriptive names that match behavior
- Return what the function name promises
- Keep side effects explicit or avoid them
- Be consistent within the codebase
- Match platform conventions (iOS, Android, Web)
- Honor principle of least surprise in APIs
- 遵循框架惯例(Phoenix、React、Relay)
- 使用清晰、描述性的名称,与行为匹配
- 返回值符合函数名称的承诺
- 让副作用显式化或避免副作用
- 在代码库中保持一致性
- 匹配平台惯例(iOS、Android、Web)
- 在API设计中遵循最少惊讶原则
Examples of Good POLA in YourApp
你的应用中符合POLA的示例
- Command handlers return or
{:ok, result}(consistent){:error, reason} - React components with not
onPress(platform convention)onClick - Ecto changesets don't touch database (pure validation)
- GraphQL mutations clearly named: ,
createTask,updateTaskdeleteTask
- 命令处理器返回 或
{:ok, result}(一致性){:error, reason} - React组件使用 而非
onPress(平台惯例)onClick - Ecto changesets 不直接操作数据库(纯验证)
- GraphQL突变命名清晰:、
createTask、updateTaskdeleteTask
Application Checklist
应用检查清单
Before implementing
实现前
- Is this the simplest solution? (KISS)
- Do we actually need this now? (YAGNI)
- Will this behavior surprise users? (POLA)
- 这是最简单的解决方案吗?(KISS)
- 我们现在真的需要这个吗?(YAGNI)
- 这个行为会让用户感到惊讶吗?(POLA)
During implementation
实现中
- Prefer straightforward over clever
- Implement only what's required
- Follow established conventions
- Name things accurately
- Make side effects explicit
- 优先选择直接而非精巧
- 只实现必要的部分
- 遵循已有的惯例
- 准确命名
- 让副作用显式化
During code review
代码审查中
- Is there a simpler approach?
- Are we building speculative features?
- Does the API behave as expected?
- Are conventions followed?
- 有没有更简单的方法?
- 我们是否在构建推测性的功能?
- API的行为符合预期吗?
- 是否遵循了惯例?
Red Flags
危险信号
KISS Violations
KISS 违反
- "Let me show you this clever trick..."
- More than 3 levels of abstraction
- Requires 10-minute explanation
- Uses advanced language features unnecessarily
- “让我给你展示这个精巧的技巧...”
- 超过3层的抽象
- 需要10分钟的解释
- 不必要地使用高级语言特性
YAGNI Violations
YAGNI 违反
- "We might need this later..."
- Unused parameters/options
- Configurable everything
- "Generic framework" for 2 use cases
- “我们以后可能会需要这个...”
- 未使用的参数/选项
- 一切都做成可配置的
- 为2个用例构建“通用框架”
POLA Violations
POLA 违反
- "Well, technically it does..."
- Inconsistent naming
- Hidden side effects
- Surprising error conditions
- “从技术上讲,它确实可以...”
- 不一致的命名
- 隐藏的副作用
- 出乎意料的错误情况
Integration with Existing Skills
与现有技能的集成
Works with
可配合使用
- : Simple implementations of SOLID patterns
solid-principles - : Simplify when improving code
boy-scout-rule - : Simple code is easier to test
test-driven-development - : Credo flags complexity
elixir-code-quality-enforcer - : Linting enforces consistency
typescript-code-quality-enforcer
- : SOLID模式的简单实现
solid-principles - : 改进代码时简化实现
boy-scout-rule - : 简单代码更易于测试
test-driven-development - : Credo 标记复杂度问题
elixir-code-quality-enforcer - : 代码检查工具强制一致性
typescript-code-quality-enforcer
Remember
谨记
"Simplicity is the ultimate sophistication." - Leonardo da Vinci
- Prefer boring, proven solutions over novel approaches
- Build incrementally based on actual requirements
- Follow conventions so code behaves as expected
- Delete speculative code immediately
- Simple != simplistic (handle errors, edge cases properly)
“简单是终极的复杂。” - 列奥纳多·达·芬奇
- 优先选择常规、经过验证的解决方案而非新颖方法
- 根据实际需求逐步构建
- 遵循惯例,让代码行为符合预期
- 立即删除推测性代码
- 简单不等于简陋(要妥善处理错误和边缘情况)