เหตุใดการจัดการข้อยกเว้นจึงไม่ดี


93

ภาษา Go ของ Google ไม่มีข้อยกเว้นเป็นตัวเลือกในการออกแบบและ Linus of Linux ชื่อเสียงเรียกว่าข้อยกเว้น ทำไม?


2
ผู้สร้าง ZeroMQ เขียนเกี่ยวกับวิธีที่เขาคิดว่ามันเป็นความผิดพลาดที่จะเขียนใน C ++ (ส่วนใหญ่เกิดจากการจัดการข้อผิดพลาด) 250bpm.com/blog:4
serbaut

Go อาจไม่มีข้อยกเว้น แต่มี "ความตื่นตระหนก" ที่คุณสามารถ "กู้คืน" ได้ (ในขณะที่คำสั่งรอการตัดบัญชียังคงดำเนินการอยู่) และให้ขั้นตอนการควบคุมที่ไม่ใช่ในพื้นที่ ...
Kerrek SB

นี่เป็นบทความที่ดีlightra.com/papers/exceptionsเป็นอันตราย (การจัดการข้อยกเว้นถือว่าเป็นอันตราย)
masterxilo

Afaics สามารถจำลองข้อยกเว้นได้ใน Go with สำเร็จรูปที่มีนัยสำคัญแม้ว่าจุดนี้อาจมีความหมายมากกว่าสำหรับการคายน้ำตาลจากไวยากรณ์มากกว่าการเขียนแบบสำเร็จรูปด้วยตนเอง
Shelby Moore III

คำตอบ:


83

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

ลองพิจารณาสิ่งนี้เป็นตัวอย่างง่ายๆ:

class Frobber
{
    int m_NumberOfFrobs;
    FrobManager m_FrobManager;

public:
    void Frob()
    {
        m_NumberOfFrobs++;

        m_FrobManager.HandleFrob(new FrobObject());
    }
};

สมมติว่าFrobManagerจะนี้รูปลักษณ์ที่ตกลงใช่มั้ย? หรืออาจจะไม่ ... ลองนึกดูว่าถ้าอย่างใดอย่างหนึ่งหรือมีข้อยกเว้น ในตัวอย่างนี้การเพิ่มขึ้นของจะไม่ย้อนกลับ ดังนั้นใครก็ตามที่ใช้อินสแตนซ์นี้จะมีวัตถุที่เสียหายdeleteFrobObjectFrobManager::HandleFrob()operator newm_NumberOfFrobsFrobber

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

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

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

หนังสือทั้งเล่มเขียนเกี่ยวกับการเข้ารหัสที่ปลอดภัยใน C ++ ผู้เชี่ยวชาญหลายคนเข้าใจผิด ถ้ามันซับซ้อนและมีความแตกต่างมากมายนั่นอาจเป็นสัญญาณที่ดีที่คุณต้องเพิกเฉยต่อคุณสมบัตินั้น :-)


9
คำตอบที่น่าสนใจอย่างไรก็ตามมันไม่ได้สะท้อนถึงประสบการณ์การเขียนโปรแกรมของฉันเลย ดังนั้นฉันเดาว่าอาจเป็นปัญหาเฉพาะใน Java หรือ C ++ มากกว่าพูด Python) หรือเฉพาะโดเมน
ddaa

42
ข้อยกเว้นที่เขียนด้วยภาษาที่มีการจัดการโดยใช้รูปแบบ try-catch-ในที่สุดไม่ควรปล่อยให้สถานะไม่ถูกต้องหากเขียนอย่างถูกต้อง เนื่องจากในที่สุดบล็อกได้รับการรับรองว่าจะดำเนินการได้จึงสามารถยกเลิกการจัดสรรวัตถุได้ที่นั่น ส่วนที่เหลือควรได้รับการดูแลโดยตัวแปรที่อยู่นอกขอบเขตและการเก็บขยะ
Robert Harvey

7
@ddaa ปัญหาเป็นไปได้แน่นอนใน Python ผลลัพธ์ที่ได้มักจะทำให้เกิดข้อผิดพลาดได้ยาก บางทีคุณอาจจะพิถีพิถันเป็นพิเศษหรือโชคดี แต่คุณคิดถูกแล้วที่เป็นปัญหาใน C ++ มากกว่าซึ่งข้อบกพร่องที่พบบ่อยที่สุดจาก EH ที่ไม่ดีคือการรั่วไหลของหน่วยความจำ ฉันพยายามย้ำว่าการรั่วไหลไม่ใช่ปัญหาที่ร้ายแรงที่สุด @Robert GC จะลดการรั่วไหลของหน่วยความจำ แต่ฉันไม่แน่ใจว่าโค้ดที่ได้รับการจัดการจะช่วยให้คุณปราศจากข้อผิดพลาดของโปรแกรมเมอร์ โดยเฉพาะอย่างยิ่งหากใครบางคนไม่ให้ความสำคัญกับความปลอดภัยยกเว้นเพราะพวกเขาไม่คิดว่ามันเป็นปัญหาในภาษาของพวกเขานั่นไม่ใช่สัญญาณที่ดี
asveikau

4
@lzprgmr มีข้อยกเว้นให้คุณจัดการกับความแตกต่าง ประเภทของข้อผิดพลาดที่แตกต่างกัน สถานที่ในรหัส การจัดการกับข้อผิดพลาดในการเชื่อมต่ออาจจำเป็นต้องมีการเชื่อมต่อใหม่ แต่ไม่ใช่ระหว่างฟังก์ชันที่ซ้อนกันอยู่ คุณต้องการทำฟองนั้นขึ้นอยู่กับตัวจัดการการเชื่อมต่อหรืออะไรบางอย่าง จากนั้นการจัดการกับค่าที่ส่งคืนจะบังคับให้คุณตรวจสอบข้อผิดพลาดในการโทรแต่ละครั้งและทำฟองนั้นด้วยตนเอง (ในกรณีของข้อผิดพลาดในการรีเซ็ตการเชื่อมต่อเช่น) นอกจากนี้ค่าผลตอบแทนกองขึ้นในการเรียกซ้อนกัน: func3 สามารถกลับ -1 func2 เรียก func3 ผลตอบแทน -2 ข้อผิดพลาดของเขา -1 func3 ของ ฯลฯ ..
abourget

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

50

เหตุผลที่ Go ไม่มีข้อยกเว้นอธิบายไว้ในคำถามที่พบบ่อยเกี่ยวกับการออกแบบภาษา Go:

ข้อยกเว้นเป็นเรื่องราวที่คล้ายกัน มีการเสนอแบบจำลองสำหรับข้อยกเว้นจำนวนมาก แต่แต่ละแบบจะเพิ่มความซับซ้อนให้กับภาษาและเวลาทำงาน โดยธรรมชาติของพวกเขาข้อยกเว้นช่วงการทำงานและบางทีแม้แต่ goroutines พวกเขามีผลกระทบมากมาย นอกจากนี้ยังมีความกังวลเกี่ยวกับผลกระทบที่จะมีต่อห้องสมุด ตามคำจำกัดความแล้วยังมีประสบการณ์ที่ยอดเยี่ยมกับภาษาอื่น ๆ ที่สนับสนุนแสดงว่ามีผลอย่างมากต่อข้อกำหนดของไลบรารีและอินเตอร์เฟส คงจะดีไม่น้อยหากพบการออกแบบที่ช่วยให้พวกเขามีความพิเศษอย่างแท้จริงโดยไม่ต้องกระตุ้นให้ข้อผิดพลาดทั่วไปกลายเป็นขั้นตอนการควบคุมพิเศษที่ต้องให้โปรแกรมเมอร์ทุกคนชดเชย

เช่นเดียวกับยาชื่อสามัญข้อยกเว้นยังคงเป็นปัญหาที่เปิดเผย

กล่าวอีกนัยหนึ่งก็คือพวกเขายังไม่ได้หาวิธีรองรับข้อยกเว้นใน Go ในแบบที่พวกเขาคิดว่าน่าพอใจ พวกเขาไม่ได้บอกว่าข้อยกเว้นนั้นไม่ดีต่อตัวเอง

อัพเดท - พ.ค. 2555

ตอนนี้นักออกแบบ Go ได้ปีนลงจากรั้วแล้ว คำถามที่พบบ่อยของพวกเขาตอนนี้กล่าวว่า:

เราเชื่อว่าข้อยกเว้นการมีเพศสัมพันธ์กับโครงสร้างการควบคุมเช่นเดียวกับในสำนวน try-catch-ในที่สุดส่งผลให้เกิดรหัสที่ซับซ้อน นอกจากนี้ยังมีแนวโน้มที่จะสนับสนุนให้โปรแกรมเมอร์ติดป้ายกำกับข้อผิดพลาดทั่วไปมากเกินไปเช่นไม่สามารถเปิดไฟล์ได้เป็นพิเศษ

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

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

ดูบทความ Defer, Panic และ Recover สำหรับรายละเอียด

ดังนั้นคำตอบสั้น ๆ ก็คือพวกเขาสามารถทำได้แตกต่างกันโดยใช้การคืนค่าหลายค่า (และพวกเขาก็มีรูปแบบของการจัดการข้อยกเว้นอยู่ดี)


... และ Linus แห่งชื่อเสียงของ Linux เรียกข้อยกเว้นว่าอึ

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

"สิ่งที่จัดการข้อยกเว้น C ++ ทั้งหมดเสียโดยพื้นฐานโดยเฉพาะอย่างยิ่งสำหรับเมล็ดพืช"

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

ข้อสรุปของฉันคือ Linus ไม่ได้เรียกข้อยกเว้น (โดยทั่วไป) ว่า "อึ" เลย!


30
ฉันเกลียดเวลาที่พวกเขาซ่อนข้อมูลโดยใส่ไว้ใน FAQ :)
brian d foy

7
โปรดทราบว่า Linus ขึ้นต้นอีเมลด้วย "C ++ เป็นภาษาที่น่ากลัว" และยังคงคุยโวว่าเขาเกลียดทั้ง C ++ และคนที่เลือกโปรแกรมในนั้นมากแค่ไหน ดังนั้นฉันไม่คิดว่าความคิดเห็นของเขาเกี่ยวกับข้อยกเว้นของ C ++ สามารถถือว่าเชื่อถือได้เนื่องจากความคิดเห็นที่ค่อนข้างเอนเอียงของเขาต่อ C ++
Pharap

1
@ Hi-Angel - ดังข้อความที่ฉันยกมากล่าวว่า: "สำหรับการจัดการข้อผิดพลาดธรรมดาการส่งคืนหลายค่าของ Go ทำให้ง่ายต่อการรายงานข้อผิดพลาดโดยไม่ทำให้ค่าส่งคืนมากเกินไป" . นั่นเป็นวิธีที่เกี่ยวข้องกัน ไม่ว่าจะด้วยวิธีใดฉันก็อ้างเหตุผลของนักออกแบบ Go หากคุณต้องการโต้แย้งโต้แย้งกับ >> พวกเขา <<
Stephen C

1
@ Hi-Angel - ฉันไม่ได้เขียนย่อหน้าเหล่านั้น ฉันแค่ยกมา หากคุณมีปัญหากับพวกเขาคุณควรปรึกษาผู้เขียน ในทางทฤษฎีฉันสามารถให้การสอนเกี่ยวกับภาษา Go เป็นบริบทได้ แต่พูดตรงไปตรงมาผู้ที่ไม่เข้าใจคำศัพท์ Go สามารถเยี่ยมชมเว็บไซต์ Go เพื่อค้นหาความหมายทั้งหมด
Stephen C

1
".. ได้ผลลัพธ์เป็นรหัสที่ซับซ้อน" เมื่อนานมาแล้วฉันเคยเขียนโปรแกรม C ด้วยการทำงานของสตริงหลาย ๆ ทุกวิธีคือการจัดสรรหน่วยความจำจากนั้นตรวจสอบว่าการจัดสรรประสบความสำเร็จหรือไม่ ดูเหมือนว่าโค้ดส่วนใหญ่เป็นเพียงการตรวจสอบการจัดสรร มันไม่ซับซ้อน? C ++ ที่มีข้อยกเว้นช่วยฉันได้มาก
robsosno

30

ข้อยกเว้นไม่ใช่เรื่องเลวร้าย แต่ถ้าคุณรู้ว่ามันจะเกิดขึ้นมากมายมันอาจมีราคาแพงในแง่ของประสิทธิภาพ

หลักทั่วไปคือข้อยกเว้นควรตั้งค่าสถานะเงื่อนไขพิเศษและคุณไม่ควรใช้เพื่อควบคุมผังงานโปรแกรม


9
@ โรเบิร์ต: "คุณไม่ควรใช้มันเพื่อควบคุมผังรายการ" ฉันไม่ได้คิดแบบนี้มุมมองใหม่สำหรับฉัน: P +1
okw

2
นอกจากนี้ยังขึ้นอยู่กับภาษาจริงๆ เป็นการยากที่จะหลีกเลี่ยงข้อยกเว้นหากคุณกำลังเขียนโปรแกรมใน Java
Charles Salvia

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

6
พวกเขาไม่ต้องเสียค่าใช้จ่ายมาก ตัวอย่างเช่นคุณสามารถใช้ "ลอง" ที่เสียเวลาดำเนินการเป็นศูนย์และให้ "โยน" ค้นหาตัวจัดการข้อยกเว้นในตารางตามที่อยู่ผู้โทรที่เห็นบนสแต็ก ... ฉันจะบอกว่าเหตุผลที่ใหญ่ที่สุดที่จะไม่ ข้อยกเว้นการใช้งานไม่เกี่ยวข้องกับประสิทธิภาพเลย
asveikau

Rephrased; คำถามบอกใบ้อย่างชัดเจนในการใช้ข้อยกเว้นโดยทั่วไปหรือไม่ใช้ข้อยกเว้นเลย (เป็นเรื่องไร้สาระหรือไม่มีแม้แต่ในภาษา) คำตอบของคุณจะแสดงเฉพาะสาเหตุที่ข้อยกเว้นไม่ดีต่อประสิทธิภาพเมื่อใช้สำหรับโฟลว์การควบคุมโปรแกรม
Maarten Bodewes

27

ฉันไม่เห็นด้วยกับ "โยนข้อยกเว้นในสถานการณ์พิเศษเท่านั้น" แม้ว่าโดยทั่วไปจะเป็นความจริง แต่ก็ทำให้เข้าใจผิด ข้อยกเว้นสำหรับข้อผิดพลาดเงื่อนไข (การดำเนินการล้มเหลว)

ไม่ว่าคุณจะใช้ภาษาใดให้เลือกสำเนาของFramework Design Guidelines : Conventions, Idioms, and Patterns for Reusable .NET Libraries (2nd Edition) บทที่ว่าด้วยการโยนข้อยกเว้นจะไม่มีเพื่อน คำพูดบางส่วนจากฉบับพิมพ์ครั้งแรก (ฉบับที่ 2 ในที่ทำงานของฉัน):

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

มีหน้าหมายเหตุเกี่ยวกับประโยชน์ของข้อยกเว้น (ความสอดคล้องของ API, การเลือกตำแหน่งของรหัสการจัดการข้อผิดพลาด, ความทนทานที่ดีขึ้น ฯลฯ ) มีส่วนเกี่ยวกับประสิทธิภาพที่มีหลายรูปแบบ (Tester-Doer, Try-Parse)

ข้อยกเว้นและการจัดการข้อยกเว้นไม่เลว เช่นเดียวกับคุณสมบัติอื่น ๆ พวกเขาสามารถใช้ในทางที่ผิดได้


4
ฉันต้องไม่เห็นด้วยกับเรื่องนั้น ฉันไม่ได้ต่อต้านข้อยกเว้นและหนังสือเล่มนั้นเป็นสิ่งที่ต้องมีอย่างไรก็ตามมันมีอคติต่อการพัฒนา. NET และ C #

3
ฉันรู้ว่านี่เป็นเรื่องโบราณเพียงแค่ต้องการแสดงความคิดเห็นว่าดูเหมือนว่าจะมีความไม่ลงรอยกันระหว่างประเภท. NET และประเภท * nix ไลบรารีทั้งหมดที่ฉันใช้เป็น Linux dev ใช้โค้ดส่งคืนและคำแนะนำสไตล์ * nix ที่ฉันอ่าน (เช่น บริษัท ของฉันและGoogleเป็นต้น) เพียงแค่พูดว่า "เราไม่ทำข้อยกเว้น" แค่คิดว่ามันน่าสนใจ
jarvisteve

1
เฟรมเวิร์กควรปฏิบัติกับข้อยกเว้นที่แตกต่างจากแอปพลิเคชันของผู้ใช้ปลายทาง เฟรมเวิร์กไม่มีวิธีจัดการกับข้อผิดพลาดนอกเหนือจากการโยนข้อยกเว้นแอปพลิเคชันของผู้บริโภคทำ
0x6C38

12

จากมุมมองของ golang ฉันเดาว่าไม่มีการจัดการข้อยกเว้นทำให้กระบวนการรวบรวมง่ายและปลอดภัย

จากมุมมองของ Linus ฉันเข้าใจว่ารหัสเคอร์เนลนั้นเกี่ยวกับกรณีมุม ดังนั้นจึงสมเหตุสมผลที่จะปฏิเสธข้อยกเว้น

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

ตัวอย่างเช่นใช้ได้ดีในโค้ดระดับสูงส่วนใหญ่ที่ผู้ใช้หันหน้าเข้าหาเช่นโค้ดแอปพลิเคชันบนเว็บและเดสก์ท็อป


แต่สิ่งที่เป็นจริงสำหรับโค้ดเคอร์เนลก็เป็นจริงเช่นกันสำหรับกระบวนการเซิร์ฟเวอร์เนทีฟที่รันมานาน
Lothar

11

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

  1. อย่าใช้ข้อยกเว้นในการควบคุมโฟลว์โปรแกรมนั่นคืออย่าพึ่งพาคำสั่ง "catch" เพื่อเปลี่ยนโฟลว์ของตรรกะ สิ่งนี้ไม่เพียง แต่จะซ่อนรายละเอียดต่างๆรอบ ๆ ตรรกะเท่านั้น แต่ยังสามารถนำไปสู่ประสิทธิภาพที่ไม่ดีได้อีกด้วย
  2. อย่าโยนข้อยกเว้นจากภายในฟังก์ชันเมื่อ "สถานะ" ที่ส่งคืนจะมีความหมายมากขึ้นให้โยนข้อยกเว้นในสถานการณ์พิเศษเท่านั้น การสร้างข้อยกเว้นเป็นการดำเนินการที่มีราคาแพงและต้องใช้ประสิทธิภาพสูง ตัวอย่างเช่นหากคุณเรียกใช้เมธอดเพื่อเปิดไฟล์และไฟล์นั้นไม่มีอยู่ให้โยนข้อยกเว้น "FileNotFound" หากคุณเรียกใช้เมธอดที่กำหนดว่ามีบัญชีลูกค้าหรือไม่ให้ส่งคืนค่าบูลีนอย่าส่งคืนข้อยกเว้น "CustomerNotFound"
  3. เมื่อพิจารณาว่าจะจัดการกับข้อยกเว้นหรือไม่อย่าใช้ประโยค "try ... catch" เว้นแต่คุณจะทำสิ่งที่เป็นประโยชน์ได้โดยมีข้อยกเว้น หากคุณไม่สามารถจัดการกับข้อยกเว้นได้คุณควรปล่อยให้เกิดฟองในกลุ่มการโทร มิฉะนั้นตัวจัดการอาจ "กลืน" ข้อยกเว้นและรายละเอียดจะสูญหายไป (เว้นแต่คุณจะเปลี่ยนข้อยกเว้นใหม่)

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

3
แต่ถ้า GetCustomer แสดงข้อยกเว้นแทนที่จะส่งคืนค่าว่างรหัสไคลเอ็นต์ยังคงต้องจัดการกับข้อยกเว้น ไม่ว่าจะโดยการตรวจสอบค่าว่างหรือโดยการจัดการข้อยกเว้นความรับผิดชอบจะอยู่ที่รหัสไคลเอนต์ - หากไม่ทำสิ่งที่ถูกต้องไม่ว่าจะด้วยวิธีใดไม่ช้าก็เร็วบางสิ่งจะระเบิด
คริส

1
@TrueWill Languages ​​ที่รองรับเทมเพลต / generics แก้ปัญหานี้ได้โดยส่งคืนOption<T>แทนnullในปัจจุบัน เพิ่งได้รับการแนะนำใน Java 8 เช่นรับคำแนะนำจาก Guava (และอื่น ๆ )
Maarten Bodewes

@owlstead ครับ. น่ารักมาก นี่เป็นวิธีที่ดีหากภาษาของคุณรองรับและมีการจับคู่รูปแบบ
TrueWill

@owlstead อีกครั้งสิ่งที่คริสพูดยังคงแข็งแกร่งสำหรับสิ่งนั้น - ผู้ใช้ต้องจำไว้ว่าให้เรียกใช้ฟังก์ชัน. orElse (defaultObject) หรือสำนวนใดก็ตามที่ภาษาที่เป็นปัญหาได้ตัดสินใจ ในตอนท้ายของวันในที่สุดโปรแกรมเมอร์ที่เป็นปัญหาแทนที่จะเป็นวิธีการจัดการข้อผิดพลาด
Pharap

9

อาร์กิวเมนต์ทั่วไปคือไม่มีทางที่จะบอกได้ว่าข้อยกเว้นใดบ้างที่จะออกมาจากโค้ดบางส่วน (ขึ้นอยู่กับภาษา) และมีลักษณะคล้ายกับgotos มากเกินไปทำให้ยากต่อการติดตามการดำเนินการทางจิตใจ

http://www.joelonsoftware.com/items/2003/10/13.html

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


1
รหัส C มีข้อยกเว้นในรูปแบบที่แตกต่างกัน คุณต้องปิดทุกการโทรไปยังฟังก์ชันที่ไม่สำคัญใน ifs ซึ่งทำให้การใช้ภาษานั้นปวดหัว!
RCIX

1
นอกจากนี้ยังมีsetjmp/ ของlongjmpซึ่งค่อนข้างแย่
Tim Sylvester

9
คุณต้องการรับคำแนะนำของผู้ชายที่ให้ความสำคัญกับโปรแกรมเมอร์ Duct Tape และใครไม่เชื่อว่าการทดสอบหน่วยเป็นสิ่งที่จำเป็นหรือไม่? joelonsoftware.com/items/2009/09/23.html
TrueWill

4
นี่เป็นความผิดพลาดแบบคลาสสิก (หรือโกง) ในการอภิปรายที่การโต้แย้งในเรื่องนี้ถูกแทนที่ด้วยการอ้างอิงบุคลิกภาพ นั่นมักเป็นสัญญาณของการอภิปรายที่เสื่อมถอย
Petr Gladkikh

1
@PetrGladkikh การอภิปรายเริ่มต้นด้วย OP ที่อ้างถึงความคิดเห็นของ Linus ... การโกงนั้นเรียกว่าการผิดพลาดของการอุทธรณ์ต่อผู้มีอำนาจ การสนทนาสามารถดำเนินต่อไปได้จากตรงนั้นและไม่ใช่เรื่อง "โกง" ที่จะตอบคำถามว่าทำไม Linus ไม่ชอบข้อยกเว้นโดยอ้างถึงบุคลิกของเขา
Jim Balter

7

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


7
RAII มีประโยชน์แม้ไม่มีข้อยกเว้น
Mark Ransom

4
อย่างไรก็ตามข้อยกเว้นจะไม่มีประโยชน์หากไม่มี RAII (หรือการจัดการทรัพยากรอัตโนมัติอื่น ๆ )
Greg Rogers

+1 สำหรับการชี้ให้เห็นสถานการณ์ที่ข้อยกเว้นไม่เหมาะสมและข้อยกเว้นนั้นไม่เลวร้ายโดยเนื้อแท้
Pharap

4

กรณีการใช้งานที่ดีสำหรับข้อยกเว้นคือ ....

สมมติว่าคุณอยู่ในโปรเจ็กต์และคอนโทรลเลอร์ทุกตัว (ประมาณ 20 ตัวควบคุมหลักที่แตกต่างกัน) ขยายคอนโทรลเลอร์ซูเปอร์คลาสตัวเดียวด้วยวิธีการดำเนินการ จากนั้นคอนโทรลเลอร์ทุกตัวจะทำสิ่งต่าง ๆ ที่แตกต่างจากกันที่เรียกอ็อบเจ็กต์ B, C, D ในกรณีเดียวและ F, G, D ในอีกกรณีหนึ่ง มีข้อยกเว้นในการช่วยเหลือที่นี่ในหลาย ๆ กรณีที่มีรหัสส่งคืนจำนวนมากและตัวควบคุมทุกตัวก็จัดการแตกต่างกัน ฉันตีรหัสทั้งหมดนั้นโยนข้อยกเว้นที่เหมาะสมจาก "D" จับมันในวิธีการดำเนินการของตัวควบคุมระดับสูงและตอนนี้ตัวควบคุมทั้งหมดของเราสอดคล้องกัน ก่อนหน้านี้ D ส่งคืนค่าว่างสำหรับกรณีข้อผิดพลาดที่แตกต่างกันหลายกรณีที่เราต้องการบอกผู้ใช้ปลายทาง แต่ทำไม่ได้และฉันไม่ได้ '

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

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


1
+1 ได้อย่างง่ายดายหนึ่งในข้อโต้แย้งที่ดีที่สุดสำหรับการใช้ข้อยกเว้นที่ฉันได้อ่าน สามารถใช้ตัวอย่างที่ละเอียดกว่านี้ได้ (เช่นแผนภาพ UML รหัสเทียมบางรหัสหรือรหัสจริง) แต่ประเด็นเกี่ยวกับการทำให้คลาสย่อยดำเนินการอย่างสม่ำเสมอเป็นสิ่งที่ดี นอกจากนี้ข้อเท็จจริงที่หลักฐานของคุณเป็นเพียงเล็กน้อยแสดงให้เห็นว่าข้อยกเว้นมีประโยชน์ในสถานการณ์จริงและประโยชน์คือจุดประสงค์หลักของคุณลักษณะภาษาใด ๆ
Pharap

นอกจากนี้หากคุณอยู่ในสกาลาคุณสามารถคืนค่า Try [ResponseType] ซึ่งแสดงถึงข้อยกเว้นหรือการตอบกลับจริง จากนั้นคุณสามารถทำตามรูปแบบเดียวกับที่ฉันอธิบายไว้ข้างต้นโดยไม่มีข้อยกเว้นที่แท้จริงนอกเหนือจากที่ลองใช้ จากนั้นก็เหมือนกับทุกวิธีที่คุณได้รับผลตอบแทนประเภทการตอบสนอง 1 + n ซึ่งฉันคิดว่าจำเป็น อย่างไรก็ตามในสกาลาส่งคืน Future [response] ซึ่งทำงานคล้ายกับ Try แต่สามารถช่วยในการเขียนโปรแกรมแบบอะซิงโครนัสได้มากขึ้น
Dean Hiller

2

ในทางทฤษฎีพวกเขาแย่จริงๆ ในโลกคณิตศาสตร์ที่สมบูรณ์แบบคุณไม่สามารถรับสถานการณ์ยกเว้นได้ ดูภาษาที่ใช้งานได้พวกเขาไม่มีผลข้างเคียงดังนั้นจึงแทบไม่มีแหล่งที่มาสำหรับสถานการณ์ที่ไม่เป็นที่ยอมรับ

แต่ความเป็นจริงก็เป็นอีกเรื่องหนึ่ง เรามักมีสถานการณ์ที่ "คาดไม่ถึง" เสมอ นี่คือเหตุผลที่เราต้องการข้อยกเว้น

ฉันคิดว่าเราสามารถนึกถึงข้อยกเว้นของน้ำตาลไวยากรณ์สำหรับ ExceptionSituationObserver คุณเพิ่งได้รับการแจ้งเตือนข้อยกเว้น ไม่มีอะไรมาก.

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


2
"ดูภาษาที่ใช้งานได้พวกเขาไม่มีผลข้างเคียงดังนั้นจึงแทบไม่มีที่มาสำหรับสถานการณ์ที่ไม่เป็นที่ยอมรับ" นั่นคือการพูดเกินจริงอย่างสิ้นเชิง
Stephen C

3
5/0 ในคณิตศาสตร์คืออะไร? อาร์คซิน (200)? Sqrt (-1)? คณิตศาสตร์มีสถานการณ์พิเศษมากมาย
Robert Fraser

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

3
@MikeChaliy - ฉันขอโทษ แต่นั่นเป็นเพียงความซับซ้อน คุณสามารถใช้เหตุผลแบบนั้นเพื่อบอกว่าไม่มีสถานการณ์ยกเว้นในทุกกรณี ในความเป็นจริงนิพจน์ทางคณิตศาสตร์ที่ไม่มีความหมาย (หรือไม่มีค่าที่แน่นอน) เป็นสิ่งพิเศษ นี่ไม่ได้หมายความว่าพวกเขาจะต้องจัดการโดยการขว้างปาและจับข้อยกเว้น ... แต่ถ้าคุณไม่ทำเช่นนั้นคุณก็ต้องมีค่าพิเศษ (เช่น Inf และ Nan) หรือการดำเนินการที่ส่งคืนค่าหลายค่า ในระยะสั้นกรณีเหล่านี้จำเป็นต้องได้รับการดูแลเป็นพิเศษ
Stephen C

1
คอมพิวเตอร์เป็นเครื่องจักรของรัฐ ไม่ใช่โลกคณิตศาสตร์ที่สมบูรณ์แบบ
Arunav Sanyal

1

กระบวนทัศน์การจัดการข้อยกเว้นของ C ++ ซึ่งเป็นพื้นฐานบางส่วนสำหรับ Java นั้นและในทางกลับกัน. net ก็แนะนำแนวคิดที่ดีบางประการ แต่ก็มีข้อ จำกัด ที่รุนแรงเช่นกัน ความตั้งใจในการออกแบบที่สำคัญประการหนึ่งของการจัดการข้อยกเว้นคือการอนุญาตให้ใช้วิธีการต่างๆเพื่อให้แน่ใจว่าพวกเขาจะตอบสนองเงื่อนไขหลังการใช้งานหรือยกเลิกข้อยกเว้นและตรวจสอบให้แน่ใจว่าการล้างข้อมูลใด ๆ ที่จำเป็นต้องเกิดขึ้นก่อนที่เมธอดจะสามารถออกได้จะเกิดขึ้น น่าเสียดายที่กระบวนทัศน์การจัดการข้อยกเว้นของ C ++, Java และ. net ทั้งหมดไม่สามารถให้วิธีการที่ดีในการจัดการสถานการณ์ที่ปัจจัยที่ไม่คาดคิดขัดขวางไม่ให้ดำเนินการล้างข้อมูลที่คาดไว้ ในทางกลับกันหมายความว่าเราต้องเสี่ยงที่ทุกอย่างจะหยุดชะงักหากมีสิ่งที่ไม่คาดคิดเกิดขึ้น (วิธีการ C ++ ในการจัดการข้อยกเว้นเกิดขึ้นระหว่างการคลายสแต็ก)

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


1

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


0
  • โดยทั่วไปข้อยกเว้นที่ไม่ได้รับการจัดการนั้นไม่ดี
  • จัดการข้อยกเว้นไม่ดีไม่ดี (แน่นอน)
  • 'ความดี / ความเลว' ของการจัดการข้อยกเว้นขึ้นอยู่กับบริบท / ขอบเขตและความเหมาะสมไม่ใช่เพื่อประโยชน์ในการทำ

0

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

สำหรับการส่งสัญญาณข้อผิดพลาดโดยทั่วไปฉันชอบสัญญาณข้อผิดพลาด ทั้งหมดขึ้นอยู่กับ API กรณีการใช้งานและความรุนแรงหรือหากการบันทึกเพียงพอ นอกจากนี้ฉันกำลังพยายามกำหนดพฤติกรรมใหม่throw Phonebooks()แทน แนวคิดที่ว่า "ข้อยกเว้น" มักจะเป็นทางตัน แต่ "สมุดโทรศัพท์" มีข้อมูลที่เป็นประโยชน์เกี่ยวกับการกู้คืนข้อผิดพลาดหรือเส้นทางการดำเนินการทางเลือกอื่น (ยังไม่พบกรณีการใช้งานที่ดี แต่พยายามต่อไป)


0

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


ปัญหาไม่ได้ (หรือไม่ควร) ว่าโค้ดสามารถล้มเหลวได้หรือไม่ปัญหาที่ใหญ่กว่าคือขอบเขตที่หากรหัสล้มเหลวเราจะใส่ใจกับรายละเอียดนั้น ๆ หากพยายามโหลดเอกสารและวิธีการ "อ่านข้อมูล" วิธีใดวิธีหนึ่งล้มเหลววิธีหนึ่งมักไม่สนใจว่าจะใช้วิธีใดเนื่องจากผลจะเหมือนกันไม่ว่าจะโหลดเอกสารไม่ได้ก็ตาม ตามแนวคิดแล้วการจัดการข้อยกเว้นควรเป็นสิ่งที่ดีสำหรับสิ่งนั้น ปัญหาคือกระบวนทัศน์การจัดการข้อยกเว้นของ. NET และ Java ไม่ได้ให้วิธีที่ดีในการแยกแยะข้อยกเว้น "น่าเบื่อ" ที่ควรรวมเข้าด้วยกันจากที่ไม่ควร
supercat
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.