Skip to main content

encapsulating-complexity

Hide implementation details behind interfaces - work at domain level (what), not implementation level (how)

来源信息

仓库
obra/clank
最近来源活动
2025年10月7日 17:04
检测到的 SKILL.md 语言
英语
星标
41
分支
27

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
Encapsulating Complexity
description
Hide implementation details behind interfaces - work at domain level (what), not implementation level (how)
when_to_use
When designing any class or interface. When implementation details leak into public API. When storage format (JSON, SQL, files) is exposed. When working with raw data structures (dicts, rows) instead of domain objects. When client code must know HOW things work internally. When changing implementation would break client code. When database queries mixed with business logic. When switching storage type requires interface changes. When tests must know internal structure.
version
1.0.0
languages
all
# Encapsulating Complexity ## Overview Hide HOW things work. Expose only WHAT they do. Work at domain level (Users, Orders, Config), not implementation level (dicts, SQL rows, JSON files). **Core principle:** The point of encapsulation is to create possibilities (many ways to implement) and restrict possibilities (one way to use). Implementation details hidden = free to change implementation without breaking clients. **Violating the letter of this rule is violating the spirit of information hiding.** ## When to Use **Apply to every class and interface:** - Designing new classes/modules - Creating public APIs - Reviewing code for abstraction leaks - Refactoring to improve maintainability **Warning signs you're violating encapsulation:** - Client code knows storage format (JSON, SQL, files) - Interface exposes database/file operations - Returns raw dicts/rows instead of domain objects - Client code constructs SQL queries or file paths - Changing from JSON to YAML breaks client code - Switching databases requires changing all callers - Tests must know internal data structures - Method names reveal implementation (saveToJSON, queryDatabase) - Public fields exposing internal state ## The Encapsulation Test **Ask these questions about every public method/field:** 1. **Does the interface expose HOW or WHAT?** - "Get user" = WHAT (good) - "SELECT * FROM users" = HOW (bad) 2. **Can I change implementation without breaking clients?** - JSON → YAML: should work - PostgreSQL → MongoDB: should work - If clients break: encapsulation violated 3. **Do clients work at domain level or implementation level?** - Domain: `user.email`, `config.get_timeout()` - Implementation: `row[2]`, `json_data['timeout_ms']` 4. **Do return values expose internals?** - Domain: `User` object - Implementation: `dict` with database column names **If answers reveal implementation details → encapsulation violated.** ## Core Pattern: Hide the How ### Before (Implementation Exposed) ```python class ConfigManager: def __init__(self, json_path): self.json_path = json_path # ❌ Exposes JSON self._data = {} def get_value(self, key): return self._data.get(key) # ⚠️ Returns raw value def save_to_json(self): # ❌ "JSON" in method name with open(self.json_path, 'w') as f: json.dump(self._data, f) ``` **Client code:** ```python config = ConfigManager("/path/to/config.json") # Must know it's JSON timeout = config.get_value("timeout_ms") # Must know exact key format config.save_to_json() # Tied to JSON format ``` **If you switch JSON → YAML:** Client code breaks. Method names wrong. Constructor signature wrong. ### After (Implementation Hidden) ```python class Config: def __init__(self, config_file): # ✅ No format specified self._storage = self._load(config_file) # ✅ Implementation hidden def get_timeout(self): # ✅ Domain method, not raw key access return self._storage.get("timeout_ms", 5000) def set_timeout(self, seconds): # ✅ Domain operation self._storage["timeout_ms"] = seconds * 1000 # ms internally def save(self): # ✅ No "JSON" in name self._persist(self._storage) def _load(self, config_file): # ✅ Private - can change format # Could load JSON, YAML, TOML, etc. pass def _persist(self, data): # ✅ Private - implementation detail # Format hidden from clients pass ``` **Client code:** ```python config = Config("app.config") # Format agnostic timeout = config.get_timeout() # Domain method (seconds) config.set_timeout(10) config.save() ``` **Switch JSON → YAML:** Client code unchanged. Just change `_load()` and `_persist()`. ## Domain Level vs Implementation Level **Work at the problem domain, not the solution domain:** ### Example 1: User Management ❌ **Implementation Level:** ```python class UserManager: def get_user_row(self, user_id): # Returns database row as dict cur.execute("SELECT * FROM users WHERE id = %s", (user_id,)) return dict(cur.fetchone()) # ❌ Raw row dict def update_user_column(self, user_id, column, value): # ❌ Exposes database columns cur.execute(f"UPDATE users SET {column} = %s WHERE id = %s", ...) ``` **Client must know:** - Database schema (column names) - SQL concepts (rows, columns) - Data types in database ✅ **Domain Level:** ```python class UserRepository: def get_user(self, user_id: str) -> User: # Returns domain object row = self._query_user(user_id) return User.from_storage(row) # ✅ Domain object def update_email(self, user_id: str, new_email: str) -> None: # ✅ Domain operation, no column names user = self.get_user(user_id) user.email = new_email self._persist_user(user) class User: def __init__(self, id, email, name): self.id = id self.email = email self.name = name ``` **Client knows:** - Users (domain concept) - Email (domain field) - Nothing about database ### Example 2: Configuration ❌ **Implementation Level:** ```python class ConfigManager: def get(self, key): return self._config.get(key) # ❌ Must know exact key names # Client code timeout = config.get("server.timeout_milliseconds") # ❌ Internal structure port = config.get("server.port") ``` ✅ **Domain Level:** ```python class Config: def server_timeout(self): # ✅ Domain method return self._get("server.timeout_ms", 5000) / 1000 # Hide ms→sec def server_port(self): # ✅ Domain method return self._get("server.port", 8080) # Client code timeout = config.server_timeout() # ✅ Clean domain API port = config.server_port() ``` ## Separation of Concerns **Don't mix persistence with domain logic:** ❌ **Mixed (From Baseline Test):** ```python class ReportGenerator: def generate(self, start_date, end_date): # Database access conn = psycopg2.connect(...) cursor.execute("SELECT ...") rows = cursor.fetchall() # Business logic total = sum(row[3] for row in rows) # HTML formatting html = f"<html>...</html>" # File I/O with open(f"report_{start_date}.html", "w") as f: f.write(html) ``` **4 concerns mixed: database, calculation, formatting, file I/O** ✅ **Separated:** ```python class SalesRepository: """Concern: Data access""" def get_sales(self, start_date, end_date) -> List[Sale]: # Database logic hidden here pass class SalesCalculator: """Concern: Business logic""" def calculate_metrics(self, sales: List[Sale]) -> SalesMetrics: # Pure calculation, no database/file knowledge pass class HTMLFormatter: """Concern: Presentation""" def format_report(self, metrics: SalesMetrics) -> str: # HTML generation, no database/calculation knowledge pass class ReportGenerator: """Concern: Orchestration""" def __init__(self, repo, calculator, formatter): self._repo = repo self._calculator = calculator self._formatter = formatter def generate(self, start_date, end_date, output_path): sales = self._repo.get_sales(start_date, end_date) # What metrics = self._calculator.calculate_metrics(sales) # What html = self._formatter.format_report(metrics) # What with open(output_path, "w") as f: f.write(html) ``` **Each class can change independently. Database switch doesn't touch formatting. HTML→PDF doesn't touch database.** ## What to Hide ### Hide Storage Format ❌ **Exposed:** ```python class DataStore: def load_from_json(self): # ❌ "JSON" in name pass def save_to_json(self): # ❌ Tied to JSON pass ``` ✅ **Hidden:** ```python class DataStore: def load(self): # ✅ Format agnostic self._read_storage() # Private - can be JSON/YAML/SQL def save(self): # ✅ Format agnostic self._write_storage() # Private - implementation hidden ``` ### Hide Database Details ❌ **Exposed:** ```python def get_users(self): cur.execute("SELECT * FROM users") return [dict(row) for row in cur.fetchall()] # ❌ Raw database rows ``` ✅ **Hidden:** ```python def get_users(self) -> List[User]: rows = self._query_all_users() # SQL hidden in private method return [User.from_row(row) for row in rows] # ✅ Domain objects ``` ### Hide Data Structures ❌ **Exposed:** ```python class UserCache: def __init__(self): self.users_dict = {} # ❌ Public dict # Client code manipulates dict directly cache.users_dict[user_id] = user_data ``` ✅ **Hidden:** ```python class UserCache: def __init__(self): self._storage = {} # ✅ Private implementation def add_user(self, user): # ✅ Domain operation self._storage[user.id] = user def get_user(self, user_id): # ✅ Domain operation return self._storage.get(user_id) ``` **Internal dict can become Redis, Memcached, or database without breaking clients.** ### Hide Algorithms and Complexity ❌ **Exposed:** ```python def sort_users_by_name(users): # ❌ Client must understand sorting implementation # Uses quicksort internally return quicksort(users, key=lambda u: u.name) ``` ✅ **Hidden:** ```python def get_users_sorted_by_name(users): # ✅ How it sorts is hidden - could be quicksort, mergesort, timsort return sorted(users, key=lambda u: u.name) ``` ## Interface Design Principles ### 1. Name by What, Not How ❌ **How (implementation exposed):** - `save_to_json()`, `load_from_yaml()`, `write_to_file()` - `execute_sql_query()`, `get_database_connection()` - `parse_json_response()`, `build_xml_request()` ✅ **What (implementation hidden):** - `save()`, `load()`, `persist()` - `get_users()`, `find_by_email()` - `send_request()`, `get_response()` ### 2. Accept and Return Domain Objects ❌ **Raw data structures:** ```python def create_user(self, user_dict: dict) -> dict: # ❌ Accepts and returns dicts # Client must know dict structure ``` ✅ **Domain objects:** ```python def create_user(self, email: str, name: str) -> User: # ✅ Clear parameters, returns domain object # Internal structure hidden ``` ### 3. Work at Single Abstraction Level ❌ **Mixed levels:** ```python def process_order(self, order): validate_order(order) # High level # ❌ Low-level SQL mixed in conn.execute("INSERT INTO orders VALUES (%s, %s)", ...) send_confirmation(order) # High level ``` ✅ **Consistent level:** ```python def process_order(self, order): # All high level - persistence hidden validate_order(order) saved_order = self._repository.save(order) # Hides SQL send_confirmation(saved_order) ``` ## Common Violations from Baseline Testing ### Violation 1: Exposing Storage Format **Baseline:** ConfigManager exposes JSON in interface, UserManager exposes SQL. ❌ **What agents naturally do:** ```python class ConfigManager: def __init__(self, json_path): # Exposes JSON self.json_path = json_path # Public attribute def save(self): with open(self.json_path, 'w') as f: json.dump(self._config, f) # JSON logic in public view ``` ✅ **Hide format:** ```python class Config: def __init__(self, config_file): # ✅ Format neutral self._loader = self._create_loader(config_file) # Private self._data = self._loader.load() # Hidden def save(self): self._loader.persist(self._data) # ✅ Format hidden def _create_loader(self, config_file): # Private - can support JSON, YAML, TOML, etc.
在 GitHub 查看
这个 SKILL.md 很大,SkillsMP 这里只预览前一段内容。 在 GitHub 查看