Error Handling Best Practices
Error Handling: Fehler richtig behandeln
Gutes Error Handling unterscheidet robuste von fragilen Anwendungen. Lernen Sie, wie Sie Fehler vorhersehbar und hilfreich behandeln.
Grundprinzipien
✅ Best Practices:
- Fail Fast: Fehler früh erkennen und werfen
- Spezifische Exceptions statt generischer
- Fehler dort behandeln wo sie behandelt werden können
- Immer loggen, aber User-freundliche Messages zeigen
- Niemals Fehler verschlucken (leerer catch-Block)
JavaScript/Node.js
// Eigene Error-Klassen
class AppError extends Error {
constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') {
super(message);
this.statusCode = statusCode;
this.code = code;
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
class NotFoundError extends AppError {
constructor(resource = 'Resource') {
super(`${resource} nicht gefunden`, 404, 'NOT_FOUND');
}
}
class ValidationError extends AppError {
constructor(errors) {
super('Validierung fehlgeschlagen', 400, 'VALIDATION_ERROR');
this.errors = errors;
}
}
// Verwendung
async function getUser(id) {
const user = await db.users.findById(id);
if (!user) {
throw new NotFoundError('User');
}
return user;
}
// Express Error Handler
app.use((err, req, res, next) => {
console.error(err);
// Operational Errors (erwartet)
if (err.isOperational) {
return res.status(err.statusCode).json({
error: {
code: err.code,
message: err.message,
errors: err.errors
}
});
}
// Programming Errors (Bugs)
res.status(500).json({
error: {
code: 'INTERNAL_ERROR',
message: 'Ein unerwarteter Fehler ist aufgetreten'
}
});
});
PHP Exceptions
<?php
// Eigene Exception-Hierarchie
class AppException extends Exception {
protected string $errorCode;
public function __construct(string $message, string $errorCode, int $httpCode = 500) {
parent::__construct($message, $httpCode);
$this->errorCode = $errorCode;
}
public function getErrorCode(): string {
return $this->errorCode;
}
public function toArray(): array {
return [
'error' => [
'code' => $this->errorCode,
'message' => $this->getMessage()
]
];
}
}
class NotFoundException extends AppException {
public function __construct(string $resource = 'Resource') {
parent::__construct("{$resource} nicht gefunden", 'NOT_FOUND', 404);
}
}
class ValidationException extends AppException {
private array $errors;
public function __construct(array $errors) {
parent::__construct('Validierung fehlgeschlagen', 'VALIDATION_ERROR', 400);
$this->errors = $errors;
}
public function toArray(): array {
return array_merge(parent::toArray(), ['errors' => $this->errors]);
}
}
// Verwendung
function getUser(int $id): User {
$user = User::find($id);
if (!$user) {
throw new NotFoundException('User');
}
return $user;
}
// Global Handler
set_exception_handler(function (Throwable $e) {
error_log($e->getMessage() . "\n" . $e->getTraceAsString());
if ($e instanceof AppException) {
http_response_code($e->getCode());
echo json_encode($e->toArray());
} else {
http_response_code(500);
echo json_encode([
'error' => [
'code' => 'INTERNAL_ERROR',
'message' => 'Ein Fehler ist aufgetreten'
]
]);
}
});
Python
# Eigene Exceptions
class AppError(Exception):
def __init__(self, message, code='INTERNAL_ERROR', status_code=500):
self.message = message
self.code = code
self.status_code = status_code
super().__init__(self.message)
def to_dict(self):
return {
'error': {
'code': self.code,
'message': self.message
}
}
class NotFoundError(AppError):
def __init__(self, resource='Resource'):
super().__init__(f'{resource} nicht gefunden', 'NOT_FOUND', 404)
class ValidationError(AppError):
def __init__(self, errors):
super().__init__('Validierung fehlgeschlagen', 'VALIDATION_ERROR', 400)
self.errors = errors
def to_dict(self):
result = super().to_dict()
result['errors'] = self.errors
return result
# Flask Error Handler
@app.errorhandler(AppError)
def handle_app_error(error):
return jsonify(error.to_dict()), error.status_code
@app.errorhandler(Exception)
def handle_generic_error(error):
app.logger.error(f'Unhandled error: {error}', exc_info=True)
return jsonify({
'error': {
'code': 'INTERNAL_ERROR',
'message': 'Ein Fehler ist aufgetreten'
}
}), 500
Try-Catch Strategien
// ❌ Fehler verschlucken
try {
riskyOperation();
} catch (e) {
// Nichts tun - SCHLECHT!
}
// ❌ Zu breites Catching
try {
// Viel Code...
} catch (e) {
console.log('Irgendein Fehler');
}
// ✅ Spezifisches Catching
try {
const data = await fetchData();
const result = processData(data);
return result;
} catch (error) {
if (error instanceof NetworkError) {
// Retry-Logik
return fetchWithRetry();
}
if (error instanceof ValidationError) {
// User informieren
showValidationErrors(error.errors);
return;
}
// Unbekannte Fehler weiterwerfen
throw error;
}
// ✅ Finally für Cleanup
let connection;
try {
connection = await db.connect();
return await connection.query(sql);
} catch (error) {
logger.error('DB Error:', error);
throw error;
} finally {
if (connection) {
await connection.close();
}
}
Error Boundaries (React)
class ErrorBoundary extends React.Component {
state = { hasError: false, error: null };
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
componentDidCatch(error, errorInfo) {
// Zu Error-Service senden
logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
return (
<div className="error-fallback">
<h2>Etwas ist schiefgelaufen</h2>
<button onClick={() => this.setState({ hasError: false })}>
Erneut versuchen
</button>
</div>
);
}
return this.props.children;
}
}
// Verwendung
<ErrorBoundary>
<MyComponent />
</ErrorBoundary>
Logging vs. User Messages
class UserService {
async createUser(data) {
try {
// Validierung
const errors = this.validate(data);
if (errors.length) {
throw new ValidationError(errors);
}
// Erstellen
return await db.users.create(data);
} catch (error) {
// Technische Details loggen
logger.error('User creation failed', {
error: error.message,
stack: error.stack,
data: { email: data.email } // Keine Passwörter!
});
// User-freundliche Message
if (error instanceof ValidationError) {
throw error; // Schon user-freundlich
}
if (error.code === 'ER_DUP_ENTRY') {
throw new AppError(
'Diese E-Mail ist bereits registriert',
'EMAIL_EXISTS',
409
);
}
throw new AppError(
'Registrierung fehlgeschlagen. Bitte später erneut versuchen.',
'REGISTRATION_FAILED',
500
);
}
}
}
💡 Tipp:
Überwachen Sie Ihre Fehler mit strukturiertem Logging und dem Enjyn Status Monitor.