อะไรคือวิธีปฏิบัติที่ดีที่สุดสำหรับการจับและการยกเว้นใหม่?


156

ควรจับโยนข้อยกเว้นใหม่อีกครั้งโดยตรงหรือควรปิดล้อมข้อยกเว้นใหม่หรือไม่

นั่นคือฉันควรทำสิ่งนี้:

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

หรือสิ่งนี้:

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

หากคำตอบของคุณคือการขว้างปาโดยตรงโปรดแนะนำการใช้ข้อยกเว้นการผูกมัดฉันไม่สามารถเข้าใจสถานการณ์จริงที่เราใช้ข้อยกเว้นการผูกมัด

คำตอบ:


287

คุณไม่ควรจับข้อยกเว้น เว้นแต่คุณตั้งใจจะทำบางสิ่งบางอย่างที่มีความหมาย

"สิ่งที่มีความหมาย" อาจเป็นหนึ่งในสิ่งเหล่านี้:

จัดการข้อยกเว้น

การกระทำที่มีความหมายชัดเจนที่สุดคือการจัดการข้อยกเว้นเช่นโดยแสดงข้อความแสดงข้อผิดพลาดและยกเลิกการดำเนินการ:

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

การบันทึกหรือล้างข้อมูลบางส่วน

บางครั้งคุณไม่ทราบวิธีจัดการข้อยกเว้นอย่างถูกต้องในบริบทเฉพาะ บางทีคุณอาจขาดข้อมูลเกี่ยวกับ "ภาพรวม" แต่คุณต้องการบันทึกความล้มเหลวใกล้กับจุดที่เกิดขึ้นมากที่สุด ในกรณีนี้คุณอาจต้องการจับบันทึกและโยนใหม่:

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

สถานการณ์ที่เกี่ยวข้องคือที่ที่คุณอยู่ในสถานที่ที่เหมาะสมในการดำเนินการล้างข้อมูลสำหรับการดำเนินการที่ล้มเหลว แต่ไม่ต้องตัดสินใจว่าควรจัดการความล้มเหลวที่ระดับบนสุดได้อย่างไร ในเวอร์ชัน PHP ก่อนหน้านี้จะถูกนำมาใช้เป็น

$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 ได้เปิดตัวfinallyคำหลักดังนั้นสำหรับสถานการณ์การล้างข้อมูลขณะนี้มีวิธีอื่นในการเข้าถึง หากโค้ดการล้างข้อมูลต้องทำงานไม่ว่าจะเกิดอะไรขึ้น (เช่นทั้งข้อผิดพลาดและความสำเร็จ) ขณะนี้คุณสามารถทำเช่นนี้ได้ในขณะที่โปร่งใสอนุญาตให้มีข้อยกเว้นที่ถูกโยนทิ้งเพื่อเผยแพร่:

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

เกิดข้อผิดพลาด (ยกเว้นการโยง)

กรณีที่สามคือที่ที่คุณต้องการจัดกลุ่มความล้มเหลวที่เป็นไปได้หลายอย่างภายใต้ร่มขนาดใหญ่ ตัวอย่างสำหรับการจัดกลุ่มแบบลอจิคัล:

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);
        }
    }
}

ในกรณีนี้คุณไม่ต้องการให้ผู้ใช้Componentทราบว่ามีการใช้งานโดยใช้การเชื่อมต่อฐานข้อมูล (บางทีคุณอาจต้องการให้ตัวเลือกของคุณเปิดอยู่และใช้ที่เก็บข้อมูลแบบไฟล์ในอนาคต) ดังนั้นสเปคของคุณสำหรับComponentจะบอกว่า "ในกรณีของความล้มเหลวในการเริ่มต้นComponentInitExceptionจะถูกโยน" สิ่งนี้ช่วยให้ผู้บริโภคComponentจับข้อยกเว้นของชนิดที่คาดไว้ในขณะที่ยังอนุญาตให้การดีบักรหัสเพื่อเข้าถึงรายละเอียดทั้งหมด (ขึ้นอยู่กับการนำไปใช้)รายละเอียด

ให้บริบทที่สมบูรณ์ยิ่งขึ้น (ยกเว้นการโยง)

ในที่สุดก็มีหลายกรณีที่คุณอาจต้องการให้บริบทเพิ่มเติมสำหรับข้อยกเว้น ในกรณีนี้มันสมเหตุสมผลที่จะรวมข้อยกเว้นไว้ในอีกอันหนึ่งซึ่งเก็บข้อมูลเพิ่มเติมเกี่ยวกับสิ่งที่คุณพยายามทำเมื่อเกิดข้อผิดพลาด ตัวอย่างเช่น:

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);
        }
    }
}

กรณีนี้คล้ายกับด้านบน (และตัวอย่างอาจไม่ใช่สิ่งที่ดีที่สุดที่จะเกิดขึ้น) แต่มันแสดงให้เห็นถึงจุดที่ให้บริบทเพิ่มเติม: หากมีการโยนข้อยกเว้นจะเป็นการบอกเราว่าการคัดลอกไฟล์ล้มเหลว แต่ทำไมมันถึงล้มเหลว ข้อมูลนี้มีให้ในข้อยกเว้นที่ห่อ (ซึ่งอาจมีมากกว่าหนึ่งระดับหากตัวอย่างมีความซับซ้อนมากขึ้น)

มูลค่าของการทำเช่นนี้จะแสดงให้เห็นถ้าคุณคิดเกี่ยวกับสถานการณ์ที่เช่นการสร้าง UserProfileวัตถุทำให้ไฟล์ถูกคัดลอกเนื่องจากโปรไฟล์ผู้ใช้ถูกเก็บไว้ในไฟล์และสนับสนุนความหมายของการทำธุรกรรม: คุณสามารถเปลี่ยนแปลง "เลิกทำ" ได้เนื่องจากพวกเขาเท่านั้น คัดลอกโปรไฟล์จนกว่าคุณจะยอมรับ

ในกรณีนี้ถ้าคุณทำ

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

และเนื่องจากข้อผิดพลาดข้อยกเว้น "เป้าหมายไดเรกทอรีไม่สามารถสร้างได้" คุณจะมีสิทธิ์สับสนได้ การตัดข้อยกเว้น "แกนกลาง" นี้ในเลเยอร์ของข้อยกเว้นอื่น ๆ ที่ให้บริบทจะทำให้เกิดข้อผิดพลาดได้ง่ายขึ้น ("การสร้างการคัดลอกโปรไฟล์ล้มเหลว" -> "การดำเนินการคัดลอกไฟล์ล้มเหลว" -> "ไม่สามารถสร้างไดเรกทอรีเป้าหมาย")


ฉันเห็นด้วยกับเหตุผล 2 ข้อสุดท้ายเท่านั้น: 1 / จัดการข้อยกเว้น: คุณไม่ควรทำในระดับนี้ 2 / การบันทึกหรือล้างข้อมูล: ใช้ในที่สุดและบันทึกข้อยกเว้นด้านบนข้อมูลของคุณ
remi bourgarel

1
@remi: ยกเว้นว่า PHP ไม่รองรับการfinallyสร้าง (ยังเป็นอย่างน้อย) ... นั่นคือออกไปซึ่งหมายความว่าเราต้องหันไปใช้สิ่งสกปรกเช่นนี้ ...
ircmaxell

@remibourgarel: 1: นั่นเป็นเพียงตัวอย่าง แน่นอนคุณไม่ควรทำในระดับนี้ แต่คำตอบนั้นนานพอ 2: @ircmaxell บอกว่าไม่มีfinallyPHP
Jon

3
ในที่สุดตอนนี้ PHP 5.5 ก็ใช้ในที่สุด
OCDev

12
มีเหตุผลที่ฉันคิดว่าคุณพลาดจากรายการของคุณที่นี่ - คุณอาจไม่สามารถบอกได้ว่าคุณสามารถจัดการข้อยกเว้นได้จนกว่าคุณจะได้รับมันและมีโอกาสตรวจสอบมัน ตัวอย่างเช่น wrapper สำหรับ API ระดับต่ำกว่าที่ใช้รหัสข้อผิดพลาด (และมี zillions ของพวกเขา) อาจมีคลาสข้อยกเว้นเดียวที่มันโยนอินสแตนซ์ของข้อผิดพลาดใด ๆ กับerror_codeคุณสมบัติที่สามารถตรวจสอบเพื่อรับข้อผิดพลาดพื้นฐาน รหัส. หากคุณสามารถจัดการข้อผิดพลาดเหล่านั้นได้อย่างมีความหมายคุณอาจต้องจับตรวจสอบและหากคุณไม่สามารถจัดการข้อผิดพลาดได้
Mark Amery

37

มันคือทั้งหมดที่เกี่ยวกับการรักษาสิ่งที่เป็นนามธรรม ดังนั้นฉันขอแนะนำให้ใช้การผูกมัดข้อยกเว้นเพื่อโยนโดยตรง เท่าที่ฉันจะอธิบายแนวคิดของabstractions รั่ว

สมมติว่าคุณกำลังสร้างแบบจำลอง ตัวแบบควรจะเป็นนามธรรมออกไปจากการคงอยู่ของข้อมูลทั้งหมดและการตรวจสอบความถูกต้องจากส่วนที่เหลือของแอปพลิเคชัน ดังนั้นจะเกิดอะไรขึ้นเมื่อคุณพบข้อผิดพลาดของฐานข้อมูล ถ้าคุณDatabaseQueryExceptionทำสิ่งนั้นซ้ำคุณกำลังรั่วสิ่งที่เป็นนามธรรม เพื่อทำความเข้าใจว่าทำไมคิดเกี่ยวกับสิ่งที่เป็นนามธรรมสำหรับวินาที คุณไม่สนใจว่าตัวแบบจะเก็บข้อมูลอย่างไร ในทำนองเดียวกันคุณไม่สนใจสิ่งที่ผิดพลาดในระบบต้นแบบของแบบจำลองเพียงแค่คุณรู้ว่ามีบางอย่างผิดปกติและสิ่งที่เกิดขึ้นโดยประมาณ

ดังนั้นโดยการสร้าง DatabaseQueryException ขึ้นมาอีกครั้งคุณกำลังทำสิ่งที่เป็นนามธรรมและต้องใช้รหัสโทรศัพท์เพื่อทำความเข้าใจกับความหมายของสิ่งที่เกิดขึ้นภายใต้ตัวแบบ แต่ให้สร้างชื่อสามัญModelStorageExceptionและห่อสิ่งที่ติดDatabaseQueryExceptionอยู่ภายในนั้นแทน ด้วยวิธีนี้รหัสโทรของคุณยังคงสามารถจัดการกับข้อผิดพลาดทางความหมายได้ แต่มันไม่สำคัญว่าเทคโนโลยีต้นแบบของ Model จะเป็นอย่างไรเนื่องจากคุณเพียงแสดงข้อผิดพลาดจากเลเยอร์นามธรรมนั้น ยิ่งไปกว่านั้นเมื่อคุณรวมข้อยกเว้นไว้ถ้ามันทำให้เกิดฟองจนสุดและจำเป็นต้องเข้าสู่ระบบคุณสามารถติดตามไปยังข้อยกเว้นรากที่ถูกโยน (เดินโซ่) เพื่อให้คุณยังมีข้อมูลการดีบักทั้งหมดที่คุณต้องการ!

อย่าเพียง แต่จับและสร้างข้อยกเว้นซ้ำอีกครั้งเว้นแต่คุณจะต้องทำการประมวลผลภายหลัง แต่บล็อกอย่าง} catch (Exception $e) { throw $e; }ไม่มีจุดหมาย แต่คุณสามารถรวมข้อยกเว้นใหม่เพื่อให้ได้สิ่งที่เป็นนามธรรมอย่างมีนัยสำคัญ


2
คำตอบที่ดี ดูเหมือนว่ามีคนไม่กี่คนที่อยู่รอบ ๆ Stack Overflow (ตามคำตอบ ฯลฯ ) เป็นเรื่องที่ใช้ผิดพวกเขา
James

8

IMHO จับข้อยกเว้นเพียง rethrow มันเป็นไร้ประโยชน์ ในกรณีนี้ก็ไม่ได้จับมันและปล่อยให้ก่อนเรียกว่าวิธีการจัดการกับมัน(หรือที่รู้จักวิธีการที่ 'บน' ในกองโทรออก)

หากคุณสร้างมันขึ้นมาใหม่การผูกมัดข้อยกเว้นที่ถูกจับเข้าไปในอันใหม่คุณจะต้องโยนมันเป็นแนวปฏิบัติที่ดีเพราะมันจะเก็บข้อมูลที่มีข้อยกเว้นที่จับได้ อย่างไรก็ตามการ rethrowing นั้นจะมีประโยชน์ก็ต่อเมื่อคุณเพิ่มข้อมูลบางอย่างหรือจัดการบางสิ่งบางอย่างในข้อยกเว้นที่ถูกดักจับอาจเป็นบริบทค่าการบันทึกการปล่อยทรัพยากร

วิธีที่จะเพิ่มข้อมูลบางอย่างคือการขยายExceptionชั้นเรียนจะมีข้อยกเว้นเช่นNullParameterException, DatabaseExceptionฯลฯ เพิ่มเติมมากกว่านี้อนุญาตให้ developper ที่จะจับเพียงข้อยกเว้นบางอย่างที่เขาสามารถจัดการ ตัวอย่างเช่นสามารถจับได้เพียงอย่างเดียวDatabaseExceptionและพยายามแก้ไขสิ่งที่ทำให้เกิดExceptionเช่นเชื่อมต่อกับฐานข้อมูลอีกครั้ง


2
มันไม่ไร้ประโยชน์มีหลายครั้งที่คุณจำเป็นต้องทำอะไรบางอย่างกับข้อยกเว้นพูดในฟังก์ชั่นที่ขว้างมันแล้วโยนมันอีกครั้งเพื่อให้สิ่งที่สูงขึ้นทำอย่างอื่น ในโครงการใดโครงการหนึ่งที่ฉันกำลังทำงานอยู่ในบางครั้งเราพบข้อยกเว้นในวิธีการดำเนินการแสดงการแจ้งเตือนที่เป็นมิตรกับผู้ใช้และจากนั้นก็ทำการโยนใหม่เพื่อให้บล็อก catch ลองเพิ่มเติมในโค้ดสามารถจับได้อีกครั้งเพื่อบันทึกข้อผิดพลาด บันทึก
MitMaro

1
ดังที่ฉันได้กล่าวไปแล้วคุณเพิ่มข้อมูลบางอย่างลงในข้อยกเว้น คุณไม่เพียงแค่คิดใหม่เหมือนในตัวอย่างของ OP
Clere Herreman

2
คุณสามารถสร้างใหม่ได้หากคุณต้องการปิดทรัพยากร แต่ไม่มีข้อมูลเพิ่มเติมเพื่อเพิ่ม ฉันยอมรับว่ามันไม่ใช่สิ่งที่สะอาดที่สุดในโลก แต่ก็ไม่น่ากลัว
ircmaxell

2
@ircmaxell เห็นด้วยแก้ไขเพื่อสะท้อนให้เห็นว่ามันไร้ประโยชน์เฉพาะในกรณีที่คุณไม่ได้ทำอะไรเลยยกเว้น rethrowing มัน
Clement Herreman

1
บิตที่สำคัญคือคุณหลวมข้อมูลไฟล์และ / หรือสายที่ซึ่งข้อยกเว้นได้รับการโยนโดยการขว้างมันอีกครั้ง ดังนั้นจึงเป็นการดีกว่าที่จะขุดใหม่และส่งผ่านเก่าไปตามตัวอย่างที่ 2 ของคำถาม ไม่เช่นนั้นมันจะชี้ไปที่บล็อก catch ทำให้คุณคาดเดาว่าปัญหาที่แท้จริงเกิดอะไรขึ้น
DanMan

2

คุณต้องดู ข้อยกเว้นวิธีปฏิบัติที่ดีที่สุดใน PHP 5.3

การจัดการข้อยกเว้นใน PHP ไม่ใช่คุณสมบัติใหม่โดยเด็ดขาด ในลิงค์ต่อไปนี้คุณจะเห็นคุณสมบัติใหม่สองอย่างใน PHP 5.3 ตามข้อยกเว้น ข้อแรกคือข้อยกเว้นแบบซ้อนและชุดที่สองเป็นประเภทข้อยกเว้นใหม่ที่เสนอโดยส่วนขยาย SPL (ซึ่งปัจจุบันเป็นส่วนขยายหลักของรันไทม์ PHP) คุณสมบัติใหม่ทั้งสองนี้ได้พบแล้วในหนังสือแนวทางปฏิบัติที่ดีที่สุดและสมควรได้รับการตรวจสอบอย่างละเอียด

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


1

คุณมักจะคิดอย่างนี้

ชั้นอาจมีข้อยกเว้นหลายประเภทที่จะไม่ตรงกัน ดังนั้นคุณสร้างคลาสยกเว้นสำหรับคลาสหรือประเภทของคลาสนั้นแล้วโยนทิ้ง

ดังนั้นรหัสที่ใช้คลาสจะต้องมีข้อยกเว้นหนึ่งประเภท


1
เฮ้คุณสามารถให้รายละเอียดเพิ่มเติมหรือลิงค์ที่ฉันสามารถอ่านเพิ่มเติมเกี่ยวกับวิธีการนี้
ราหุลประสาท
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.