--- title: Error Handling Patterns category: constraints severity: high --- # Error Handling Patterns ## Core Rules 1. **Catch `Exception`, return safe fallbacks** (False/[]/None) 2. **Log exceptions with traceback** using `_logger.exception()` 3. **Never swallow exceptions silently** - always log or return sensible default 4. **Avoid nested try/except blocks** - flatten exception handling ## Pattern: Try/Except Safe Fallback This is the dominant pattern in the codebase (219+ instances). ```python # Standard pattern from database.py, api_client.py, etc. try: result = risky_operation() return process(result) except Exception as exc: _logger.warning("Operation failed: %s", exc) return safe_fallback # False, [], None, {} ``` ### Examples from Codebase **database.py** - DuckDB operations: ```python def get_svd_vectors(self, window: str): try: conn = duckdb.connect(self.db_path, read_only=True) try: result = conn.execute(query, (window,)).fetchall() return self._parse_vectors(result) finally: conn.close() except Exception as exc: _logger.warning("Failed to get SVD vectors: %s", exc) return [] ``` **ai_provider.py** - HTTP retries: ```python try: resp = requests.post(url, json=json, headers=headers, timeout=10) resp.raise_for_status() return resp.json() except requests.ConnectionError as exc: if attempt == retries: raise ProviderError(f"Connection error: {exc}") from exc # ... retry logic ``` ## Pattern: Optional Dependency Fallback Gracefully degrade when optional packages are unavailable. ```python # UMAP fallback in explorer_helpers.py try: import umap HAS_UMAP = True except ImportError: HAS_UMAP = False _logger.debug("UMAP not available, using SVD vectors directly") def project_to_2d(vectors): if HAS_UMAP: return umap.UMAP().fit_transform(vectors) return vectors[:, :2] # Fallback: first 2 SVD dimensions ``` ## Anti-Patterns ### 1. Bare except with pass (CRITICAL) **File**: `database.py`, line 47 ```python # BAD - catches KeyboardInterrupt, SystemExit, MemoryError try: conn.execute("CREATE SEQUENCE IF NOT EXISTS motions_id_seq START 1") except: # bare except pass ``` **Fix**: Catch specific exception or log and continue: ```python try: conn.execute("CREATE SEQUENCE IF NOT EXISTS motions_id_seq START 1") except Exception as exc: _logger.debug("Sequence creation skipped (may already exist): %s", exc) ``` ### 2. Nested Exception Handling **File**: `explorer.py`, lines 244-261 ```python # BAD - opaque error paths try: result = compute_svd(motions) except Exception: try: result = fallback_compute(motions) except Exception: pass # Both exceptions silently dropped ``` **Fix**: Flatten and handle each case explicitly: ```python # GOOD - explicit handling try: result = compute_svd(motions) except Exception as exc: _logger.warning("SVD failed, trying fallback: %s", exc) try: result = fallback_compute(motions) except Exception as fallback_exc: _logger.error("Both SVD approaches failed: %s, %s", exc, fallback_exc) raise ``` ## Rule Summary | Pattern | When to Use | Return Value | |---------|-------------|--------------| | Safe fallback | Best-effort operations | `[]`, `{}`, `False`, `None` | | Re-raise | Critical operations that must succeed | raise | | Log and continue | Optional steps in pipeline | (continue) | | Graceful degradation | Optional dependencies | Default behavior | ## When to Log vs Return | Scenario | Action | |----------|--------| | User action fails | Log warning, return safe default | | Internal error (corrupt data) | Log error, return safe default | | Transient failure (network) | Log warning, retry if appropriate | | Configuration error | Log error, raise with clear message |