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.
143 lines
3.8 KiB
143 lines
3.8 KiB
---
|
|
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 |
|
|
|