Memuat...
👋 Selamat Pagi!

Cara Membangun Error Handling yang Robust di Aplikasi Production

Error handling yang buruk bikin aplikasi crash mendadak. Pelajari cara membangun sistem error handling profesional yang menjaga stabilitas aplikasi production A...

Cara Membangun Error Handling yang Robust di Aplikasi Production

Aplikasi yang crash tanpa pesan error jelas adalah mimpi buruk setiap developer. User frustrasi, data hilang, dan reputasi bisnis hancur dalam sekejap.

Error handling yang robust bukan cuma soal try-catch sederhana. Ini tentang membangun sistem yang bisa menangkap, mencatat, dan menangani kesalahan dengan cara yang tidak merusak user experience.

Di artikel ini, kita akan bedah cara membangun error handling profesional yang siap untuk aplikasi production.

Mengapa Error Handling Itu Krusial?

Banyak developer pemula mengabaikan error handling sampai aplikasi sudah live. Padahal ini adalah fondasi stabilitas aplikasi.

Tanpa error handling yang baik, satu error kecil bisa membuat seluruh sistem down. User melihat white screen of death, transaksi gagal tanpa notifikasi, dan Anda kehilangan data berharga.

Error handling yang robust melindungi aplikasi dari kegagalan cascade, memberikan feedback yang jelas ke user, dan mempermudah debugging saat terjadi masalah.

Prinsip Dasar Error Handling yang Benar

Ada beberapa prinsip fundamental yang harus dipahami sebelum implementasi.

Fail Fast vs Fail Safe

Fail fast berarti aplikasi langsung stop saat menemukan error serius. Ini bagus untuk development karena memaksa kita fix masalah segera.

Fail safe berarti aplikasi tetap berjalan meski ada error, dengan fallback mechanism. Ini yang kita butuhkan di production.

Kombinasi keduanya: fail fast di development, fail safe di production dengan logging yang komprehensif.

Never Trust External Input

Semua data dari luar aplikasi—user input, API response, database query—harus dianggap tidak reliable.

Validasi dan sanitasi wajib dilakukan sebelum processing. Jangan berasumsi data akan selalu sesuai ekspektasi.

// ❌ Buruk: Langsung pakai tanpa validasi
$user = User::find($request->id);
$user->email = $request->email;
$user->save();

// ✅ Baik: Validasi dulu
$validated = $request->validate([
    'id' => 'required|integer|exists:users,id',
    'email' => 'required|email|max:255'
]);

$user = User::findOrFail($validated['id']);
$user->email = $validated['email'];
$user->save();

Separate Error from Exception

Error adalah kondisi yang tidak bisa diprediksi (out of memory, disk full). Exception adalah kondisi exceptional yang bisa diantisipasi (record not found, validation failed).

Gunakan exception untuk flow control yang bisa diprediksi. Jangan pakai exception untuk kondisi normal.

Implementasi Error Handling di Berbagai Layer

Error handling harus diterapkan di setiap layer aplikasi, bukan hanya di controller.

Layer Validation

Validation adalah line of defense pertama. Tolak bad data sebelum masuk ke business logic.

class UpdateProfileRequest extends FormRequest
{
    public function rules()
    {
        return [
            'name' => 'required|string|max:100',
            'email' => 'required|email|unique:users,email,' . $this->user()->id,
            'phone' => 'nullable|regex:/^[0-9]{10,13}$/',
            'date_of_birth' => 'nullable|date|before:today'
        ];
    }

    public function messages()
    {
        return [
            'email.unique' => 'Email sudah digunakan akun lain.',
            'phone.regex' => 'Format nomor telepon tidak valid.',
            'date_of_birth.before' => 'Tanggal lahir harus di masa lalu.'
        ];
    }
}

Validation error harus mengembalikan pesan yang user-friendly, bukan technical jargon.

Layer Business Logic

Di service layer, gunakan custom exception untuk business rule violation.

class InsufficientBalanceException extends Exception
{
    public function __construct($required, $available)
    {
        $message = "Saldo tidak cukup. Dibutuhkan: Rp" . number_format($required) . 
                   ", Tersedia: Rp" . number_format($available);
        parent::__construct($message);
    }
}

class TransferService
{
    public function transfer($fromAccount, $toAccount, $amount)
    {
        DB::beginTransaction();
        
        try {
            if ($fromAccount->balance balance);
            }
            
            $fromAccount->balance -= $amount;
            $toAccount->balance += $amount;
            
            $fromAccount->save();
            $toAccount->save();
            
            DB::commit();
            
            return true;
            
        } catch (Exception $e) {
            DB::rollBack();
            Log::error('Transfer failed: ' . $e->getMessage(), [
                'from' => $fromAccount->id,
                'to' => $toAccount->id,
                'amount' => $amount
            ]);
            throw $e;
        }
    }
}

Kesulitan dengan tugas programming atau butuh bantuan coding? KerjaKode siap membantu menyelesaikan tugas IT dan teknik informatika Anda. Dapatkan bantuan profesional di jasa tugas IT KerjaKode.

Layer Database

Database operation adalah sumber error yang paling sering: connection timeout, deadlock, constraint violation.

try {
    $order = Order::create($orderData);
    
} catch (QueryException $e) {
    
    if ($e->getCode() === '23000') {
        // Duplicate entry or foreign key constraint
        Log::warning('Database constraint violation', [
            'error' => $e->getMessage(),
            'data' => $orderData
        ]);
        throw new ValidationException('Data tidak valid atau sudah ada.');
    }
    
    if (Str::contains($e->getMessage(), 'Deadlock')) {
        // Retry dengan exponential backoff
        return $this->retryWithBackoff(function() use ($orderData) {
            return Order::create($orderData);
        });
    }
    
    // Unknown database error
    Log::error('Database error: ' . $e->getMessage());
    throw new SystemException('Terjadi kesalahan sistem. Silakan coba lagi.');
}

Layer External API

API eksternal unreliable. Network bisa down, response bisa invalid, rate limit bisa exceeded.

class PaymentGatewayService
{
    public function processPayment($orderId, $amount)
    {
        $maxRetries = 3;
        $attempt = 0;
        
        while ($attempt retry(2, 100)
                    ->post($this->gatewayUrl, [
                        'order_id' => $orderId,
                        'amount' => $amount,
                        'api_key' => $this->apiKey
                    ]);
                
                if (!$response->successful()) {
                    throw new PaymentGatewayException(
                        'Payment gateway error: ' . $response->status()
                    );
                }
                
                return $response->json();
                
            } catch (ConnectionException $e) {
                $attempt++;
                
                if ($attempt >= $maxRetries) {
                    Log::error('Payment gateway unreachable after retries', [
                        'order_id' => $orderId,
                        'attempts' => $attempt
                    ]);
                    throw new PaymentGatewayException(
                        'Gateway pembayaran tidak dapat dihubungi. Silakan coba lagi nanti.'
                    );
                }
                
                sleep(pow(2, $attempt)); // Exponential backoff
            }
        }
    }
}

Logging Strategy yang Efektif

Logging adalah mata dan telinga Anda di production. Tanpa log yang baik, debugging jadi nightmare.

Log Level yang Tepat

Jangan log everything sebagai error. Gunakan level yang sesuai dengan severity.

  • DEBUG: Informasi detail untuk debugging, hanya aktif di development
  • INFO: Event normal yang penting (user login, order created)
  • WARNING: Kondisi tidak normal tapi tidak critical (retry API, fallback executed)
  • ERROR: Kesalahan yang butuh perhatian (payment failed, email not sent)
  • CRITICAL: Error yang membuat sistem tidak bisa berfungsi (database down, disk full)
// ❌ Buruk: Semua error level sama
Log::error('User accessed page');
Log::error('Payment processing');
Log::error('Database connection failed');

// ✅ Baik: Level sesuai severity
Log::info('User accessed dashboard', ['user_id' => $userId]);
Log::warning('Payment retry attempt', ['attempt' => 2, 'order_id' => $orderId]);
Log::error('Database connection failed', ['host' => $dbHost, 'error' => $e->getMessage()]);

Structured Logging

Log yang terstruktur lebih mudah di-query dan di-analyze.

// ❌ Buruk: String concatenation
Log::error("Payment failed for order " . $orderId . " amount " . $amount);

// ✅ Baik: Structured data
Log::error('Payment failed', [
    'order_id' => $orderId,
    'amount' => $amount,
    'user_id' => $userId,
    'payment_method' => $paymentMethod,
    'error_code' => $errorCode,
    'timestamp' => now(),
    'ip_address' => request()->ip()
]);

Dengan structured logging, Anda bisa dengan mudah query: "Berapa payment yang failed hari ini?", "Payment method apa yang paling sering error?"

Contextual Information

Setiap log harus punya context yang cukup untuk debugging tanpa perlu melihat code.

Log::error('Order creation failed', [
    'user_id' => auth()->id(),
    'cart_items' => $cart->items->count(),
    'total_amount' => $cart->total,
    'shipping_address' => $shippingAddress,
    'payment_method' => $paymentMethod,
    'error' => $e->getMessage(),
    'trace' => $e->getTraceAsString(),
    'request_id' => request()->header('X-Request-ID')
]);

Error Response untuk User

User tidak peduli tentang NullPointerException atau database constraint. Mereka butuh pesan yang jelas dan actionable.

User-Friendly Error Messages

// ❌ Buruk: Technical message
return response()->json([
    'error' => 'SQLSTATE[23000]: Integrity constraint violation'
], 500);

// ✅ Baik: User-friendly message
return response()->json([
    'message' => 'Maaf, terjadi kesalahan saat memproses data Anda.',
    'action' => 'Silakan coba lagi atau hubungi support jika masalah berlanjut.',
    'error_code' => 'DB_CONSTRAINT_001'
], 500);

Error code berguna untuk support team saat user melaporkan masalah.

Consistent Error Response Format

Standardisasi format error response agar frontend mudah handling.

class ApiExceptionHandler
{
    public function render($request, Exception $exception)
    {
        if ($exception instanceof ValidationException) {
            return response()->json([
                'status' => 'error',
                'type' => 'validation',
                'message' => 'Data yang Anda masukkan tidak valid.',
                'errors' => $exception->errors(),
                'timestamp' => now()->toIso8601String()
            ], 422);
        }
        
        if ($exception instanceof ModelNotFoundException) {
            return response()->json([
                'status' => 'error',
                'type' => 'not_found',
                'message' => 'Data yang Anda cari tidak ditemukan.',
                'timestamp' => now()->toIso8601String()
            ], 404);
        }
        
        // Generic error
        $errorId = Str::uuid();
        
        Log::error('Unhandled exception', [
            'error_id' => $errorId,
            'exception' => get_class($exception),
            'message' => $exception->getMessage(),
            'trace' => $exception->getTraceAsString()
        ]);
        
        return response()->json([
            'status' => 'error',
            'type' => 'system',
            'message' => 'Terjadi kesalahan sistem. Mohon coba beberapa saat lagi.',
            'error_id' => $errorId,
            'timestamp' => now()->toIso8601String()
        ], 500);
    }
}

Error ID memudahkan user melaporkan masalah dan team support mencari log yang relevan.

Monitoring dan Alerting

Error handling tidak berhenti di logging. Anda harus tahu saat error terjadi, bukan tunggu user komplain.

Real-time Error Tracking

Gunakan service seperti Sentry, Bugsnag, atau Rollbar untuk real-time error tracking.

// Install Sentry
composer require sentry/sentry-laravel

// config/sentry.php
'dsn' => env('SENTRY_LARAVEL_DSN'),
'environment' => env('APP_ENV'),
'release' => env('APP_VERSION'),

// Sentry akan otomatis capture exception
// dan memberikan context lengkap

Tool ini memberikan stack trace, user context, breadcrumbs, dan aggregation error yang sama.

Alert Rules

Setup alert untuk kondisi critical agar bisa response cepat.

  • Error rate naik lebih dari 5% dalam 5 menit
  • Critical error terjadi (payment gateway down, database unreachable)
  • Specific error terjadi lebih dari 10x dalam 1 jam
  • Response time API lebih dari 5 detik

Testing Error Handling

Error handling code juga harus di-test, bukan cuma happy path.

public function test_transfer_with_insufficient_balance()
{
    $user = User::factory()->create();
    $fromAccount = Account::factory()->create([
        'user_id' => $user->id,
        'balance' => 50000
    ]);
    $toAccount = Account::factory()->create();
    
    $this->expectException(InsufficientBalanceException::class);
    
    $service = new TransferService();
    $service->transfer($fromAccount, $toAccount, 100000);
}

public function test_api_handles_gateway_timeout()
{
    Http::fake([
        'payment-gateway.com/*' => Http::response([], 504)
    ]);
    
    $response = $this->postJson('/api/payment', [
        'order_id' => 123,
        'amount' => 50000
    ]);
    
    $response->assertStatus(503)
        ->assertJson([
            'type' => 'gateway_error',
            'message' => 'Gateway pembayaran tidak dapat dihubungi. Silakan coba lagi nanti.'
        ]);
}

Best Practices Error Handling

Beberapa best practice yang perlu diingat:

Jangan Catch Exception Tanpa Handle

// ❌ Buruk: Silent failure
try {
    $result = $this->processPayment();
} catch (Exception $e) {
    // kosong - error hilang tanpa jejak
}

// ✅ Baik: Log atau re-throw
try {
    $result = $this->processPayment();
} catch (PaymentException $e) {
    Log::error('Payment failed', ['error' => $e->getMessage()]);
    throw $e;
} catch (Exception $e) {
    Log::critical('Unexpected error in payment', ['error' => $e->getMessage()]);
    throw new SystemException('Terjadi kesalahan sistem.');
}

Jangan Return Error Code di Success Response

// ❌ Buruk: HTTP 200 tapi ada error
return response()->json([
    'status' => 'error',
    'message' => 'Payment failed'
], 200);

// ✅ Baik: HTTP status code yang sesuai
return response()->json([
    'status' => 'error',
    'message' => 'Payment failed'
], 400);

Gunakan Circuit Breaker untuk External Service

Jika external service down, jangan spam request. Gunakan circuit breaker pattern.

class CircuitBreaker
{
    private $failureThreshold = 5;
    private $timeout = 60; // seconds
    
    public function call($callback)
    {
        $key = 'circuit_breaker:' . md5(serialize($callback));
        $failures = Cache::get($key, 0);
        
        if ($failures >= $this->failureThreshold) {
            throw new ServiceUnavailableException(
                'Service sementara tidak tersedia. Silakan coba lagi nanti.'
            );
        }
        
        try {
            $result = $callback();
            Cache::forget($key);
            return $result;
            
        } catch (Exception $e) {
            Cache::put($key, $failures + 1, $this->timeout);
            throw $e;
        }
    }
}

Kesimpulan

Error handling yang robust adalah investasi untuk stabilitas aplikasi jangka panjang.

Implementasi yang baik meliputi validation di setiap layer, custom exception untuk business logic, logging yang terstruktur, error response yang user-friendly, dan monitoring real-time.

Jangan tunggu aplikasi crash di production. Mulai implementasi error handling yang proper dari sekarang.

Error adalah bagian normal dari software development. Yang membedakan aplikasi profesional adalah bagaimana error itu di-handle dengan graceful.

Dengan error handling yang solid, aplikasi Anda akan lebih stable, user experience lebih baik, dan debugging jauh lebih mudah saat masalah terjadi.

Ajie Kusumadhany
Written by

Ajie Kusumadhany

Founder & Lead Developer KerjaKode. Berpengalaman dalam pengembangan web modern dengan Laravel, React.js, Vue.js, dan teknologi terkini. Passionate tentang coding, teknologi, dan berbagi pengetahuan melalui artikel.

Promo Spesial Hari Ini!

10% DISKON

Promo berakhir dalam:

00 Jam
:
00 Menit
:
00 Detik
Klaim Promo Sekarang!

*Promo berlaku untuk order hari ini

0
User Online
Halo! 👋
Kerjakode Support Online
×

👋 Hai! Pilih layanan yang kamu butuhkan:

Chat WhatsApp Sekarang