orthogonality-principle
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseOrthogonality 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
表明组件非正交的危险信号
- Change amplification: Changing one thing requires changing many others
- Shotgun surgery: One feature scattered across many files
- Tight coupling: Components know too much about each other
- Duplicate logic: Same concept implemented multiple ways
- Cascading changes: Change in A breaks B, C, D unexpectedly
- 修改扩散:改动一处需要同时修改多处
- 霰弹式修改:一个功能分散在多个文件中
- 紧耦合:组件之间过度了解彼此的内部实现
- 逻辑重复:同一概念被多次实现
- 连锁变更:修改组件A意外导致组件B、C、D出现问题
Achieving Orthogonality
实现正交性的方法
1. Separation of Concerns
1. 关注点分离
Keep unrelated responsibilities separate
将无关职责分开
Elixir Example
Elixir 示例
elixir
undefinedelixir
undefinedNON-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})
endend
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})
endend
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
undefinedundefinedTypeScript 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 orthogonaltypescript
// 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 orthogonal2. Interface Segregation
2. 接口隔离
Create focused, minimal interfaces
创建聚焦、精简的接口
Elixir Example (Interface Segregation)
Elixir 示例(接口隔离)
elixir
undefinedelixir
undefinedNON-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
undefinedundefined3. Dependency Injection
3. 依赖注入
Don't hardcode dependencies - inject them
不要硬编码依赖项——采用注入方式
Elixir Example (Dependency Injection)
Elixir 示例(依赖注入)
elixir
undefinedelixir
undefinedNON-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
undefinedundefined4. Event-Driven Architecture
4. 事件驱动架构
Decouple through events instead of direct calls
通过事件而非直接调用实现解耦
Elixir Example (Event-Driven Architecture)
Elixir 示例(事件驱动架构)
elixir
undefinedelixir
undefinedNON-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
undefinedundefinedTypeScript 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 TaskManagertypescript
// 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 TaskManager5. Data Orthogonality
5. 数据正交性
Don't duplicate data - maintain single source of truth
不要重复存储数据——维护单一数据源
Elixir Example (Data Orthogonality)
Elixir 示例(数据正交性)
elixir
undefinedelixir
undefinedNON-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
undefinedundefinedTypeScript 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 issuestypescript
// 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 issuesPractical 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
undefinedORTHOGONAL - 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
undefinedundefinedExamples
示例
Orthogonal patterns in the codebase
代码库中的正交模式
-
CQRS: Commands/Queries are orthogonal
- Change query without affecting command
- Add command without changing queries
-
Atomic Design: Atoms/Molecules/Organisms are orthogonal
- Change atom styling without affecting organisms
- Add new molecule without touching existing ones
-
GraphQL Schema: Types are orthogonal
- Add fields to one type without affecting others
- Each type has focused responsibility
-
Microservices: Bounded contexts are orthogonal
- Change billing without affecting scheduling
- Add analytics without touching core services
-
CQRS:命令/查询是正交的
- 修改查询逻辑无需影响命令逻辑
- 添加新命令无需改动查询逻辑
-
原子设计:原子/分子/生物组件是正交的
- 修改原子样式无需影响生物组件
- 添加新分子组件无需改动现有组件
-
GraphQL Schema:类型是正交的
- 为一个类型添加字段无需影响其他类型
- 每个类型都有聚焦的职责
-
微服务:限界上下文是正交的
- 修改计费服务无需影响调度服务
- 添加分析服务无需改动核心服务
Non-orthogonal anti-patterns to avoid
需要避免的非正交反模式
- Shared mutable state (global variables)
- Deep inheritance hierarchies
- Circular dependencies
- God objects (modules that do everything)
- Feature envy (functions in module A that mostly use data from module B)
- 共享可变状态(全局变量)
- 深层继承层级
- 循环依赖
- 上帝对象(包揽所有功能的模块)
- 特性羡慕(模块A中的函数主要使用模块B的数据)
Integration with Existing Skills
与现有技能的集成
Works with
兼容的技能
- : Single Responsibility → Orthogonality
solid-principles - : Encapsulation → Orthogonality
structural-design-principles - : KISS → Fewer dependencies → More orthogonal
simplicity-principles - : Commands/Queries naturally orthogonal
cqrs-pattern - : Component hierarchy naturally orthogonal
atomic-design-pattern
- :单一职责 → 正交性
solid-principles - :封装 → 正交性
structural-design-principles - :KISS → 更少依赖 → 更高正交性
simplicity-principles - :命令/查询天然正交
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.
- 将关注点拆分为独立组件
- 最小化组件之间的耦合
- 使用事件实现松协调
- 维护单一数据源
- 隔离测试组件
系统的正交性越高,就越灵活、越易于维护。