You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3.8 KiB
3.8 KiB
| title | category | severity |
|---|---|---|
| Error Handling Patterns | constraints | high |
Error Handling Patterns
Core Rules
- Catch
Exception, return safe fallbacks (False/[]/None) - Log exceptions with traceback using
_logger.exception() - Never swallow exceptions silently - always log or return sensible default
- Avoid nested try/except blocks - flatten exception handling
Pattern: Try/Except Safe Fallback
This is the dominant pattern in the codebase (219+ instances).
# 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:
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:
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.
# 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
# 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:
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
# 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:
# 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 |