การตรวจสอบพารามิเตอร์ Constructor ใน C # - แนวปฏิบัติที่ดีที่สุด


34

แนวปฏิบัติที่ดีที่สุดสำหรับการตรวจสอบความถูกต้องของพารามิเตอร์คอนสตรัคเตอร์คืออะไร?

สมมติว่า C # เป็นเรื่องง่าย:

public class MyClass
{
    public MyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            throw new ArgumentException("Text cannot be empty");

        // continue with normal construction
    }
}

มันจะเป็นที่ยอมรับที่จะโยนข้อยกเว้น?

ทางเลือกที่ฉันพบคือการตรวจสอบความถูกต้องก่อนที่จะสร้างอินสแตนซ์:

public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
        {
            MessageBox.Show("Text cannot be empty");
            return null;
        }
        else
        {
            return new MyClass(text);
        }
    }
}

10
"ยอมรับการยกเว้นได้หรือไม่" ทางเลือกคืออะไร
S.Lott

10
@ S.Lott: ไม่ทำอะไรเลย ตั้งค่าเริ่มต้น พิมพ์ mesasge ไปที่ console / logfile สั่นกระดิ่ง. ไฟกระพริบ
FrustratedWithFormsDesigner

3
@FrustratedWithFormsDesigner: "ไม่ทำอะไรเลยเหรอ?" ข้อ จำกัด "ข้อความไม่ว่าง" จะได้รับความพึงพอใจอย่างไร "ตั้งค่าเริ่มต้น" หรือไม่ ค่าอาร์กิวเมนต์ข้อความจะใช้อย่างไร ความคิดอื่น ๆ นั้นใช้ได้ แต่ทั้งสองจะนำไปสู่วัตถุในสถานะที่ละเมิดข้อ จำกัด ในชั้นนี้
S.Lott

1
@ S.Lott เพราะกลัวว่าจะทำซ้ำตัวเองฉันเปิดโอกาสให้มีวิธีอื่นที่ฉันไม่เคยรู้ ฉันเชื่อว่าฉันไม่ได้รู้ทั้งหมดว่าฉันรู้อย่างแน่นอน
MPelletier

3
@ MPelletier การตรวจสอบความถูกต้องของพารามิเตอร์ล่วงหน้าจะเป็นการละเมิด DRY (อย่าทำซ้ำตัวเอง) เนื่องจากคุณจะต้องคัดลอกการตรวจสอบความถูกต้องล่วงหน้าไปยังรหัสที่พยายามยกตัวอย่างคลาสนี้
CaffGeek

คำตอบ:


26

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

if (string.IsNullOrEmpty(text))
    throw new ArgumentException("message", "text");

หากคุณใช้. NET 4 คุณสามารถทำได้ หลักสูตรนี้ขึ้นอยู่กับว่าคุณพิจารณาสตริงที่มีเฉพาะ white space ว่าไม่ถูกต้อง

if (string.IsNullOrWhiteSpace(text))
    throw new ArgumentException("message", "text");

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

@FrustratedWithFormsDesigner - ฉันถือว่าคุณลงคะแนนให้ฉันเพื่อสิ่งนี้หรือไม่? แน่นอนว่าคุณถูกต้องที่ฉันคาดการณ์ แต่ชัดเจนว่าวัตถุทางทฤษฎีนี้จะต้องไม่ถูกต้องหากข้อความอินพุตว่างเปล่า คุณต้องการโทรInitเพื่อรับรหัสข้อผิดพลาดบางประเภทเมื่อข้อยกเว้นทำงานได้ดีและบังคับให้วัตถุของคุณรักษาสถานะที่ถูกต้องอยู่เสมอ
ChaosPandion

2
ฉันมักจะแยกออกว่ากรณีแรกออกเป็นสองข้อยกเว้นเฉพาะกิจการ: หนึ่งที่ทดสอบเป็นโมฆะและพ่นArgumentNullExceptionและอื่น ๆ ArgumentExceptionซึ่งการทดสอบที่ว่างเปล่าและพ่น ช่วยให้ผู้โทรจะพิถีพิถันมากขึ้นหากพวกเขาต้องการในการจัดการข้อยกเว้น
Jesse C. Slicer

1
@Jesse - ฉันใช้เทคนิคนั้น แต่ฉันยังอยู่ในรั้วเกี่ยวกับประโยชน์โดยรวมของมัน
ChaosPandion

3
+1 สำหรับวัตถุที่ไม่เปลี่ยนรูป! ฉันใช้มันทุกที่ที่เป็นไปได้

22

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

ใน C ++ การโยนข้อยกเว้นจากตัวสร้างเป็นความคิดที่ไม่ดีเพราะมันทำให้คุณมีหน่วยความจำที่จัดสรรไว้ซึ่งมีวัตถุที่ไม่มีการกำหนดค่าเริ่มต้นซึ่งคุณไม่มีการอ้างอิงถึง (เช่นเป็นหน่วยความจำแบบคลาสสิกรั่วไหล) นั่นอาจเป็นสิ่งที่มาจากความอัปยศ - กลุ่มนักพัฒนา C ++ โรงเรียนเก่าได้ทำการเรียนรู้ C # ครึ่งหนึ่งและนำสิ่งที่พวกเขารู้จาก C ++ มาใช้ ในทางตรงกันข้ามใน Objective-C Apple ได้แยกขั้นตอนการจัดสรรออกจากขั้นตอนการเริ่มต้นดังนั้นตัวสร้างในภาษานั้นสามารถโยนข้อยกเว้นได้

C # ไม่สามารถรั่วไหลของหน่วยความจำจากการโทรไม่สำเร็จไปยังตัวสร้าง แม้แต่บางคลาสใน. NET Framework ก็จะมีข้อยกเว้นในตัวสร้าง


คุณจะได้นัวเนียขนกับคุณแสดงความคิดเห็น C ++ :)
ChaosPandion

5
ehh, C ++ เป็นภาษาที่ดี แต่หนึ่ง peeves สัตว์เลี้ยงของฉันเป็นคนที่เรียนรู้ภาษาหนึ่งเดียวแล้วเขียนเช่นนั้นตลอดเวลา C # ไม่ใช่ C ++
Ant

10
มันอาจเป็นอันตรายหากทิ้งข้อยกเว้นจาก ctor ใน C # เนื่องจาก ctor อาจจัดสรรทรัพยากรที่ไม่มีการจัดการซึ่งจะไม่ถูกกำจัด และต้องมีการเขียน finalizer เพื่อป้องกันสถานการณ์ที่มีการสร้างวัตถุที่ไม่สมบูรณ์ แต่สถานการณ์เหล่านี้ค่อนข้างหายาก บทความที่จะมาถึงใน C # Dev Center จะครอบคลุมสถานการณ์เหล่านี้และวิธีการป้องกันโค้ดรอบตัวดังนั้นโปรดดูพื้นที่นั้นเพื่อดูรายละเอียด
Eric Lippert

6
ใช่มั้ย? การขว้างปายกเว้นจาก ctor คือสิ่งเดียวที่คุณสามารถทำใน c ++ ถ้าคุณไม่สามารถสร้างวัตถุและไม่มีเหตุผลนี้มีที่จะทำให้เกิดการรั่วไหลของหน่วยความจำ ( แต่เห็นได้ชัดว่ามันขึ้นอยู่กับ)
jk

@jk: อืมคุณพูดถูก! แม้ว่ามันจะเป็นทางเลือกอื่นในการติดธงวัตถุที่ไม่ดีในฐานะ "ซอมบี้" ซึ่ง STL เห็นได้ชัดว่าแทนที่การโยนข้อยกเว้น: yosefk.com/c++fqa/exceptions.html#fqa-17.2 การแก้ปัญหาของ Objective-C มีความเป็นระเบียบมาก
Ant

12

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

ในไม่ช้าทุกคนทำมัน เมื่อวันก่อนฉันทำมันเพื่อวัตถุที่แคบมาก ๆ สักวันบนถนนฉันหรือคนอื่นอาจจะจ่ายราคาสำหรับสลิปในทางปฏิบัติ

ฉันควรทราบว่าโดย "นวกรรมิก" ฉันหมายถึงสิ่งที่ลูกค้าเรียกเพื่อสร้างวัตถุ นั่นอาจเป็นสิ่งอื่นที่ไม่ใช่โครงสร้างที่เกิดขึ้นจริงโดยใช้ชื่อว่า "Constructor" ตัวอย่างเช่นสิ่งนี้ใน C ++ จะไม่ละเมิดหลักการ IMNSHO:

struct funky_object
{
  ...
private:
  funky_object();
  bool initialize(std::string);

  friend boost::optional<funky_object> build_funky(std::string);
};
boost::optional<funky_object> build_funky(std::string str)
{
  funky_object fo;
  if (fo.initialize(str)) return fo;
  return boost::optional<funky_object>();
}

เนื่องจากวิธีเดียวในการสร้าง a funky_objectคือการเรียกbuild_funkyใช้หลักการของการไม่อนุญาตให้วัตถุที่ไม่ถูกต้องยังคงอยู่ถึงแม้ว่า "Constructor" ที่แท้จริงจะไม่ทำงานให้เสร็จ

นั่นเป็นงานพิเศษมากมาย แต่เพื่อผลประโยชน์ที่น่าสงสัย ฉันยังต้องการเส้นทางยกเว้น


9

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

public class MyClass
{
    private MyClass(string text)
    {
        //normal construction
    }

    public static MyClass MakeMyClass(string text)
    {
        if (String.IsNullOrEmpty(text))
            return null;
        else
            return new MyClass(text);
    }
}
public class CallingClass
{
    public MyClass MakeMyClass(string text)
    {
        var cls = MyClass.MakeMyClass(text);
        if(cls == null)
             //show messagebox or throw exception
        return cls;
    }
}

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


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

เราไม่เคยยอมรับเลยว่าข้อยกเว้นนั้นยอดเยี่ยมกว่าการส่งคืนรหัสพวกเขาสามารถแสดงได้อย่างมีประสิทธิภาพหากถูกโยนบ่อยเกินไป อย่างไรก็ตามการโยนข้อยกเว้นจากตัวสร้างจะทำให้หน่วยความจำรั่วแม้กระทั่งใน. NET หากคุณได้จัดสรรบางสิ่งที่ไม่มีการจัดการ คุณต้องทำงานเป็นจำนวนมากในการเข้ารอบเพื่อทำกรณีนี้ อย่างไรก็ตามโรงงานเป็นความคิดที่ดีที่นี่และ TBH ควรมีข้อยกเว้นแทนที่จะส่งคืนค่าว่าง สมมติว่าคุณสร้างคลาสที่ 'ไม่ดี' เพียงไม่กี่คลาสจากนั้นทำให้การขว้างมาจากโรงงานนั้นดีกว่า
gbjbaanb

2
  • ตัวสร้างไม่ควรมีผลข้างเคียงใด ๆ
    • สิ่งใดที่มากกว่าการกำหนดค่าเริ่มต้นของฟิลด์ส่วนตัวควรถูกมองว่าเป็นผลข้างเคียง
    • ตัวสร้างที่มีผลข้างเคียงจะทำลาย Single-Responsibilty-Principle (SRP) และทำงานตรงกันข้ามกับจิตวิญญาณของการเขียนโปรแกรมเชิงวัตถุ (OOP)
  • คอนสตรัคควรเบาและไม่ควรล้มเหลว
    • ตัวอย่างเช่นฉันมักจะสั่นเมื่อเห็นบล็อกแบบลองจับภายในตัวสร้าง ตัวสร้างไม่ควรโยนข้อยกเว้นหรือบันทึกข้อผิดพลาด

เราอาจตั้งคำถามกับแนวทางเหล่านี้อย่างสมเหตุสมผลแล้วพูดว่า "แต่ฉันไม่ปฏิบัติตามกฎเหล่านี้และรหัสของฉันก็ใช้ได้ดี!" เพื่อที่ฉันจะตอบว่า "ดีที่อาจเป็นจริงจนกว่าจะไม่"

  • ข้อยกเว้นและข้อผิดพลาดภายในตัวสร้างไม่คาดคิดมาก หากพวกเขาไม่ได้รับคำสั่งให้ทำเช่นนั้นโปรแกรมเมอร์ในอนาคตจะไม่ถูกล้อมรอบการเรียกใช้ตัวสร้างเหล่านี้ด้วยรหัสป้องกัน
  • หากมีสิ่งใดล้มเหลวในการผลิตการติดตามสแต็กที่สร้างขึ้นอาจเป็นการยากที่จะแยกวิเคราะห์ ด้านบนของการติดตามสแต็กอาจชี้ไปที่การเรียกใช้ตัวสร้าง แต่มีหลายสิ่งเกิดขึ้นในตัวสร้างและอาจไม่ชี้ไปที่ LOC จริงที่ล้มเหลว
    • ฉันได้แยกวิเคราะห์ร่องรอยสแต็ค. NET จำนวนมากซึ่งกรณีนี้เกิดขึ้น

0

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

ดังนั้นมันขึ้นอยู่กับว่าข้อความพารามิเตอร์ที่สำคัญคือวิธีการ - วัตถุทำให้รู้สึกกับค่า Null หรือเปล่า?


2
ฉันคิดว่าคำถามหมายความว่าชั้นเรียนต้องการสตริงที่ไม่ว่างเปล่า นั่นไม่ใช่กรณีในตัวอย่างของ StringBuilder
Sergio Acosta

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

0

ความชอบของฉันคือการตั้งค่าเริ่มต้น แต่ฉันรู้ว่า Java มีห้องสมุด "Apache Commons" ที่ทำสิ่งนี้และฉันคิดว่านั่นเป็นความคิดที่ดีเช่นกัน ฉันไม่เห็นปัญหาในการขว้างข้อยกเว้นหากค่าที่ไม่ถูกต้องจะทำให้วัตถุอยู่ในสถานะที่ใช้ไม่ได้ สตริงไม่ใช่ตัวอย่างที่ดี แต่ถ้าเป็นเพราะ DI ของคนจน คุณไม่สามารถใช้งานได้หากมีการnullส่งผ่านค่าแทนที่ICustomerRepositoryส่วนติดต่อ ในสถานการณ์เช่นนี้การโยนข้อยกเว้นเป็นวิธีที่ถูกต้องในการจัดการสิ่งต่าง ๆ ฉันจะบอกว่า


-1

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

แต่ถ้าคุณอยู่ใน c # / Java แสดงว่าคุณไม่มีปัญหานี้เนื่องจากตัวรวบรวมขยะจะรวบรวมหน่วยความจำ หากคุณใช้ C # ฉันคิดว่าการใช้คุณสมบัตินี้เป็นวิธีที่ดีและสอดคล้องกันเพื่อให้แน่ใจว่ามีการรับประกันข้อ จำกัด ของข้อมูล

public class MyClass
{
    private string _text;
    public string Text 
    {
        get
        {
            return _text;
        } 
        set
        {
            if (string.IsNullOrWhiteSpace(value))
                throw new ArgumentException("Text cannot be empty");
            _text = value;
        } 
    }

    public MyClass(string text)
    {
        Text = text;
        // continue with normal construction
    }
}

-3

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

1 - สร้างอินสแตนซ์

2 - เรียกวิธีการเตรียมใช้งานด้วยผลลัพธ์ส่งคืน

หรือ

เรียกเมธอด / ฟังก์ชันที่สร้างคลาสให้กับคุณที่สามารถทำการตรวจสอบได้

หรือ

มีธง isValid ซึ่งฉันไม่ชอบเป็นพิเศษ แต่บางคนก็ทำ


3
@Dunk นั่นเป็นเพียงผิด
CaffGeek

4
@Chad นั่นคือความคิดเห็นของคุณ แต่ความคิดเห็นของฉันไม่ผิด มันแตกต่างจากของคุณ ฉันพบว่าโค้ดที่อ่านได้ยากจะอ่านได้ยากขึ้นและฉันเห็นข้อผิดพลาดที่สร้างขึ้นมากเมื่อผู้คนใช้ try-catch ในการจัดการข้อผิดพลาดเล็กน้อยกว่าวิธีดั้งเดิมมากกว่า นั่นเป็นประสบการณ์ของฉันและมันก็ไม่ผิด มันเป็นสิ่งที่มันเป็น.
Dunk

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

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

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