341 lines
8.5 KiB
Markdown
341 lines
8.5 KiB
Markdown
---
|
|
name: error-handling-completeness
|
|
description: Evaluates if error handling is sufficient for new code - checks try-catch coverage, logging, user messages, retry logic. Focuses on external calls and user-facing code.
|
|
---
|
|
|
|
# Error Handling Completeness Skill
|
|
|
|
**Purpose**: Prevent production crashes with systematic error handling.
|
|
|
|
**Trigger Words**: API call, external, integration, network, database, file, user input, async, promise, await
|
|
|
|
---
|
|
|
|
## Quick Decision: Needs Error Handling Check?
|
|
|
|
```python
|
|
def needs_error_check(code_context: dict) -> bool:
|
|
"""Decide if error handling review is needed."""
|
|
|
|
# High-risk operations (always check)
|
|
high_risk = [
|
|
"fetch", "axios", "requests", "http", # HTTP calls
|
|
"db.", "query", "execute", # Database
|
|
"open(", "read", "write", # File I/O
|
|
"json.loads", "json.parse", # JSON parsing
|
|
"int(", "float(", # Type conversions
|
|
"subprocess", "exec", # External processes
|
|
"await", "async", # Async operations
|
|
]
|
|
|
|
code = code_context.get("code", "").lower()
|
|
return any(risk in code for risk in high_risk)
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling Checklist (Fast)
|
|
|
|
### 1. **External API Calls** (Most Critical)
|
|
```python
|
|
# ❌ BAD - No error handling
|
|
def get_user_data(user_id):
|
|
response = requests.get(f"https://api.example.com/users/{user_id}")
|
|
return response.json() # What if network fails? 404? Timeout?
|
|
|
|
# ✅ GOOD - Complete error handling
|
|
def get_user_data(user_id):
|
|
try:
|
|
response = requests.get(
|
|
f"https://api.example.com/users/{user_id}",
|
|
timeout=5 # Timeout!
|
|
)
|
|
response.raise_for_status() # Check HTTP errors
|
|
return response.json()
|
|
|
|
except requests.Timeout:
|
|
logger.error(f"Timeout fetching user {user_id}")
|
|
raise ServiceUnavailableError("User service timeout")
|
|
|
|
except requests.HTTPError as e:
|
|
if e.response.status_code == 404:
|
|
raise UserNotFoundError(f"User {user_id} not found")
|
|
logger.error(f"HTTP error fetching user: {e}")
|
|
raise
|
|
|
|
except requests.RequestException as e:
|
|
logger.error(f"Network error: {e}")
|
|
raise ServiceUnavailableError("Cannot reach user service")
|
|
```
|
|
|
|
**Quick Checks**:
|
|
- ✅ Timeout set?
|
|
- ✅ HTTP errors handled?
|
|
- ✅ Network errors caught?
|
|
- ✅ Logged?
|
|
- ✅ User-friendly error returned?
|
|
|
|
---
|
|
|
|
### 2. **Database Operations**
|
|
```python
|
|
# ❌ BAD - Swallows errors
|
|
def delete_user(user_id):
|
|
try:
|
|
db.execute("DELETE FROM users WHERE id = ?", [user_id])
|
|
except Exception:
|
|
pass # Silent failure!
|
|
|
|
# ✅ GOOD - Specific handling
|
|
def delete_user(user_id):
|
|
try:
|
|
result = db.execute("DELETE FROM users WHERE id = ?", [user_id])
|
|
if result.rowcount == 0:
|
|
raise UserNotFoundError(f"User {user_id} not found")
|
|
|
|
except db.IntegrityError as e:
|
|
logger.error(f"Cannot delete user {user_id}: {e}")
|
|
raise DependencyError("User has related records")
|
|
|
|
except db.OperationalError as e:
|
|
logger.error(f"Database error: {e}")
|
|
raise DatabaseUnavailableError()
|
|
```
|
|
|
|
**Quick Checks**:
|
|
- ✅ Specific exceptions (not bare `except`)?
|
|
- ✅ Logged?
|
|
- ✅ User-friendly error?
|
|
|
|
---
|
|
|
|
### 3. **File Operations**
|
|
```python
|
|
# ❌ BAD - File might not exist
|
|
def read_config():
|
|
with open("config.json") as f:
|
|
return json.load(f)
|
|
|
|
# ✅ GOOD - Handle missing file
|
|
def read_config():
|
|
try:
|
|
with open("config.json") as f:
|
|
return json.load(f)
|
|
except FileNotFoundError:
|
|
logger.warning("config.json not found, using defaults")
|
|
return DEFAULT_CONFIG
|
|
except json.JSONDecodeError as e:
|
|
logger.error(f"Invalid JSON in config.json: {e}")
|
|
raise ConfigurationError("Malformed config.json")
|
|
except PermissionError:
|
|
logger.error("Permission denied reading config.json")
|
|
raise
|
|
```
|
|
|
|
**Quick Checks**:
|
|
- ✅ FileNotFoundError handled?
|
|
- ✅ JSON parse errors caught?
|
|
- ✅ Permission errors handled?
|
|
|
|
---
|
|
|
|
### 4. **Type Conversions**
|
|
```python
|
|
# ❌ BAD - Crash on invalid input
|
|
def process_age(age_str):
|
|
age = int(age_str) # What if "abc"?
|
|
return age * 2
|
|
|
|
# ✅ GOOD - Validated
|
|
def process_age(age_str):
|
|
try:
|
|
age = int(age_str)
|
|
if age < 0 or age > 150:
|
|
raise ValueError("Age out of range")
|
|
return age * 2
|
|
except ValueError:
|
|
raise ValidationError(f"Invalid age: {age_str}")
|
|
```
|
|
|
|
**Quick Checks**:
|
|
- ✅ ValueError caught?
|
|
- ✅ Range validation?
|
|
- ✅ Clear error message?
|
|
|
|
---
|
|
|
|
### 5. **Async/Await** (JavaScript/Python)
|
|
```javascript
|
|
// ❌ BAD - Unhandled promise rejection
|
|
async function fetchUser(id) {
|
|
const user = await fetch(`/api/users/${id}`);
|
|
return user.json(); // What if network fails?
|
|
}
|
|
|
|
// ✅ GOOD - Handled
|
|
async function fetchUser(id) {
|
|
try {
|
|
const response = await fetch(`/api/users/${id}`);
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status}`);
|
|
}
|
|
return await response.json();
|
|
} catch (error) {
|
|
console.error(`Failed to fetch user ${id}:`, error);
|
|
throw new ServiceError("Cannot fetch user");
|
|
}
|
|
}
|
|
```
|
|
|
|
**Quick Checks**:
|
|
- ✅ Try-catch around await?
|
|
- ✅ HTTP status checked?
|
|
- ✅ Logged?
|
|
|
|
---
|
|
|
|
## Error Handling Patterns
|
|
|
|
### Pattern 1: Retry with Exponential Backoff
|
|
```python
|
|
def call_api_with_retry(url, max_retries=3):
|
|
for attempt in range(max_retries):
|
|
try:
|
|
response = requests.get(url, timeout=5)
|
|
response.raise_for_status()
|
|
return response.json()
|
|
|
|
except requests.Timeout:
|
|
if attempt < max_retries - 1:
|
|
wait = 2 ** attempt # 1s, 2s, 4s
|
|
logger.warning(f"Timeout, retrying in {wait}s...")
|
|
time.sleep(wait)
|
|
else:
|
|
raise
|
|
```
|
|
|
|
**When to use**: Transient failures (network, rate limits)
|
|
|
|
---
|
|
|
|
### Pattern 2: Fallback Values
|
|
```python
|
|
def get_user_avatar(user_id):
|
|
try:
|
|
return fetch_from_cdn(user_id)
|
|
except CDNError:
|
|
logger.warning(f"CDN failed for user {user_id}, using default")
|
|
return DEFAULT_AVATAR_URL
|
|
```
|
|
|
|
**When to use**: Non-critical operations, graceful degradation
|
|
|
|
---
|
|
|
|
### Pattern 3: Circuit Breaker
|
|
```python
|
|
class CircuitBreaker:
|
|
def __init__(self, max_failures=5):
|
|
self.failures = 0
|
|
self.max_failures = max_failures
|
|
self.is_open = False
|
|
|
|
def call(self, func):
|
|
if self.is_open:
|
|
raise ServiceUnavailableError("Circuit breaker open")
|
|
|
|
try:
|
|
result = func()
|
|
self.failures = 0 # Reset on success
|
|
return result
|
|
except Exception as e:
|
|
self.failures += 1
|
|
if self.failures >= self.max_failures:
|
|
self.is_open = True
|
|
logger.error("Circuit breaker opened")
|
|
raise
|
|
```
|
|
|
|
**When to use**: Preventing cascading failures
|
|
|
|
---
|
|
|
|
## Output Format
|
|
|
|
```markdown
|
|
## Error Handling Report
|
|
|
|
**Status**: [✅ COMPLETE | ⚠️ GAPS FOUND]
|
|
|
|
---
|
|
|
|
### Missing Error Handling: 3
|
|
|
|
1. **[HIGH] No timeout on API call (api_client.py:45)**
|
|
- **Issue**: `requests.get()` has no timeout
|
|
- **Risk**: Indefinite hang if service slow
|
|
- **Fix**:
|
|
```python
|
|
response = requests.get(url, timeout=5)
|
|
```
|
|
|
|
2. **[HIGH] Unhandled JSON parse error (config.py:12)**
|
|
- **Issue**: `json.load()` not wrapped in try-catch
|
|
- **Risk**: Crash on malformed JSON
|
|
- **Fix**:
|
|
```python
|
|
try:
|
|
config = json.load(f)
|
|
except json.JSONDecodeError as e:
|
|
logger.error(f"Invalid JSON: {e}")
|
|
return DEFAULT_CONFIG
|
|
```
|
|
|
|
3. **[MEDIUM] Silent exception swallowing (db.py:89)**
|
|
- **Issue**: `except Exception: pass`
|
|
- **Risk**: Failures go unnoticed
|
|
- **Fix**: Log error or use specific exception
|
|
|
|
---
|
|
|
|
**Good Practices Found**: 2
|
|
- ✅ Database errors logged properly (db.py:34)
|
|
- ✅ Retry logic on payment API (payments.py:67)
|
|
|
|
---
|
|
|
|
**Next Steps**:
|
|
1. Add timeout to API calls (5 min)
|
|
2. Wrap JSON parsing in try-catch (2 min)
|
|
3. Remove silent exception handlers (3 min)
|
|
```
|
|
|
|
---
|
|
|
|
## What This Skill Does NOT Do
|
|
|
|
❌ Catch every possible exception (too noisy)
|
|
❌ Force try-catch everywhere (only where needed)
|
|
❌ Replace integration tests
|
|
❌ Handle business logic errors (validation, etc.)
|
|
|
|
✅ **DOES**: Check critical error-prone operations (network, I/O, parsing)
|
|
|
|
---
|
|
|
|
## Configuration
|
|
|
|
```bash
|
|
# Strict mode: check all functions
|
|
export LAZYDEV_ERROR_HANDLING_STRICT=1
|
|
|
|
# Disable error handling checks
|
|
export LAZYDEV_DISABLE_ERROR_CHECKS=1
|
|
```
|
|
|
|
---
|
|
|
|
**Version**: 1.0.0
|
|
**Focus**: External calls, I/O, parsing, async
|
|
**Speed**: <2 seconds per file
|