simplicity-principles

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Simplicity 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
undefined
elixir
undefined

COMPLEX - 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

```elixir

COMPLEX - 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

undefined
undefined

TypeScript 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 it
typescript
// 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 it
typescript
// 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
undefined
elixir
undefined

YAGNI 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 yet
end 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 yet
end 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

```elixir
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

```elixir

YAGNI 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
undefined
defmodule User do schema "users" do field :email, :string # Add fields when requirements emerge end end
undefined

TypeScript 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
undefined
elixir
undefined

ASTONISHING - 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

```elixir
def update_user(user, attrs) do Repo.update(User.changeset(user, attrs))

Caller handles cache invalidation explicitly

end

```elixir

ASTONISHING - 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
undefined
def get_or_create_user(id) do case Repo.get(User, id) do nil -> create_default_user(id) user -> user end end
undefined

TypeScript 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
    {:ok, result}
    or
    {:error, reason}
    (consistent)
  • React components with
    onPress
    not
    onClick
    (platform convention)
  • Ecto changesets don't touch database (pure validation)
  • GraphQL mutations clearly named:
    createTask
    ,
    updateTask
    ,
    deleteTask
  • 命令处理器返回
    {:ok, result}
    {:error, reason}
    (一致性)
  • React组件使用
    onPress
    而非
    onClick
    (平台惯例)
  • Ecto changesets 不直接操作数据库(纯验证)
  • GraphQL突变命名清晰:
    createTask
    updateTask
    deleteTask

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

可配合使用

  • solid-principles
    : Simple implementations of SOLID patterns
  • boy-scout-rule
    : Simplify when improving code
  • test-driven-development
    : Simple code is easier to test
  • elixir-code-quality-enforcer
    : Credo flags complexity
  • typescript-code-quality-enforcer
    : Linting enforces consistency
  • solid-principles
    : SOLID模式的简单实现
  • boy-scout-rule
    : 改进代码时简化实现
  • test-driven-development
    : 简单代码更易于测试
  • elixir-code-quality-enforcer
    : Credo 标记复杂度问题
  • 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)
“简单是终极的复杂。” - 列奥纳多·达·芬奇
  • 优先选择常规、经过验证的解决方案而非新颖方法
  • 根据实际需求逐步构建
  • 遵循惯例,让代码行为符合预期
  • 立即删除推测性代码
  • 简单不等于简陋(要妥善处理错误和边缘情况)

When in doubt, choose the simpler path

如有疑问,选择更简单的方案