ตัวแปรข้อผิดพลาดเป็นการต่อต้านรูปแบบหรือการออกแบบที่ดีหรือไม่?


44

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


6
สิ่งนี้try/ catchมีอยู่สำหรับ นอกจากนี้คุณสามารถเพิ่มtry/ catchเพิ่มกองซ้อนของคุณในตำแหน่งที่เหมาะสมยิ่งขึ้นเพื่อจัดการกับมัน (ช่วยให้แยกความกังวลได้มากขึ้น)
jpmc26

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

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

3
@piedar ฉันสร้างคำถามแยกที่สามารถพูดคุยได้อย่างอิสระมากขึ้น: programmers.stackexchange.com/questions/245255/…
Nzall

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

คำตอบ:


65

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

มีข้อดีเล็กน้อยสำหรับการใช้ข้อยกเว้นหากคุณมีตัวเลือก

ข้อความ

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

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

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

ไม่สามารถเรียกนโยบายของชื่อ "foo" สำหรับผู้ใช้ "bar" ซึ่งถูกอ้างอิงในโปรไฟล์ผู้ใช้

เปรียบเทียบสิ่งนี้กับโค้ดส่งคืน -85 คุณจะเลือกอันไหน

โทรสแต็ค

ข้อยกเว้นมักจะมีรายละเอียดการโทรที่ช่วยแก้ไขรหัสได้เร็วขึ้นและเร็วขึ้นและยังสามารถบันทึกได้ด้วยรหัสการโทรหากต้องการ สิ่งนี้ช่วยให้นักพัฒนาสามารถระบุปัญหาได้ตามปกติและมีประสิทธิภาพมาก เปรียบเทียบสิ่งนี้กับไฟล์บันทึกด้วยค่าส่งคืน (เช่น -85, 101, 0, ฯลฯ ) ซึ่งคุณต้องการเลือกอันไหน

ล้มเหลววิธีเอนเอียงที่รวดเร็ว

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

การห่อและแกะข้อยกเว้น

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

ความเรียบง่ายของรหัส

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

ข้อสรุป

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

อย่างไรก็ตามหากเป็น C ++ มันจะเป็นการยากกว่าที่จะกำหนดเนื่องจากมี codebase ขนาดใหญ่อยู่แล้วกับรหัสส่งคืนและนักพัฒนาจำนวนมากจะถูกปรับให้ส่งคืนค่าเมื่อเทียบกับข้อยกเว้น (เช่น Windows มีมากมายกับ HRESULTs) . นอกจากนี้ในหลาย ๆ แอปพลิเคชันอาจเป็นปัญหาด้านประสิทธิภาพด้วย (หรืออย่างน้อยก็รับรู้ได้)


5
Windows ส่งคืนค่า HRESULT จากฟังก์ชั่น C ++ เพื่อรักษาความเข้ากันได้ของ C ใน API สาธารณะ (และเนื่องจากคุณเริ่มเข้าสู่โลกแห่งความเจ็บปวดที่พยายามจะยกเว้นข้อผิดพลาดข้ามเขตแดน) อย่าทำตามรุ่นของระบบปฏิบัติการหากคุณเขียนแอปพลิเคชัน
โคดี้เกรย์

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

2
@TrentonMaki หากคุณกำลังพูดคุยเกี่ยวกับข้อผิดพลาดจาก C ++ ก่อสร้าง, คำตอบที่ดีที่สุดคือที่นี่: parashift.com/c++-faq-lite/ctors-can-throw.html ในระยะสั้นโยนข้อยกเว้น แต่อย่าลืมล้างการรั่วไหลที่อาจเกิดขึ้นก่อน ฉันไม่ทราบว่ามีภาษาอื่นใดที่การขว้างตรงจากคอนสตรัคเตอร์เป็นสิ่งที่ไม่ดี ผู้ใช้ส่วนใหญ่ของ API ฉันคิดว่าต้องการดักจับข้อยกเว้นมากกว่าตรวจสอบรหัสข้อผิดพลาด
Ogre Psalm33

3
"สถานการณ์ขั้นสูงเช่นนั้นไม่ใช่เรื่องง่ายที่จะนำไปใช้กับค่าส่งคืน" แน่นอนคุณสามารถ! สิ่งที่คุณต้องทำคือสร้างErrorStateReturnVariableซุปเปอร์คลาสและคุณสมบัติอย่างหนึ่งของมันคือInnerErrorState(ซึ่งเป็นตัวอย่างของErrorStateReturnVariable) ซึ่งการใช้คลาสย่อยสามารถตั้งค่าให้แสดงข้อผิดพลาดโซ่ ... โอ้รอ : p
Brian S

5
เพียงเพราะภาษารองรับข้อยกเว้นไม่ได้ทำให้พวกเขาเป็นยาครอบจักรวาล ข้อยกเว้นแนะนำเส้นทางการปฏิบัติการที่ซ่อนอยู่และผลกระทบของมันจะต้องมีการควบคุมอย่างเหมาะสม พยายาม / จับง่ายต่อการเพิ่ม แต่การกู้คืนถูกต้องยาก ...
Matthieu M.

21

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

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


1
ขอบคุณสำหรับคำอธิบาย! คำตอบของคุณ + Omer Iqbal ตอบคำถามของฉัน
Mikayla Maki

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

1
@ Cyanfish: ใช่ แต่ต้องระวังไม่ให้มีการออกแบบมากเกินไปโดยเฉพาะเมื่อสร้างห้องสมุด ดีกว่าให้คนหนึ่งที่เรียบง่ายและกลไกการเตือนการทำงานกว่า 2, 3 หรือมากกว่า
Doc Brown

ข้อยกเว้นควรจะโยนเมื่อความล้มเหลวที่ไม่รู้จักหรือสถานการณ์ที่เรียกคืนไม่ได้รับประสบการณ์ - กรณีพิเศษ คุณควรคาดหวังผลกระทบต่อประสิทธิภาพเมื่อสร้างข้อยกเว้น
Gusdor

@Gusdor: อย่างนั้นเป็นเหตุผลที่ "เตือน" โดยทั่วไปควร IMHO ไม่ผิดข้อยกเว้นโดยค่าเริ่มต้น แต่สิ่งนี้ก็ขึ้นอยู่กับระดับของสิ่งที่เป็นนามธรรม บางครั้งบริการหรือไลบรารีก็ไม่สามารถตัดสินใจได้ว่าเหตุการณ์ที่ผิดปกติควรได้รับการปฏิบัติเป็นข้อยกเว้นจากมุมมองของผู้โทร โดยส่วนตัวแล้วในสถานการณ์เช่นนี้ฉันต้องการ lib เพียงเพื่อตั้งตัวบ่งชี้การเตือน (ไม่มีข้อยกเว้น) ให้ผู้ทดสอบโทรไปที่การตั้งค่าสถานะและโยนข้อยกเว้นถ้าเขาคิดว่ามันเหมาะสม นั่นคือสิ่งที่ฉันมีอยู่ในใจเมื่อฉันเขียนข้างต้น "มันจะดีกว่าที่จะให้หนึ่งกลไกการเตือนใน libary"
Doc Brown

20

มีหลายวิธีในการส่งสัญญาณข้อผิดพลาด:

  • ตัวแปรข้อผิดพลาดในการตรวจสอบ: C , Go , ...
  • ข้อยกเว้น: Java , C # , ...
  • ตัวจัดการ "เงื่อนไข": Lisp (เท่านั้น?), ...
  • polymorphic return: Haskell , ML , สนิม , ...

ปัญหาของตัวแปรข้อผิดพลาดคือมันง่ายที่จะลืมตรวจสอบ

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

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

ผลตอบแทน Polymorphic ( Either a bใน Haskell) เป็นคำตอบที่ฉันโปรดปราน:

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

ปัญหาเดียวคือพวกเขาอาจนำไปสู่การตรวจสอบมากเกินไป; ภาษาที่ใช้พวกมันมีสำนวนที่เชื่อมโยงการเรียกใช้ฟังก์ชั่นที่ใช้พวกมันเข้าด้วยกัน แต่มันอาจจะต้องใช้การพิมพ์ / ถ่วงอีก ใน Haskell นี้จะmonads ; แต่นี้อยู่ไกลน่ากลัวกว่าที่มันฟังดูรถไฟ Oriented Programming


1
คำตอบที่ดี! ฉันหวังว่าฉันจะได้รับรายการวิธีจัดการข้อผิดพลาด
Mikayla Maki

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

1
@Dunk: ฉันไม่คิดว่าคำตอบของฉันทำให้คำขอโทษของข้อยกเว้นทั้ง; แม้ว่ามันอาจจะขึ้นอยู่กับประเภทของรหัสที่คุณเขียน ประสบการณ์การทำงานส่วนตัวของฉันมีแนวโน้มที่จะชอบระบบที่ล้มเหลวในการปรากฏตัวของข้อผิดพลาดเพราะความเสียหายของข้อมูลเงียบเงียบกว่า (และไม่ถูกตรวจจับ) และข้อมูลที่ฉันทำงานมีคุณค่าต่อลูกค้า (แน่นอนมันหมายถึง
Matthieu M.

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

12

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

คุณท้ายด้วยรหัสเช่นนี้:

String result = x.doActionA();
if (result != null) {
  throw new Exception(result);
}
result = x.doActionB();
if (result != null) {
  throw new Exception(result);
}

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

if (!x.doActionA()) {
  throw new Exception(x.getError());
}
if (!x.doActionB()) {
  throw new Exception(x.getError());
}

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

x.doActionA();
x.doActionB();

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

รหัสปัจจุบัน (แย่มาก):

private String doActionA() {
  try {
    someOperationThatCanGoWrong1();
    someOperationThatCanGoWrong2();
    someOperationThatCanGoWrong3();
    return null;
  } catch(Exception e) {
    return "Something went wrong!";
  }
}

ใหม่และปรับปรุง:

private void doActionA() throws Exception {
  someOperationThatCanGoWrong1();
  someOperationThatCanGoWrong2();
  someOperationThatCanGoWrong3();
}

การติดตามแบบ Strack นั้นได้รับการเก็บรักษาไว้และข้อความนั้นมีอยู่ในข้อยกเว้นไม่ใช่ "บางอย่างผิดปกติ!"

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


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

1
นั่นเป็นความจริง แต่ไม่ได้เกิดขึ้นที่นี่ และคุณสามารถรับข้อยกเว้นใน doActionA และล้อมรอบด้วยข้อยกเว้นอื่นด้วยข้อความสถานะ จากนั้นคุณจะยังคงมีการติดตามสแต็กและข้อความที่มีประโยชน์ throw new Exception("Something went wrong with " + instanceVar, ex);
mrjink

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

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

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

5

"เพื่อจัดการข้อผิดพลาดที่อาจเกิดขึ้นได้หลายอย่างซึ่งไม่ควรหยุดการทำงาน"

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

สองรูปแบบที่ฉันใช้คือ:

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

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

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

รหัสทั่วไปที่ใช้ตัวจัดการข้อผิดพลาดอาจมีลักษณะเช่นนี้

class MyFunClass {
  public interface ErrorHandler {
     void onError(Exception e);
     void onWarning(Exception e);
  }

  ErrorHandler eh;

  public void canFail(int i) {
     if(i==0) {
        if(eh!=null) eh.onWarning(new Exception("canFail shouldn't be called with i=0"));
     }
     if(i==1) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=1 is fatal");
        throw new RuntimeException("canFail called with i=2");
     }
     if(i==2) {
        if(eh!=null) eh.onError(new Exception("canFail called with i=2 is an error, but not fatal"));
     }
  }
}

3
+1 สำหรับการสังเกตว่าผู้ใช้ต้องการคำเตือนแทนที่จะเป็นข้อผิดพลาด อาจจะคุ้มค่าที่จะกล่าวถึงwarningsแพ็คเกจของ Python ซึ่งเป็นอีกรูปแบบหนึ่งสำหรับปัญหานี้
James_pic

ขอบคุณสำหรับคำตอบที่ยอดเยี่ยม! นี่คือสิ่งที่ฉันต้องการเห็นรูปแบบเพิ่มเติมสำหรับการจัดการข้อผิดพลาดที่การลอง / จับแบบดั้งเดิมอาจไม่เพียงพอ
Mikayla Maki

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

5

มักจะไม่มีอะไรผิดปกติกับการใช้รูปแบบนี้หรือรูปแบบนั้นตราบใดที่คุณใช้รูปแบบที่ทุกคนใช้ ในการพัฒนาObjective-Cรูปแบบที่ต้องการมากคือการส่งตัวชี้โดยที่วิธีการที่เรียกว่าสามารถฝากวัตถุ NSError ข้อยกเว้นถูกสงวนไว้สำหรับข้อผิดพลาดในการเขียนโปรแกรมและนำไปสู่ความผิดพลาด (เว้นแต่คุณจะมีโปรแกรมเมอร์Java หรือ. NET ที่เขียนแอพ iPhone เครื่องแรกของพวกเขา) และใช้งานได้ดีทีเดียว


4

ตอบคำถามแล้ว แต่ฉันไม่สามารถช่วยเหลือตัวเองได้

คุณไม่สามารถคาดหวังได้ว่าข้อยกเว้นจะให้โซลูชันสำหรับกรณีการใช้งานทั้งหมด ค้อนใคร?

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

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

ดังนั้นบทเรียนที่นี่: ยกเว้นมีสถานที่ของพวกเขาเช่นเดียวกับรหัสข้อผิดพลาด เลือกอย่างชาญฉลาด


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

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

และคุณจะทำอย่างไรกับรหัสผ่านที่แข็งแกร่ง แต่ไม่ได้แย่จริงๆ? คุณจะขัดจังหวะการไหลและแสดงข้อความให้ผู้ใช้ระบุว่ารหัสผ่านที่ป้อนนั้นไม่แรงมาก ดังนั้นมีMiddlyStrongValidatorหรืออะไร และถ้านั่นไม่ได้ขัดขวางโฟลว์ของคุณจริงๆValidatorจะต้องมีการเรียกใช้ล่วงหน้านั่นคือก่อนที่จะดำเนินการโฟลว์ต่อไปในขณะที่ผู้ใช้ยังคงป้อนรหัสผ่าน (หรือคล้ายกัน) แต่การตรวจสอบความถูกต้องไม่ใช่ส่วนหนึ่งของวิธีการที่เป็นปัญหาตั้งแต่แรก :) น่าจะเป็นเรื่องของการลิ้มรส ...
กรกฎาคม

@ jhr ในเครื่องมือตรวจสอบความถูกต้องที่ฉันเขียนฉันมักจะสร้างAggregateException(หรือคล้ายกันValidationException) และวางข้อยกเว้นเฉพาะสำหรับปัญหาการตรวจสอบแต่ละรายการใน InnerExceptions ตัวอย่างเช่นอาจเป็นBadPasswordException: "รหัสผ่านของผู้ใช้มีความยาวน้อยกว่า 6" หรือMandatoryFieldMissingException: "ต้องให้ชื่อแรกแก่ผู้ใช้" เป็นต้นซึ่งไม่เท่ากับรหัสข้อผิดพลาด ข้อความเหล่านี้ทั้งหมดสามารถแสดงต่อผู้ใช้ในแบบที่พวกเขาจะเข้าใจและหากมีการNullReferenceExceptionส่งข้อความแทนเราก็จะได้รับข้อผิดพลาด
โอเมอิกอิกบัล

4

มีกรณีการใช้งานเป็นรหัสข้อผิดพลาดจะดีกว่ายกเว้น

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

คำตอบอื่น ๆ ได้อธิบายถึงสาเหตุที่ควรมีข้อยกเว้นสำหรับรหัสข้อผิดพลาดโดยทั่วไป


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

ไม่ฉันหมายถึงการรายงานไปยังผู้โทร เป็นความรับผิดชอบของผู้โทรในการตัดสินใจว่าผู้ใช้ต้องการทราบเกี่ยวกับข้อผิดพลาดหรือไม่เช่นเดียวกับข้อยกเว้น
Jack Aidley

1
@ jhr: เมื่อใดจะมีการรับประกันอะไรบ้าง? สัญญาสำหรับชั้นเรียนอาจระบุว่าลูกค้ามีความรับผิดชอบบางอย่าง หากลูกค้าปฏิบัติตามสัญญาพวกเขาจะทำสิ่งที่สัญญาต้องการ หากไม่เป็นเช่นนั้นผลที่ตามมาใด ๆ จะเป็นความผิดพลาดของรหัสลูกค้า หากต้องการป้องกันข้อผิดพลาดโดยไม่ตั้งใจจากลูกค้าและสามารถควบคุมประเภทที่ส่งคืนโดยวิธีการทำให้เป็นอันดับหนึ่งอาจมีการรวมค่าสถานะ "ความเสียหายที่อาจเกิดขึ้นโดยไม่ได้รับการยอมรับ" และไม่อนุญาตให้ลูกค้าอ่านข้อมูลโดยไม่ต้องเรียกAcknowledgePossibleCorruptionวิธี .. .
supercat

1
... แต่การมีคลาสอ็อบเจ็กต์เพื่อเก็บข้อมูลเกี่ยวกับปัญหาอาจมีประโยชน์มากกว่าการทิ้งข้อยกเว้นหรือส่งคืนรหัสข้อผิดพลาด Pass-Fail มันขึ้นอยู่กับแอปพลิเคชันที่จะใช้ข้อมูลนั้นในแบบที่เหมาะสม (เช่นเมื่อโหลดไฟล์ "Foo" แจ้งผู้ใช้ว่าข้อมูลอาจไม่น่าเชื่อถือและแจ้งให้ผู้ใช้เลือกชื่อใหม่เมื่อบันทึก)
supercat

1
มีการรับประกันหนึ่งข้อหากใช้ข้อยกเว้น: หากคุณไม่จับพวกเขาพวกเขาจะสูงขึ้น - กรณีที่แย่ที่สุดถึง UI ไม่มีการรับประกันหากคุณใช้รหัสส่งคืนที่ไม่มีใครอ่าน แน่นอนทำตาม API หากคุณต้องการใช้ ฉันเห็นด้วย! แต่มีห้องพักสำหรับข้อผิดพลาดน่าเสียดาย ... เป็น
JHR

2

ไม่มีอะไรผิดปกติหากไม่ใช้ข้อยกเว้นเมื่อข้อยกเว้นไม่เหมาะสม

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


คำถามที่น่าสนใจ ฉันคิดว่าปัญหาเกี่ยวกับคำถามและความเข้าใจของฉันนั้นไม่ชัดเจน สิ่งที่คุณอธิบายไม่ได้เป็นพิเศษ แต่เป็นข้อผิดพลาด เราควรเรียกสิ่งนั้นว่าอะไร?
Mikayla Maki

1

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

ในPerl 6มันทำผ่านfailซึ่งถ้าภายในno fatal;ขอบเขตส่งกลับFailureวัตถุยกเว้น unthrown พิเศษ

ในPerl 5คุณสามารถใช้บริบท :: ย้อนกลับreturn FAILที่คุณสามารถทำเช่นนี้กับ


-1

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

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

แก้ไข: ฉันไม่ได้ตระหนักว่านี่เป็นคำถามของกระบวนทัศน์ซอฟต์แวร์แทนที่จะเป็นกรณีเฉพาะ

ฉันขอชี้แจงประเด็นของฉันเพิ่มเติมในกรณีเฉพาะของฉันซึ่งคำตอบของฉันจะเข้าท่า

  1. ฉันมีคอลเลกชันของวัตถุเอนทิตี
  2. ฉันมีบริการเว็บสไตล์ตามขั้นตอนซึ่งทำงานกับเอนทิตีวัตถุเหล่านี้

ข้อผิดพลาดมีสองประเภท:

  1. ข้อผิดพลาดเมื่อการประมวลผลเกิดขึ้นในเลเยอร์บริการ
  2. ข้อผิดพลาดเนื่องจากมีความไม่สอดคล้องกันในวัตถุเอนทิตี

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

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

  • ใช้ตัวแปรตรวจสอบ
  • โยนข้อยกเว้นทันทีเมื่อมีการตั้งค่าเขตข้อมูลอย่างไม่ถูกต้องจากตัวตั้งค่า

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

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


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