İstisnaları yakalamak ve yeniden atmak için en iyi uygulamalar nelerdir?


156

Yakalanan istisnalar doğrudan yeniden atılmalı mı yoksa yeni bir istisna etrafına sarılmalı mıdır?

Yani, bunu yapmalıyım:

try {
  $connect = new CONNECT($db, $user, $password, $driver, $host);
} catch (Exception $e) {
  throw $e;
}

veya bu:

try {
  $connect = new CONNECT($db, $user, $password, $driver, $host);
} catch (Exception $e) {
  throw new Exception("Exception Message", 1, $e);
}

Cevabınız doğrudan atmaksa , lütfen istisna zincirinin kullanılmasını önerin, istisna zincirini kullandığımız gerçek bir dünya senaryosunu anlayamıyorum.

Yanıtlar:


287

Anlamlı bir şey yapmak istemiyorsanız istisnayı yakalamamalısınız .

"Anlamlı bir şey" bunlardan biri olabilir:

İstisnayı işleme

En belirgin anlamlı eylem, istisnayı ele almak, örneğin bir hata mesajı görüntüleyerek ve işlemi iptal ederek:

try {
    $connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
    echo "Error while connecting to database!";
    die;
}

Günlük tutma veya kısmi temizleme

Bazen bir istisnayı belirli bir bağlam içinde nasıl düzgün bir şekilde ele alacağınızı bilmiyorsunuz; "büyük resim" hakkında bilgi sahibi olmayabilirsiniz, ancak hatayı olabildiğince yakın bir yere kaydetmek istersiniz. Bu durumda, yakalamak, günlüğe kaydetmek ve yeniden atmak isteyebilirsiniz:

try {
    $connect = new CONNECT($db, $user, $password, $driver, $host);
}
catch (Exception $e) {
    logException($e); // does something
    throw $e;
}

İlgili bir senaryo, başarısız işlem için bir miktar temizleme gerçekleştirmek için doğru yerde olduğunuz, ancak hatanın en üst düzeyde nasıl ele alınması gerektiğine karar vermediğiniz yerdir. Önceki PHP sürümlerinde bu,

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
    $connect->insertSomeRecord();
}
catch (Exception $e) {
    $connect->disconnect(); // we don't want to keep the connection open anymore
    throw $e; // but we also don't know how to respond to the failure
}

PHP 5.5 finallyanahtar kelimeyi tanıttı , bu yüzden temizleme senaryoları için şimdi buna yaklaşmanın başka bir yolu var. Ne olursa olsun temizleme kodunun çalışması gerekiyorsa (yani hem hatada hem de başarıda), artık atılan istisnaların yayılmasına izin verirken bunu yapmak mümkündür:

$connect = new CONNECT($db, $user, $password, $driver, $host);
try {
    $connect->insertSomeRecord();
}
finally {
    $connect->disconnect(); // no matter what
}

Hata soyutlama (istisna zincirleme ile)

Üçüncü durum, olası birçok başarısızlığı mantıksal olarak daha büyük bir şemsiye altında gruplamak istediğiniz yerdir. Mantıksal gruplama için bir örnek:

class ComponentInitException extends Exception {
    // public constructors etc as in Exception
}

class Component {
    public function __construct() {
        try {
            $connect = new CONNECT($db, $user, $password, $driver, $host);
        }
        catch (Exception $e) {
            throw new ComponentInitException($e->getMessage(), $e->getCode(), $e);
        }
    }
}

Bu durumda, kullanıcıların Componentbir veritabanı bağlantısı kullanılarak uygulandığını bilmelerini istemezsiniz (belki de seçeneklerinizi açık tutmak ve gelecekte dosya tabanlı depolama kullanmak istersiniz). Yani sizin şartnameniz Component"başlatma hatası ComponentInitExceptiondurumunda atılacak" derdi . Bu, tüketicilerin Componentbeklenen türden istisnaları yakalamasına ve hata ayıklama kodunun tüm (uygulamaya bağlı) ayrıntılara erişmesine izin verir .

Daha zengin bağlam sağlama (istisna zincirleme hariç)

Son olarak, istisna için daha fazla bağlam sağlamak isteyebileceğiniz durumlar vardır. Bu durumda, istisnayı, hata oluştuğunda ne yapmaya çalıştığınız hakkında daha fazla bilgi tutan başka bir duruma kaydırmak mantıklıdır. Örneğin:

class FileOperation {
    public static function copyFiles() {
        try {
            $copier = new FileCopier(); // the constructor may throw

            // this may throw if the files do no not exist
            $copier->ensureSourceFilesExist();

            // this may throw if the directory cannot be created
            $copier->createTargetDirectory();

            // this may throw if copying a file fails
            $copier->performCopy();
        }
        catch (Exception $e) {
            throw new Exception("Could not perform copy operation.", 0, $e);
        }
    }
}

Bu durum yukarıdakine benzerdir (ve örnek muhtemelen ortaya çıkabilecek en iyi örnek değildir), ancak daha fazla bağlam sağlama noktasını gösterir: bir istisna atılırsa, dosya kopyasının başarısız olduğunu bildirir. Ama neden başarısız oldu? Bu bilgiler, istisnalar dışında sağlanmıştır (örnek çok daha karmaşıksa birden fazla seviye olabilir).

Örneğin, bir UserProfilenesne oluşturmanın dosyaların kopyalanmasına neden olduğu ve kullanıcı profili dosyalarda saklandığından ve işlem semantiğini desteklediği bir senaryo düşünürseniz, bunu yapmanın değeri gösterilir : değişiklikleri yalnızca bir tamamlanana kadar profilin kopyası.

Bu durumda,

try {
    $profile = UserProfile::getInstance();
}

ve sonuç olarak "Hedef dizin oluşturulamadı" istisna hatası yakalandığında, karıştırılma hakkınız olur. Bağlam sağlayan diğer istisnaların katmanlarındaki bu "çekirdek" istisnayı silmek hatayı ele almayı çok daha kolaylaştıracaktır ("Profil kopyası oluşturma başarısız oldu" -> "Dosya kopyalama işlemi başarısız oldu" -> "Hedef dizin oluşturulamadı").


Sadece son 2 nedene katılıyorum: 1 / istisnayı ele almak: bu seviyede yapmamalısınız, 2 / günlük kaydı veya temizleme: son olarak kullanın ve veri katmanınızın üzerindeki istisnayı günlüğe kaydedin
remi bourgarel

1
@remi: PHP'nin finallyyapıyı desteklememesi dışında (en azından henüz değil) ... Yani bu çıktı, yani bunun gibi kirli şeylere başvurmalıyız ...
ircmaxell

@remibourgarel: 1: Bu sadece bir örnekti. Elbette bunu bu seviyede yapmamalısınız, ancak cevap olduğu kadar uzun. 2: @ircmaxell'in dediği gibi PHP'de hayır finally.
Jon

3
Son olarak, PHP 5.5 nihayet uygulanmaktadır.
OCDev

12
Buradaki listenizden kaçırdığınızı düşündüğüm bir neden var - bir istisnayı yakalayana ve inceleme şansı bulana kadar tutamayacağınızı söyleyemeyebilirsiniz. Örneğin, hata kodlarını kullanan (ve bunların zilyonlarını içeren) daha düşük düzeyli bir API için bir sarıcı, herhangi bir hata için bir örnek atadığı tek bir istisna sınıfına error_codesahip olabilir ve temel hatayı almak için kontrol edilebilen bir özellik kodu. Bu hataların yalnızca bazılarını anlamlı bir şekilde işleyebiliyorsanız, muhtemelen yakalamak, incelemek ve hatayı kaldıramazsanız yeniden düşünün.
Mark Amery

37

Tamamen soyutlamayı korumakla ilgili. Bu yüzden doğrudan atmak için istisna zincirleme kullanmanızı öneririm. Neden olduğu kadar, sızdıran soyutlamalar kavramını açıklayayım

Diyelim ki bir model inşa ediyorsunuz. Modelin, uygulamanın geri kalanından tüm veri kalıcılığını ve doğrulamasını soyutlaması gerekiyor. Şimdi bir veritabanı hatası aldığınızda ne olur? Eğer yeniden düşünürsen DatabaseQueryException, soyutlamayı sızdırıyorsun. Nedenini anlamak için bir saniyeliğine soyutlamayı düşünün. Modelin verileri nasıl sakladığı umurumda değil , sadece öyle. Aynı şekilde, modelin altında yatan sistemlerde neyin yanlış gittiğini umursamıyorsunuz, sadece bir şeyin yanlış gittiğini ve neyin yanlış gittiğini biliyorsunuz.

Dolayısıyla, DatabaseQueryException'ı yeniden düzenleyerek, soyutlamayı sızdırıyorsunuz ve arama kodunun modelin altında neler olup bittiğini anlamasını istersiniz. Bunun yerine, bir jenerik oluşturun ModelStorageExceptionve yakalanan DatabaseQueryExceptionşeyin içine sarın . Bu şekilde, arama kodunuz yine de semantik olarak hatayı ele almaya çalışabilir, ancak Modelin temelindeki teknolojinin önemi yoktur, çünkü yalnızca o soyutlama katmanından hataları ortaya çıkarırsınız. Daha da iyisi, istisnayı tamamladığınızdan, tümüyle kabarıyorsa ve günlüğe kaydedilmesi gerekiyorsa, atılan kök istisnasını (zinciri yürütebilirsiniz) takip edebilirsiniz, böylece ihtiyacınız olan tüm hata ayıklama bilgilerine sahip olursunuz!

İşlem sonrası işlem yapmanız gerekmedikçe, aynı istisnayı yakalayıp tekrar düşünmeyin. Ama benzeri bir blok } catch (Exception $e) { throw $e; }anlamsızdır. Ancak, bazı önemli soyutlama kazançları için istisnaları yeniden sarabilirsiniz.


2
Mükemmel cevap. Stack Overflow (cevaplar vb. Dayalı) etrafında oldukça az sayıda kişi onları yanlış kullanıyor gibi görünüyor.
James

8

IMHO, sadece yeniden anlatmak için bir İstisna yakalamak işe yaramaz . Bu durumda, onu yakalamayın ve daha önce çağrılan yöntemlerin (çağrı yığınında 'üst' olan yöntemler) işlemesine izin verin .

Yeniden yazarsanız, yakalanan istisnayı atacağınız yenisiyle zincirlemek kesinlikle iyi bir uygulamadır, çünkü yakalanan istisnanın içerdiği bilgileri tutacaktır. Bununla birlikte, bunun yeniden ele alınması yalnızca yakalanan istisnaya bazı bilgiler eklerseniz veya bir şey kullanırsanız yararlı olur , bazı bağlamlar, değerler, günlük kaydı, kaynakları serbest bırakma, her ne olursa olsun.

Bazı bilgi eklemek için bir yolu uzatmaktır Exception, sınıf gibi istisnalar var NullParameterException, DatabaseExceptionvb Daha fazla, bu developper sadece kendisinin işleyebileceği bazı istisnalar yakalamak için izin verir. Örneğin, yalnızca veritabanına yeniden bağlanmak gibi DatabaseExceptionneden olanı çözmeye çalışabilir Exception.


2
Bu işe yaramaz değil, bir istisna üzerinde bir şey yapmanız gereken zamanlar vardır, bu onu atar ve daha yüksek bir yakalamanın başka bir şey yapmasına izin vermek için tekrar atar. Üzerinde çalıştığım projelerden birinde, bazen bir eylem yönteminde bir istisna yakalarız, kullanıcıya kolay bir bildirim görüntüleriz ve daha sonra tekrar atarız, böylece kodda daha fazla bir deneme yakalama bloğu, hatayı günlüğe kaydetmek için tekrar yakalayabilir bir kayıt.
MitMaro

1
Dediğim gibi, istisnaya bazı bilgiler eklersiniz (bir bildirim görüntüleme, günlüğe kaydetme). Sen yok sadece rethrow OP'ın örnekte olduğu gibi.
Clement Herreman

2
Kaynakları kapatmanız gerekiyorsa yeniden ekleyebilirsiniz, ancak eklenecek ek bilgi yoktur. Katılıyorum dünyanın en temiz şey değil, ama korkunç
ircmaxell

2
@ircmaxell Anlaştı, sadece geri almak dışında istisnai bir şey yapmazsanız
Clement Herreman

1
Önemli olan, istisna atıldığı dosya ve / veya satır bilgilerini yeniden atarak kaybetmenizdir. Bu nedenle, sorunun yeni bir örneğinde olduğu gibi, yenisini kandırmak ve eskisini geçirmek genellikle daha iyidir. Aksi takdirde, yakalama bloğunu gösterecek ve gerçek sorunun ne olduğunu tahmin edeceksiniz.
DanMan

2

PHP 5.3 İstisna İyi Örnekler bir göz atmak zorunda

PHP'de kural dışı durum işleme, herhangi bir streç için yeni bir özellik değildir. Aşağıdaki bağlantıda, PHP 5.3'te istisnalara dayalı iki yeni özellik göreceksiniz. Birincisi yuvalanmış istisnalar ve ikincisi SPL uzantısı tarafından sunulan yeni bir istisna türleri kümesidir (şimdi PHP çalışma zamanının çekirdek uzantısıdır). Her iki yeni özellik de en iyi en iyi uygulamalar kitabına girmiş ve ayrıntılı olarak incelenmeyi hak etmiştir.

http://ralphschindler.com/2010/09/15/exception-best-practices-in-php-5-3


1

Genellikle böyle düşünürsünüz.

Bir sınıf eşleşmeyecek birçok istisna atabilir. Yani o sınıf veya sınıf türü için bir istisna sınıfı yaratıp atıyorsunuz.

Bu nedenle, sınıfı kullanan kodun yalnızca bir tür özel durumu yakalaması gerekir.


1
Hey u u lütfen daha fazla ayrıntı veya bu yaklaşım hakkında daha fazla okuyabileceğiniz bir bağlantı sağlayabilir.
Rahul Prasad
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.