orthogonality-principle

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Orthogonality Principle

正交性原则

Build systems where components are independent and changes don't ripple unexpectedly.
构建组件相互独立、修改不会引发意外连锁反应的系统。

What is Orthogonality?

什么是正交性?

Orthogonal (from mathematics): Two lines are orthogonal if they're at right angles - changing one doesn't affect the other.
In software: Components are orthogonal when changing one doesn't require changing others. They are independent and non-overlapping.
正交(源自数学):两条直线成直角时即为正交——改变其中一条不会影响另一条。
在软件领域中:当修改一个组件无需改动其他组件时,这些组件就是正交的。它们相互独立、功能无重叠。

Benefits

优势

  • Changes are localized (less debugging)
  • Easy to test in isolation
  • Components are reusable
  • Less coupling = less complexity
  • Easier to understand and maintain
  • 修改仅影响局部(减少调试工作量)
  • 易于独立测试
  • 组件可复用
  • 低耦合 = 低复杂度
  • 更易于理解和维护

Signs of Non-Orthogonality

非正交性的迹象

Red flags indicating components are NOT orthogonal

表明组件非正交的危险信号

  1. Change amplification: Changing one thing requires changing many others
  2. Shotgun surgery: One feature scattered across many files
  3. Tight coupling: Components know too much about each other
  4. Duplicate logic: Same concept implemented multiple ways
  5. Cascading changes: Change in A breaks B, C, D unexpectedly
  1. 修改扩散:改动一处需要同时修改多处
  2. 霰弹式修改:一个功能分散在多个文件中
  3. 紧耦合:组件之间过度了解彼此的内部实现
  4. 逻辑重复:同一概念被多次实现
  5. 连锁变更:修改组件A意外导致组件B、C、D出现问题

Achieving Orthogonality

实现正交性的方法

1. Separation of Concerns

1. 关注点分离

Keep unrelated responsibilities separate

将无关职责分开

Elixir Example

Elixir 示例

elixir
undefined
elixir
undefined

NON-ORTHOGONAL - Mixed concerns

NON-ORTHOGONAL - Mixed concerns

defmodule UserController do def create(conn, params) do # Validation if valid_email?(params["email"]) do # Database user = Repo.insert!(%User{email: params["email"]})
  # External API
  Stripe.create_customer(user.email)

  # Notification
  Email.send_welcome(user.email)

  # Logging
  Logger.info("Created user #{user.id}")

  # Response
  json(conn, %{user: user})
end
end end
defmodule UserController do def create(conn, params) do # Validation if valid_email?(params["email"]) do # Database user = Repo.insert!(%User{email: params["email"]})
  # External API
  Stripe.create_customer(user.email)

  # Notification
  Email.send_welcome(user.email)

  # Logging
  Logger.info("Created user #{user.id}")

  # Response
  json(conn, %{user: user})
end
end end

Changing email format affects validation, database, Stripe, email!

Changing email format affects validation, database, Stripe, email!

ORTHOGONAL - Separated concerns

ORTHOGONAL - Separated concerns

defmodule UserController do def create(conn, params) do with {:ok, command} <- build_command(params), {:ok, user} <- UserService.create(command) do json(conn, %{user: user}) end end end
defmodule UserService do def create(command) do with {:ok, user} <- Repo.insert(User.changeset(command)), :ok <- BillingService.setup_customer(user), :ok <- NotificationService.welcome(user) do {:ok, user} end end end
defmodule UserController do def create(conn, params) do with {:ok, command} <- build_command(params), {:ok, user} <- UserService.create(command) do json(conn, %{user: user}) end end end
defmodule UserService do def create(command) do with {:ok, user} <- Repo.insert(User.changeset(command)), :ok <- BillingService.setup_customer(user), :ok <- NotificationService.welcome(user) do {:ok, user} end end end

Now can change billing without touching notifications

Now can change billing without touching notifications

Can change notifications without touching database

Can change notifications without touching database

Each service is orthogonal

Each service is orthogonal

undefined
undefined

TypeScript Example

TypeScript 示例

typescript
// NON-ORTHOGONAL - Everything in one component
function TaskList() {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [filters, setFilters] = useState<Filters>({});
  const [sorting, setSorting] = useState<Sort>({ field: 'date', dir: 'asc' });

  // Data fetching
  useEffect(() => {
    fetch('/api/tasks').then(res => res.json()).then(setTasks);
  }, []);

  // Filtering logic
  const filtered = tasks.filter(gig => {
    if (filters.status && gig.status !== filters.status) return false;
    if (filters.location && !gig.location.includes(filters.location)) return false;
    return true;
  });

  // Sorting logic
  const sorted = [...filtered].sort((a, b) => {
    const aVal = a[sorting.field];
    const bVal = b[sorting.field];
    return sorting.dir === 'asc' ? aVal - bVal : bVal - aVal;
  });

  // Rendering
  return (
    <View>
      {/* Filters UI */}
      {/* Sorting UI */}
      {/* List UI */}
    </View>
  );
}
// Changing filtering affects fetching, sorting, rendering!

// ORTHOGONAL - Separated concerns
function useTaskData() {
  const [tasks, setTasks] = useState<Task[]>([]);
  useEffect(() => {
    fetch('/api/tasks').then(res => res.json()).then(setTasks);
  }, []);
  return tasks;
}

function useTaskFiltering(tasks: Task[], filters: Filters) {
  return useMemo(() => {
    return tasks.filter(gig => {
      if (filters.status && gig.status !== filters.status) return false;
      if (filters.location && !gig.location.includes(filters.location)) return false;
      return true;
    });
  }, [tasks, filters]);
}

function useTaskSorting(tasks: Task[], sort: Sort) {
  return useMemo(() => {
    return [...tasks].sort((a, b) => {
      const aVal = a[sort.field];
      const bVal = b[sort.field];
      return sort.dir === 'asc' ? aVal - bVal : bVal - aVal;
    });
  }, [tasks, sort]);
}

function TaskList() {
  const allTasks = useTaskData();
  const [filters, setFilters] = useState<Filters>({});
  const [sort, setSort] = useState<Sort>({ field: 'date', dir: 'asc' });

  const filtered = useTaskFiltering(allTasks, filters);
  const sorted = useTaskSorting(filtered, sort);

  return (
    <View>
      <TaskFilters filters={filters} onChange={setFilters} />
      <TaskSorting sort={sort} onChange={setSort} />
      <TaskCards tasks={sorted} />
    </View>
  );
}
// Now can change filtering without touching sorting
// Can change data fetching without touching UI
// Each concern is orthogonal
typescript
// NON-ORTHOGONAL - Everything in one component
function TaskList() {
  const [tasks, setTasks] = useState<Task[]>([]);
  const [filters, setFilters] = useState<Filters>({});
  const [sorting, setSorting] = useState<Sort>({ field: 'date', dir: 'asc' });

  // Data fetching
  useEffect(() => {
    fetch('/api/tasks').then(res => res.json()).then(setTasks);
  }, []);

  // Filtering logic
  const filtered = tasks.filter(gig => {
    if (filters.status && gig.status !== filters.status) return false;
    if (filters.location && !gig.location.includes(filters.location)) return false;
    return true;
  });

  // Sorting logic
  const sorted = [...filtered].sort((a, b) => {
    const aVal = a[sorting.field];
    const bVal = b[sorting.field];
    return sorting.dir === 'asc' ? aVal - bVal : bVal - aVal;
  });

  // Rendering
  return (
    <View>
      {/* Filters UI */}
      {/* Sorting UI */}
      {/* List UI */}
    </View>
  );
}
// Changing filtering affects fetching, sorting, rendering!

// ORTHOGONAL - Separated concerns
function useTaskData() {
  const [tasks, setTasks] = useState<Task[]>([]);
  useEffect(() => {
    fetch('/api/tasks').then(res => res.json()).then(setTasks);
  }, []);
  return tasks;
}

function useTaskFiltering(tasks: Task[], filters: Filters) {
  return useMemo(() => {
    return tasks.filter(gig => {
      if (filters.status && gig.status !== filters.status) return false;
      if (filters.location && !gig.location.includes(filters.location)) return false;
      return true;
    });
  }, [tasks, filters]);
}

function useTaskSorting(tasks: Task[], sort: Sort) {
  return useMemo(() => {
    return [...tasks].sort((a, b) => {
      const aVal = a[sort.field];
      const bVal = b[sort.field];
      return sort.dir === 'asc' ? aVal - bVal : bVal - aVal;
    });
  }, [tasks, sort]);
}

function TaskList() {
  const allTasks = useTaskData();
  const [filters, setFilters] = useState<Filters>({});
  const [sort, setSort] = useState<Sort>({ field: 'date', dir: 'asc' });

  const filtered = useTaskFiltering(allTasks, filters);
  const sorted = useTaskSorting(filtered, sort);

  return (
    <View>
      <TaskFilters filters={filters} onChange={setFilters} />
      <TaskSorting sort={sort} onChange={setSort} />
      <TaskCards tasks={sorted} />
    </View>
  );
}
// Now can change filtering without touching sorting
// Can change data fetching without touching UI
// Each concern is orthogonal

2. Interface Segregation

2. 接口隔离

Create focused, minimal interfaces

创建聚焦、精简的接口

Elixir Example (Interface Segregation)

Elixir 示例(接口隔离)

elixir
undefined
elixir
undefined

NON-ORTHOGONAL - Fat interface

NON-ORTHOGONAL - Fat interface

defmodule DataStore do @callback get(key :: String.t()) :: {:ok, term()} | {:error, term()} @callback set(key :: String.t(), value :: term()) :: :ok @callback delete(key :: String.t()) :: :ok @callback list_all() :: [term()] @callback search(query :: String.t()) :: [term()] @callback bulk_insert(items :: [term()]) :: :ok @callback export_to_json() :: String.t() @callback import_from_json(json :: String.t()) :: :ok end
defmodule DataStore do @callback get(key :: String.t()) :: {:ok, term()} | {:error, term()} @callback set(key :: String.t(), value :: term()) :: :ok @callback delete(key :: String.t()) :: :ok @callback list_all() :: [term()] @callback search(query :: String.t()) :: [term()] @callback bulk_insert(items :: [term()]) :: :ok @callback export_to_json() :: String.t() @callback import_from_json(json :: String.t()) :: :ok end

Implementing simple cache requires implementing export/import!

Implementing simple cache requires implementing export/import!

Not orthogonal - simple use cases coupled to complex ones

Not orthogonal - simple use cases coupled to complex ones

ORTHOGONAL - Segregated interfaces

ORTHOGONAL - Segregated interfaces

defmodule KeyValueStore do @callback get(key :: String.t()) :: {:ok, term()} | {:error, term()} @callback set(key :: String.t(), value :: term()) :: :ok @callback delete(key :: String.t()) :: :ok end
defmodule Searchable do @callback search(query :: String.t()) :: [term()] end
defmodule BulkOperations do @callback bulk_insert(items :: [term()]) :: :ok end
defmodule Exportable do @callback export_to_json() :: String.t() @callback import_from_json(json :: String.t()) :: :ok end
defmodule KeyValueStore do @callback get(key :: String.t()) :: {:ok, term()} | {:error, term()} @callback set(key :: String.t(), value :: term()) :: :ok @callback delete(key :: String.t()) :: :ok end
defmodule Searchable do @callback search(query :: String.t()) :: [term()] end
defmodule BulkOperations do @callback bulk_insert(items :: [term()]) :: :ok end
defmodule Exportable do @callback export_to_json() :: String.t() @callback import_from_json(json :: String.t()) :: :ok end

Simple cache implements only KeyValueStore

Simple cache implements only KeyValueStore

Search index implements KeyValueStore + Searchable

Search index implements KeyValueStore + Searchable

Each interface is orthogonal to others

Each interface is orthogonal to others

undefined
undefined

3. Dependency Injection

3. 依赖注入

Don't hardcode dependencies - inject them

不要硬编码依赖项——采用注入方式

Elixir Example (Dependency Injection)

Elixir 示例(依赖注入)

elixir
undefined
elixir
undefined

NON-ORTHOGONAL - Hardcoded dependencies

NON-ORTHOGONAL - Hardcoded dependencies

defmodule OrderService do def create_order(items) do PaymentService.charge(items) # Coupled InventoryService.reserve(items) # Coupled EmailService.send_confirmation() # Coupled end end
defmodule OrderService do def create_order(items) do PaymentService.charge(items) # Coupled InventoryService.reserve(items) # Coupled EmailService.send_confirmation() # Coupled end end

Can't test without real payment/inventory/email services

Can't test without real payment/inventory/email services

Can't swap implementations

Can't swap implementations

ORTHOGONAL - Injected dependencies

ORTHOGONAL - Injected dependencies

defmodule OrderService do def create_order(items, deps \ default_deps()) do with :ok <- deps.payment.charge(items), :ok <- deps.inventory.reserve(items), :ok <- deps.email.send_confirmation() do :ok end end
defp default_deps do %{ payment: PaymentService, inventory: InventoryService, email: EmailService } end end
defmodule OrderService do def create_order(items, deps \ default_deps()) do with :ok <- deps.payment.charge(items), :ok <- deps.inventory.reserve(items), :ok <- deps.email.send_confirmation() do :ok end end
defp default_deps do %{ payment: PaymentService, inventory: InventoryService, email: EmailService } end end

Can test with mocks

Can test with mocks

test "creates order" do deps = %{ payment: MockPayment, inventory: MockInventory, email: MockEmail } assert :ok = OrderService.create_order(items, deps) end
test "creates order" do deps = %{ payment: MockPayment, inventory: MockInventory, email: MockEmail } assert :ok = OrderService.create_order(items, deps) end

Each dependency is orthogonal - can change independently

Each dependency is orthogonal - can change independently

undefined
undefined

4. Event-Driven Architecture

4. 事件驱动架构

Decouple through events instead of direct calls

通过事件而非直接调用实现解耦

Elixir Example (Event-Driven Architecture)

Elixir 示例(事件驱动架构)

elixir
undefined
elixir
undefined

NON-ORTHOGONAL - Direct coupling

NON-ORTHOGONAL - Direct coupling

defmodule UserService do def create_user(attrs) do {:ok, user} = Repo.insert(User.changeset(attrs))
# Directly coupled to all these services
BillingService.create_customer(user)
AnalyticsService.track_signup(user)
EmailService.send_welcome(user)
CacheService.invalidate("users")

{:ok, user}
end end
defmodule UserService do def create_user(attrs) do {:ok, user} = Repo.insert(User.changeset(attrs))
# Directly coupled to all these services
BillingService.create_customer(user)
AnalyticsService.track_signup(user)
EmailService.send_welcome(user)
CacheService.invalidate("users")

{:ok, user}
end end

Adding new behavior requires modifying UserService

Adding new behavior requires modifying UserService

Removing email feature requires modifying UserService

Removing email feature requires modifying UserService

ORTHOGONAL - Event-driven

ORTHOGONAL - Event-driven

defmodule UserService do def create_user(attrs) do {:ok, user} = Repo.insert(User.changeset(attrs))
# Publish event - don't know who listens
EventBus.publish({:user_created, user})

{:ok, user}
end end
defmodule UserService do def create_user(attrs) do {:ok, user} = Repo.insert(User.changeset(attrs))
# Publish event - don't know who listens
EventBus.publish({:user_created, user})

{:ok, user}
end end

Subscribers are orthogonal

Subscribers are orthogonal

defmodule BillingSubscriber do def handle_event({:user_created, user}) do BillingService.create_customer(user) end end
defmodule AnalyticsSubscriber do def handle_event({:user_created, user}) do AnalyticsService.track_signup(user) end end
defmodule BillingSubscriber do def handle_event({:user_created, user}) do BillingService.create_customer(user) end end
defmodule AnalyticsSubscriber do def handle_event({:user_created, user}) do AnalyticsService.track_signup(user) end end

Add/remove subscribers without touching UserService

Add/remove subscribers without touching UserService

Each subscriber is orthogonal to others

Each subscriber is orthogonal to others

undefined
undefined

TypeScript Example (Event-Driven Architecture)

TypeScript 示例(事件驱动架构)

typescript
// NON-ORTHOGONAL - Direct coupling
class TaskManager {
  createTask(data: TaskData) {
    const gig = this.repository.save(data);

    // Directly coupled
    this.notificationService.notifyUsersNearby(gig);
    this.searchIndex.addTask(gig);
    this.analyticsService.trackTaskCreated(gig);

    return gig;
  }
}

// ORTHOGONAL - Event-driven
class TaskManager {
  constructor(
    private repository: TaskRepository,
    private eventBus: EventBus
  ) {}

  createTask(data: TaskData) {
    const gig = this.repository.save(data);

    // Publish event
    this.eventBus.publish('gig.created', gig);

    return gig;
  }
}

// Orthogonal subscribers
eventBus.subscribe('gig.created', (gig) => {
  notificationService.notifyUsersNearby(gig);
});

eventBus.subscribe('gig.created', (gig) => {
  searchIndex.addTask(gig);
});

// Add/remove subscribers without changing TaskManager
typescript
// NON-ORTHOGONAL - Direct coupling
class TaskManager {
  createTask(data: TaskData) {
    const gig = this.repository.save(data);

    // Directly coupled
    this.notificationService.notifyUsersNearby(gig);
    this.searchIndex.addTask(gig);
    this.analyticsService.trackTaskCreated(gig);

    return gig;
  }
}

// ORTHOGONAL - Event-driven
class TaskManager {
  constructor(
    private repository: TaskRepository,
    private eventBus: EventBus
  ) {}

  createTask(data: TaskData) {
    const gig = this.repository.save(data);

    // Publish event
    this.eventBus.publish('gig.created', gig);

    return gig;
  }
}

// Orthogonal subscribers
eventBus.subscribe('gig.created', (gig) => {
  notificationService.notifyUsersNearby(gig);
});

eventBus.subscribe('gig.created', (gig) => {
  searchIndex.addTask(gig);
});

// Add/remove subscribers without changing TaskManager

5. Data Orthogonality

5. 数据正交性

Don't duplicate data - maintain single source of truth

不要重复存储数据——维护单一数据源

Elixir Example (Data Orthogonality)

Elixir 示例(数据正交性)

elixir
undefined
elixir
undefined

NON-ORTHOGONAL - Duplicate data

NON-ORTHOGONAL - Duplicate data

defmodule Task do schema "tasks" do field :hourly_rate, :decimal field :total_hours, :integer field :total_amount, :decimal # Calculated from rate * hours # Changing hourly_rate requires updating total_amount end end
defmodule Task do schema "tasks" do field :hourly_rate, :decimal field :total_hours, :integer field :total_amount, :decimal # Calculated from rate * hours # Changing hourly_rate requires updating total_amount end end

ORTHOGONAL - Computed fields

ORTHOGONAL - Computed fields

defmodule Task do schema "tasks" do field :hourly_rate, :decimal field :total_hours, :integer # total_amount computed on demand end
def total_amount(%{hourly_rate: rate, total_hours: hours}) do Decimal.mult(rate, hours) end end
defmodule Task do schema "tasks" do field :hourly_rate, :decimal field :total_hours, :integer # total_amount computed on demand end
def total_amount(%{hourly_rate: rate, total_hours: hours}) do Decimal.mult(rate, hours) end end

Single source of truth - rate and hours

Single source of truth - rate and hours

total_amount always correct, no sync issues

total_amount always correct, no sync issues

undefined
undefined

TypeScript Example (Data Orthogonality)

TypeScript 示例(数据正交性)

typescript
// NON-ORTHOGONAL - Duplicate state
interface Assignment {
  status: 'pending' | 'active' | 'completed';
  isPending: boolean;  // Duplicates status
  isActive: boolean;   // Duplicates status
  isCompleted: boolean; // Duplicates status
}
// Have to keep all flags in sync with status

// ORTHOGONAL - Single source of truth
interface Assignment {
  status: 'pending' | 'active' | 'completed';
}

// Derive flags from status
function isPending(engagement: Assignment): boolean {
  return engagement.status === 'pending';
}

function isActive(engagement: Assignment): boolean {
  return engagement.status === 'active';
}
// One source of truth, no sync issues
typescript
// NON-ORTHOGONAL - Duplicate state
interface Assignment {
  status: 'pending' | 'active' | 'completed';
  isPending: boolean;  // Duplicates status
  isActive: boolean;   // Duplicates status
  isCompleted: boolean; // Duplicates status
}
// Have to keep all flags in sync with status

// ORTHOGONAL - Single source of truth
interface Assignment {
  status: 'pending' | 'active' | 'completed';
}

// Derive flags from status
function isPending(engagement: Assignment): boolean {
  return engagement.status === 'pending';
}

function isActive(engagement: Assignment): boolean {
  return engagement.status === 'active';
}
// One source of truth, no sync issues

Practical Guidelines

实用指南

When designing modules

设计模块时

  • Each module has a single, clear purpose
  • Modules don't share internal data structures
  • Changes to one module rarely require changes to others
  • Can test each module independently
  • 每个模块有单一、明确的目标
  • 模块之间不共享内部数据结构
  • 修改一个模块几乎不需要改动其他模块
  • 可以独立测试每个模块

When designing APIs

设计API时

  • Each endpoint/function does ONE thing
  • Parameters are independent (changing one doesn't require changing others)
  • Return values are minimal (only what's needed)
  • No hidden coupling between API calls
  • 每个端点/函数只负责一件事
  • 参数相互独立(修改一个参数无需改动其他参数)
  • 返回值精简(仅返回必要内容)
  • API调用之间无隐藏耦合

When designing data

设计数据时

  • One source of truth for each piece of data
  • Computed values are computed, not stored
  • No duplicate information
  • Schema changes are localized
  • 每份数据都有单一数据源
  • 计算值按需计算,而非存储
  • 无重复信息
  • Schema变更仅影响局部

When designing systems

设计系统时

  • Components communicate through well-defined interfaces
  • Use events for loose coupling
  • Dependencies are injected, not hardcoded
  • Can replace components without affecting others
  • 组件通过定义清晰的接口通信
  • 使用事件实现松耦合协作
  • 依赖项采用注入方式,而非硬编码
  • 可以替换组件而不影响其他部分

Testing Orthogonality

测试正交性

Good test: Tests one component without needing to set up unrelated components
elixir
undefined
良好的测试:无需搭建无关组件即可测试单个组件
elixir
undefined

ORTHOGONAL - Test in isolation

ORTHOGONAL - Test in isolation

test "calculates gig total" do gig = %Task{hourly_rate: Decimal.new(25), total_hours: 8} assert Task.total_amount(gig) == Decimal.new(200) end
test "calculates gig total" do gig = %Task{hourly_rate: Decimal.new(25), total_hours: 8} assert Task.total_amount(gig) == Decimal.new(200) end

No database, no external services, pure logic

No database, no external services, pure logic

NON-ORTHOGONAL - Requires full setup

NON-ORTHOGONAL - Requires full setup

test "calculates gig total" do {:ok, requester} = create_requester() {:ok, worker} = create_worker() {:ok, gig} = create_gig(requester) {:ok, engagement} = create_engagement(gig, worker) {:ok, shift} = create_shift(engagement, hours: 8)
assert calculate_total(shift) == Decimal.new(200) end
test "calculates gig total" do {:ok, requester} = create_requester() {:ok, worker} = create_worker() {:ok, gig} = create_gig(requester) {:ok, engagement} = create_engagement(gig, worker) {:ok, shift} = create_shift(engagement, hours: 8)
assert calculate_total(shift) == Decimal.new(200) end

Have to set up requester, worker, gig, engagement just to test math

Have to set up requester, worker, gig, engagement just to test math

undefined
undefined

Examples

示例

Orthogonal patterns in the codebase

代码库中的正交模式

  1. CQRS: Commands/Queries are orthogonal
    • Change query without affecting command
    • Add command without changing queries
  2. Atomic Design: Atoms/Molecules/Organisms are orthogonal
    • Change atom styling without affecting organisms
    • Add new molecule without touching existing ones
  3. GraphQL Schema: Types are orthogonal
    • Add fields to one type without affecting others
    • Each type has focused responsibility
  4. Microservices: Bounded contexts are orthogonal
    • Change billing without affecting scheduling
    • Add analytics without touching core services
  1. CQRS:命令/查询是正交的
    • 修改查询逻辑无需影响命令逻辑
    • 添加新命令无需改动查询逻辑
  2. 原子设计:原子/分子/生物组件是正交的
    • 修改原子样式无需影响生物组件
    • 添加新分子组件无需改动现有组件
  3. GraphQL Schema:类型是正交的
    • 为一个类型添加字段无需影响其他类型
    • 每个类型都有聚焦的职责
  4. 微服务:限界上下文是正交的
    • 修改计费服务无需影响调度服务
    • 添加分析服务无需改动核心服务

Non-orthogonal anti-patterns to avoid

需要避免的非正交反模式

  1. Shared mutable state (global variables)
  2. Deep inheritance hierarchies
  3. Circular dependencies
  4. God objects (modules that do everything)
  5. Feature envy (functions in module A that mostly use data from module B)
  1. 共享可变状态(全局变量)
  2. 深层继承层级
  3. 循环依赖
  4. 上帝对象(包揽所有功能的模块)
  5. 特性羡慕(模块A中的函数主要使用模块B的数据)

Integration with Existing Skills

与现有技能的集成

Works with

兼容的技能

  • solid-principles
    : Single Responsibility → Orthogonality
  • structural-design-principles
    : Encapsulation → Orthogonality
  • simplicity-principles
    : KISS → Fewer dependencies → More orthogonal
  • cqrs-pattern
    : Commands/Queries naturally orthogonal
  • atomic-design-pattern
    : Component hierarchy naturally orthogonal
  • solid-principles
    :单一职责 → 正交性
  • structural-design-principles
    :封装 → 正交性
  • simplicity-principles
    :KISS → 更少依赖 → 更高正交性
  • cqrs-pattern
    :命令/查询天然正交
  • atomic-design-pattern
    :组件层级天然正交

Red Flags

危险信号

Signs of non-orthogonality

非正交性的迹象

  • "If I change X, I also have to change Y, Z, and W"
  • "I can't test this without setting up half the system"
  • "These two modules always change together"
  • "I have to keep these fields in sync"
  • "This module knows about too many other modules"
  • “如果我修改X,还必须修改Y、Z和W”
  • “我必须搭建半个系统才能测试这个功能”
  • “这两个模块总是需要一起修改”
  • “我必须保持这些字段同步”
  • “这个模块了解太多其他模块的细节”

Questions to ask

要问的问题

  • Can I change this independently?
  • Can I test this in isolation?
  • Is this the only place with this logic?
  • If I remove this, what breaks?
  • 我可以独立修改这个部分吗?
  • 我可以隔离测试这个部分吗?
  • 这是唯一包含该逻辑的地方吗?
  • 如果我移除这个部分,会有什么东西被破坏?

Remember

请记住

"Orthogonal systems are easier to design, build, test, and extend."
  • The Pragmatic Programmer
“正交系统更易于设计、构建、测试和扩展。”
  • 《程序员修炼之道》

Orthogonality = Independence

正交性 = 独立性

  • Separate concerns into independent components
  • Minimize coupling between components
  • Use events for loose coordination
  • Maintain single source of truth
  • Test components in isolation
The more orthogonal your system, the more flexible and maintainable it becomes.
  • 将关注点拆分为独立组件
  • 最小化组件之间的耦合
  • 使用事件实现松协调
  • 维护单一数据源
  • 隔离测试组件
系统的正交性越高,就越灵活、越易于维护。