พารามิเตอร์ในการควบคุมว่าจะโยนข้อยกเว้นหรือส่งคืน null - แนวปฏิบัติที่ดี?


24

ฉันมักจะเจอวิธีการ / ฟังก์ชั่นที่มีพารามิเตอร์บูลีนเพิ่มเติมซึ่งควบคุมว่าข้อยกเว้นจะถูกโยนออกมาเมื่อความล้มเหลวหรือกลับมาเป็นโมฆะ

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

ให้เราสมมติว่ามีเหตุผลที่ดีว่าทำไมเราต้องการสนับสนุนทั้งสองวิธี

โดยส่วนตัวแล้วฉันคิดว่าวิธีการดังกล่าวควรแยกออกเป็นสองส่วน: วิธีหนึ่งที่ทำให้เกิดข้อยกเว้นในความล้มเหลวอีกวิธีหนึ่งที่ส่งกลับค่า null เมื่อล้มเหลว

ดังนั้นจะดีกว่ากัน?

ตอบ: วิธีการหนึ่งที่มี$exception_on_failureพารามิเตอร์

/**
 * @param int $id
 * @param bool $exception_on_failure
 *
 * @return Item|null
 *   The item, or null if not found and $exception_on_failure is false.
 * @throws NoSuchItemException
 *   Thrown if item not found, and $exception_on_failure is true.
 */
function loadItem(int $id, bool $exception_on_failure): ?Item;

B: สองวิธีที่แตกต่าง

/**
 * @param int $id
 *
 * @return Item|null
 *   The item, or null if not found.
 */
function loadItemOrNull(int $id): ?Item;

/**
 * @param int $id
 *
 * @return Item
 *   The item, if found (exception otherwise).
 *
 * @throws NoSuchItemException
 *   Thrown if item not found.
 */
function loadItem(int $id): Item;

แก้ไข: C: มีอะไรอีกไหม

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

หมายเหตุ

ในกรณีที่มีคนสงสัย: ตัวอย่างอยู่ใน PHP แต่ฉันคิดว่าคำถามนี้ใช้กับทุกภาษาตราบใดที่คล้ายกับ PHP หรือ Java


5
ในขณะที่ฉันต้องทำงานใน PHP และค่อนข้างเชี่ยวชาญมันเป็นภาษาที่ไม่ดีและมีห้องสมุดมาตรฐานอยู่ทั่วทุกที่ อ่านeev.ee/blog/2012/04/09/php-a-fractal-of-bad-designสำหรับการอ้างอิง ดังนั้นจึงไม่น่าแปลกใจจริงๆที่นักพัฒนา PHP บางคนใช้นิสัยที่ไม่ดี แต่ฉันไม่เห็นกลิ่นการออกแบบนี้โดยเฉพาะ
Polygnome

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


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

4
ข้อเสนอแนะหนึ่งข้อ: แทนที่จะloadItemOrNull(id)พิจารณาว่าloadItemOr(id, defaultItem)เหมาะสมสำหรับคุณหรือไม่ หากรายการนั้นเป็นสตริงหรือหมายเลขก็มักจะทำ
user949300

คำตอบ:


35

คุณถูกต้อง: สองวิธีมีมากดีกว่าสำหรับว่าสำหรับหลายสาเหตุ:

  1. ใน Java ลายเซ็นของวิธีการที่อาจเกิดข้อยกเว้นจะรวมถึงข้อยกเว้นนี้ วิธีอื่นจะไม่ มันทำให้ชัดเจนโดยเฉพาะสิ่งที่คาดหวังจากหนึ่งและอื่น ๆ

  2. ในภาษาต่าง ๆ เช่น C # ที่ลายเซ็นของวิธีการนี้ไม่ได้บอกอะไรเกี่ยวกับข้อยกเว้นวิธีการสาธารณะควรยังคงมีการจัดทำเอกสารและเอกสารดังกล่าวรวมถึงข้อยกเว้น การจัดทำเอกสารด้วยวิธีการเดียวจะไม่ง่าย

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

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

  4. เฟรมเวิร์กบางตัวเช่น. NET Framework ได้สร้างข้อตกลงสำหรับสถานการณ์นี้และพวกเขาแก้ปัญหาด้วยสองวิธีเช่นเดียวกับที่คุณแนะนำ แตกต่างเพียงว่าพวกเขาใช้รูปแบบการอธิบายโดย Ewan ในคำตอบของเขาดังนั้นint.Parse(string): intพ่นยกเว้นในขณะที่int.TryParse(string, out int): boolไม่ได้ หลักการตั้งชื่อนี้แข็งแรงมากในชุมชนของ. NET และควรปฏิบัติตามเมื่อใดก็ตามที่รหัสตรงกับสถานการณ์ที่คุณอธิบาย


1
ขอบคุณนี่คือคำตอบที่ฉันกำลังมองหา! วัตถุประสงค์คือฉันเริ่มการสนทนาเกี่ยวกับเรื่องนี้สำหรับ Drupal CMS, drupal.org/project/drupal/issues/2932772และฉันไม่ต้องการให้เรื่องนี้เกี่ยวกับความชอบส่วนตัวของฉัน
donquixote

4
ประเพณีที่ยืนยาวจริง ๆ อย่างน้อย. NET คือการไม่ใช้สองวิธี แต่เลือกตัวเลือกที่เหมาะสมสำหรับงานที่ถูกต้องแทน มีข้อยกเว้นสำหรับสถานการณ์พิเศษไม่ให้จัดการโฟลว์ควบคุมที่คาดไว้ ไม่สามารถวิเคราะห์สตริงเป็นจำนวนเต็มหรือไม่ ไม่ใช่สถานการณ์ที่คาดไม่ถึงหรือยอดเยี่ยม int.Parse()ถือเป็นการตัดสินใจการออกแบบที่ดีจริงๆซึ่งเป็นเหตุผลว่าทำไมทีม .NET TryParseเดินไปข้างหน้าและดำเนินการ
Voo

1
การนำเสนอสองวิธีแทนที่จะคิดเกี่ยวกับประเภทการใช้งานที่เรากำลังเผชิญอยู่นั้นเป็นวิธีที่ง่าย: อย่าคิดว่าคุณกำลังทำอะไรอยู่แทนที่จะรับผิดชอบต่อผู้ใช้ API ทั้งหมดของคุณ แน่ใจว่ามันทำงาน แต่นั่นไม่ใช่จุดเด่นของการออกแบบที่ดี API (หรือการออกแบบใด ๆ สำหรับเรื่องการอ่านอย่างเช่น. โจเอลใช้เวลาในการนี้ .
Voo

@Voo จุดที่ถูกต้อง แน่นอนว่าการให้สองวิธีทำให้ผู้เลือกมีทางเลือก บางทีอาจมีค่าพารามิเตอร์บางรายการที่คาดว่าจะมีอยู่ในขณะที่ค่าพารามิเตอร์อื่น ๆ รายการที่ไม่ได้มีอยู่ก็ถือว่าเป็นกรณีปกติ บางทีส่วนประกอบอาจถูกใช้ในแอพพลิเคชั่นเดียวซึ่งคาดว่าจะมีไอเท็มอยู่เสมอและในแอปพลิเคชันอื่นที่มีรายการที่ไม่ได้มีอยู่ถือเป็นเรื่องปกติ บางทีโทรเรียกร้องก่อนที่จะเรียก->itemExists($id) ->loadItem($id)หรือบางทีมันอาจเป็นเพียงทางเลือกที่คนอื่นทำในอดีตและเราต้องยอมรับมัน
donquixote

1
เหตุผลที่ 1 ข: บางภาษา (Haskell, Swift) ต้องการความสามารถในการลบล้างได้ในลายเซ็น โดยเฉพาะ Haskell มีประเภทที่แตกต่างกันอย่างสิ้นเชิงสำหรับค่า nullable ( data Maybe a = Just a | Nothing)
John Dvorak

9

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

ในการลองจับคำสั่งและการจัดการข้อยกเว้น

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

ค การทำเครื่องหมายการเรียกใช้ฟังก์ชันtry!ซึ่งหมายความว่า "ฉันแน่ใจว่าสายนี้จะไม่ทิ้ง" หากฟังก์ชั่นที่เรียกว่าไม่โยนแอพลิเคชันที่มีการรับประกันความผิดพลาด

d การทำเครื่องหมายการเรียกใช้ฟังก์ชันtry?ซึ่งหมายความว่า "ฉันรู้ว่าฟังก์ชันสามารถโยนได้ แต่ฉันไม่สนใจว่าจะเกิดข้อผิดพลาดแบบใด" สิ่งนี้จะเปลี่ยนค่าที่ส่งคืนของฟังก์ชันจากประเภท " T" เป็นประเภท " optional<T>" และข้อผิดพลาดที่ส่งออกไปจะกลายเป็นศูนย์ส่งคืน

(a) และ (d) เป็นกรณีที่คุณถามถึง เกิดอะไรขึ้นถ้าฟังก์ชั่นสามารถกลับศูนย์และสามารถโยนข้อยกเว้น? ในกรณีดังกล่าวประเภทของค่าส่งคืนจะเป็น " optional<R>" สำหรับบางประเภท R และ (d) จะเปลี่ยนเป็น " optional<optional<R>>" ซึ่งเป็นบิตที่ลึกลับและยากที่จะเข้าใจ แต่ก็ปรับได้อย่างสมบูรณ์ และฟังก์ชั่น Swift สามารถส่งคืนoptional<Void>ได้ดังนั้น (d) สามารถใช้สำหรับการขว้างฟังก์ชั่นการคืนค่าเป็นโมฆะ


2

ฉันชอบbool TryMethodName(out returnValue)วิธีการ

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

หากคุณเพิ่งคืนค่า Null คุณจะไม่รู้ว่ามันล้มเหลวหรือค่าที่ส่งคืนนั้นถูกต้องตามกฎหมายหรือไม่

เช่น:

//throws exceptions when error occurs
Item LoadItem(int id);

//returns false when error occurs
bool TryLoadItem(int id, out Item item)

ตัวอย่างการใช้งาน (ออกหมายถึงการส่งผ่านอ้างอิงโดยไม่กำหนดค่าเริ่มต้น)

Item i;
if(TryLoadItem(3, i))
{
    Print(i.Name);
}
else
{
    Print("unable to load item :(");
}

ขออภัยคุณทำฉันที่นี่หายไป " bool TryMethodName(out returnValue)วิธีการ" ของคุณมีลักษณะอย่างไรในตัวอย่างดั้งเดิม
donquixote

แก้ไขเพื่อเพิ่มตัวอย่าง
Ewan

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

2
ใช่ขอโทษไม่ได้ตระหนักถึง 'ออก' เป็นเฉพาะ c #
Ewan

1
ไม่เสมอไป แต่ถ้ารายการ 3 เป็นโมฆะ คุณคงไม่ทราบว่ามีข้อผิดพลาดหรือไม่
Ewan

2

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


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

@ เจอราร์ดคุณช่วยยกตัวอย่างให้ฉันหน่อยได้ไหม?
สูงสุด

@Max เมื่อคุณมีตัวแปรบูลีนb: แทนที่จะเรียกfoo(…, b);คุณต้องเขียนif (b) then foo_x(…) else foo_y(…);และทำซ้ำอาร์กิวเมนต์อื่น ๆ หรือสิ่งที่ต้องการ((b) ? foo_x : foo_y)(…)ถ้าภาษามีตัวดำเนินการแบบไตรภาคและฟังก์ชัน / เมธอดชั้นหนึ่ง และสิ่งนี้อาจซ้ำไปซ้ำมาจากหลาย ๆ ที่ในโปรแกรม
BlackJack

@ BlackackJack ใช่ฉันอาจเห็นด้วยกับคุณ ฉันคิดว่าตัวอย่างที่ใช่ผมอาจจะห่อในการทำงานเช่นนี้อีกif (myGrandmaMadeCookies) eatThem() else makeFood() checkIfShouldCook(myGrandmaMadeCookies)ฉันสงสัยว่าความแตกต่างที่คุณพูดถึงนั้นเกี่ยวข้องกับว่าเรากำลังผ่านการบูลีนหรือไม่ว่าเราต้องการให้ฟังก์ชันทำตัวเหมือนคำถามเดิมหรือไม่และเราส่งผ่านข้อมูลบางอย่างเกี่ยวกับแบบจำลองของเราเช่นเดียวกับใน ตัวอย่างยายที่ฉันเพิ่งให้
สูงสุด

1
ความแตกต่างกันนิดหน่อยที่อ้างถึงในความคิดเห็นของฉันหมายถึง "คุณควรเสมอ" อาจใช้ถ้อยคำใหม่เป็น "ถ้า a, b และ c มีผลบังคับใช้แล้ว [... ]" "กฎ" ที่คุณพูดถึงนั้นเป็นกระบวนทัศน์ที่ยอดเยี่ยม แต่แตกต่างกันมากในกรณีการใช้งาน
เจอราร์ด

1

Clean Codeแนะนำให้หลีกเลี่ยงอาร์กิวเมนต์บูลีนเนื่องจากความหมายของมันนั้นทึบที่ไซต์การโทร:

loadItem(id, true) // does this throw or not? We need to check the docs ...

ในขณะที่วิธีการแยกสามารถใช้ชื่อที่แสดงออก:

loadItemOrNull(id); // meaning is obvious; no need to refer to docs

1

หากฉันต้องใช้ A หรือ B ฉันจะใช้ B สองวิธีแยกกันด้วยเหตุผลที่ระบุไว้ คำตอบ Arseni Mourzenkos

แต่มีวิธีอื่น1 : ใน Java มันถูกเรียกว่าOptionalแต่คุณสามารถใช้แนวคิดเดียวกันกับภาษาอื่น ๆ ที่คุณสามารถกำหนดประเภทของคุณเองหากภาษาไม่ได้ให้ชั้นเรียนที่คล้ายกัน

คุณกำหนดวิธีการของคุณเช่นนี้:

Optional<Item> loadItem(int id);

ในวิธีการของคุณคุณจะกลับมาOptional.of(item)ถ้าคุณพบรายการหรือคุณกลับมาOptional.empty()ในกรณีที่คุณไม่ คุณไม่เคยโยน2nullและไม่เคยกลับมา

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

  • loadItem(1).get() จะโยน NoSuchElementExceptionหรือส่งคืนรายการที่พบ
  • นอกจากนี้ยังมีloadItem(1).orElseThrow(() -> new MyException("No item 1"))การใช้ข้อยกเว้นแบบกำหนดเองหากการส่งคืนOptionalว่างเปล่า
  • loadItem(1).orElse(defaultItem)ส่งคืนรายการที่พบหรือผ่านdefaultItem(ซึ่งอาจเป็นnull) โดยไม่ต้องส่งข้อยกเว้น
  • loadItem(1).map(Item::getName) จะกลับมาอีกครั้ง Optionalพร้อมกับชื่อรายการถ้ามี
  • มีบางวิธีอื่น ๆ มีให้ดูที่Javadoc Optionalสำหรับ

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


1ฉันเดาว่ามันเหมือนคำตอบtry?ในgnasher729แต่มันไม่ชัดเจนสำหรับฉันเพราะฉันไม่รู้ Swift และดูเหมือนว่าจะเป็นคุณสมบัติทางภาษาดังนั้นจึงเป็นเรื่องเฉพาะสำหรับ Swift

2คุณสามารถโยนข้อยกเว้นด้วยเหตุผลอื่นเช่นถ้าคุณไม่รู้ว่ามีรายการหรือไม่เพราะคุณมีปัญหาในการสื่อสารกับฐานข้อมูล


0

ในฐานะที่เป็นอีกจุดหนึ่งที่เห็นด้วยกับการใช้สองวิธีนี้สามารถนำไปใช้งานได้ง่ายมาก ฉันจะเขียนตัวอย่างของฉันใน C # เพราะฉันไม่รู้ไวยากรณ์ของ PHP

public Widget GetWidgetOrNull(int id) {
    // All the lines to get your item, returning null if not found
}

public Widget GetWidget(int id) {
    var widget = GetWidgetOrNull(id);

    if (widget == null) {
        throw new WidgetNotFoundException();
    }

    return widget;
}

0

ฉันชอบสองวิธีด้วยวิธีนี้คุณจะไม่เพียง แต่บอกฉันว่าคุณจะคืนค่า แต่เป็นค่าที่แน่นอน

public int GetValue(); // If you can't get the value, you'll throw me an exception
public int GetValueOrDefault(); // If you can't get the value, you'll return me 0, since it's the default for ints

TryDoSomething () ไม่ใช่รูปแบบที่ไม่ดีเช่นกัน

public bool TryParse(string value, out int parsedValue); // If you return me false, I won't bother checking parsedValue.

ฉันเคยชินกับการเห็นอดีตในการโต้ตอบฐานข้อมูลในขณะที่หลังถูกใช้ในการแยกวิเคราะห์

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


-1

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

Thing getThing(string x);
Thing tryGetThing (string x);

และหนึ่งต้องการฟังก์ชั่นที่ทำหน้าที่เหมือนกัน แต่เมื่อใช้ x ในวงเล็บมันจำเป็นต้องเขียนฟังก์ชั่น wrapper สองชุด:

Thing getThingWithBrackets(string x)
{
  getThing("["+x+"]");
}
Thing tryGetThingWithBrackets (string x);
{
  return tryGetThing("["+x+"]");
}

หากมีข้อโต้แย้งเกี่ยวกับวิธีจัดการข้อผิดพลาดจะต้องใช้ wrapper เดียว

Thing getThingWithBrackets(string x, bool returnNullIfFailure)
{
  return getThing("["+x+"]", returnNullIfFailure);
}

หาก wrapper นั้นเรียบง่ายเพียงพอการทำสำเนารหัสอาจไม่เลวร้ายเกินไป แต่หากจำเป็นต้องเปลี่ยนแปลงการทำซ้ำรหัสจะต้องมีการทำซ้ำการเปลี่ยนแปลงใด ๆ แม้ว่าหนึ่งเลือกที่จะเพิ่มจุดเข้าที่ไม่ได้ใช้อาร์กิวเมนต์พิเศษ:

Thing getThingWithBrackets(string x)
{
   return getThingWithBrackets(x, false);
}
Thing tryGetThingWithBrackets(string x)
{
   return tryGetThingWithBrackets(x, true);
}

หนึ่งสามารถเก็บ "ตรรกะทางธุรกิจ" ทั้งหมดไว้ในหนึ่งวิธี - วิธีหนึ่งซึ่งยอมรับอาร์กิวเมนต์ที่ระบุว่าจะต้องทำอย่างไรในกรณีที่เกิดความล้มเหลว


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

แน่นอนทำให้รุ่นที่มีและไม่มีข้อยกเว้นการขว้างปาในแพคเกจแยกต่างหากและทำให้ wrappers ทั่วไป?
Will Crawford จะ

-1

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

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

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

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

บนแพลตฟอร์มที่ฝังตัวคุณไม่ต้องการข้อยกเว้นเหล่านั้น มันจะทำให้รู้สึกมากขึ้นในการตรวจสอบif (this->mag() == 0) return Vector(0, 0, 0);มันจะทำให้รู้สึกมากขึ้นในการทำการตรวจสอบอันที่จริงฉันเห็นสิ่งนี้ในรหัสจริง

ตอนนี้คิดจากมุมมองของธุรกิจ คุณสามารถลองสอนวิธีการใช้ API ได้สองวิธี:

// Development version - exceptions        // Embeded version - return 0s
Vector right = forward.cross(up);          Vector right = forward.cross(up);
Vector localUp = right.cross(forward);     Vector localUp = right.cross(forward);
Vector forwardHat = forward.unit();        Vector forwardHat = forward.tryUnit();
Vector rightHat = right.unit();            Vector rightHat = right.tryUnit();
Vector localUpHat = localUp.unit()         Vector localUpHat = localUp.tryUnit();

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

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

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

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