การขว้างข้อยกเว้นเป็นการต่อต้านแบบที่นี่หรือไม่?


33

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

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

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

public class Preferences {
    // null values are legal
    private Map<String, String> valuesFromDatabase;

    private static Map<String, String> defaultValues;

    class KeyNotFoundException extends Exception {
    }

    public String getByKey(String key) {
        try {
            return getValueByKey(key);
        } catch (KeyNotFoundException e) {
            String defaultValue = defaultValues.get(key);
            valuesFromDatabase.put(key, defaultvalue);
            return defaultValue;
        }
    }

    private String getValueByKey(String key) throws KeyNotFoundException {
        if (valuesFromDatabase.containsKey(key)) {
            return valuesFromDatabase.get(key);
        } else {
            throw new KeyNotFoundException();
        }
    }
}

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

โดยพื้นฐานแล้วมันมีวิธีการสองอย่างที่จะดึงเอามาใช้เพื่อสื่อสารกัน

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

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

กลับมาnullจะไม่ชัดเจนเนื่องจากเป็นค่าที่สมบูรณ์ตามกฎหมายเพื่อให้มีไม่มีบอกว่ามันหมายความว่าที่สำคัญไม่ได้มีหรือถ้ามีnullnull

getValueByKeyจะต้องกลับมาบางจัดเรียงของTuple<Boolean, String>, บูลที่กำหนดให้เป็นจริงถ้าคีย์มีอยู่แล้วเพื่อให้เราสามารถแยกแยะความแตกต่างระหว่างและ(true, null) (false, null)( outพารามิเตอร์สามารถใช้ใน C # แต่นั่นคือ Java)

นี่เป็นทางเลือกที่ดีกว่าไหม? ใช่คุณต้องกำหนดคลาสแบบใช้ครั้งเดียวบางส่วนเพื่อให้ได้ผลTuple<Boolean, String>แต่แล้วเราก็จะถูกกำจัดKeyNotFoundExceptionเพื่อให้เกิดความสมดุลดังกล่าว นอกจากนี้เรายังหลีกเลี่ยงค่าใช้จ่ายในการจัดการข้อยกเว้นแม้ว่าจะไม่ได้มีนัยสำคัญในแง่การปฏิบัติ - ไม่มีการพิจารณาประสิทธิภาพที่จะพูดถึงมันเป็นแอปไคลเอนต์และไม่ชอบการตั้งค่าผู้ใช้จะถูกเรียกล้านครั้งต่อวินาที

รูปแบบของวิธีการนี้สามารถใช้ฝรั่งของOptional<String>(ฝรั่งถูกใช้ไปแล้วตลอดโครงการ) แทนกำหนดเองบางส่วนTuple<Boolean, String>และจากนั้นเราสามารถแยกความแตกต่างระหว่างOptional.<String>absent()และ null"เหมาะสม" แต่ก็ยังรู้สึกแฮ็กด้วยเหตุผลที่มองเห็นได้ง่าย - การแนะนำ "ความไร้ค่า" สองระดับดูเหมือนจะเป็นการละเมิดแนวคิดที่ยืนอยู่ข้างหลังการสร้างOptionalในตอนแรก

อีกทางเลือกหนึ่งคือการตรวจสอบอย่างชัดเจนว่ามีคีย์อยู่หรือไม่ (เพิ่มboolean containsKey(String key)เมธอดและเรียกgetValueByKeyเฉพาะเมื่อเรายืนยันแล้วว่ามีอยู่)

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

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

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


1
@ GlenH7 คำตอบที่ยอมรับ (และโหวตสูงสุด) ยอมรับว่า "คุณควรใช้ [ข้อยกเว้น] ในกรณีที่พวกเขาจัดการข้อผิดพลาดได้ง่ายขึ้นด้วยความยุ่งเหยิงของรหัสน้อยลง" ฉันเดาว่าฉันถามที่นี่ว่าตัวเลือกที่ฉันระบุไว้ควรได้รับการพิจารณาว่าเป็น "ความยุ่งเหยิงของรหัสน้อยลงหรือไม่"
Konrad Morawski

1
ฉันถอน VTC ของฉันแล้ว - คุณกำลังขอสิ่งที่เกี่ยวข้อง แต่ต่างจากที่ฉันแนะนำซ้ำ

3
ฉันคิดว่าคำถามจะน่าสนใจยิ่งขึ้นเมื่อgetValueByKeyเป็นสาธารณะ
Doc Brown

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

1
เพียงเพื่อเข้าร่วม --- นี่คือ Python ที่สมบูรณ์แบบและ (ฉันคิดว่า) เป็นวิธีที่ต้องการในการจัดการปัญหานี้ในภาษานั้น แต่เห็นได้ชัดว่าภาษาต่าง ๆ มีการประชุมที่แตกต่างกัน
Patrick Collins

คำตอบ:


75

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

นี่เป็นวิธีที่สะอาดกว่ารุ่นของคุณ ( getValueByKey()วิธีการจะถูกลบ):

public String getByKey(String key) {
    if (valuesFromDatabase.containsKey(key)) {
        return valuesFromDatabase.get(key);
    } else {
        String defaultValue = defaultValues.get(key);
        valuesFromDatabase.put(key, defaultvalue);
        return defaultValue;
    }
}

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


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

59
สัญญาณแรกของความวิกลจริต: คุณเริ่มพูดกับตัวเอง
Robert Harvey

1
@BЈовић: ดีในกรณีนี้ฉันคิดว่ามันโอเค แต่ถ้าคุณต้องการสองตัวแปร - วิธีหนึ่งในการรับค่าโดยไม่ต้องสร้างรหัสที่หายไปโดยอัตโนมัติและอีกอันด้วย? คุณคิดว่าอะไรเป็นทางเลือกที่สะอาดที่สุด (และแห้ง)
Doc Brown

3
@RobertHarvey :) มีคนเขียนรหัสนี้อีกคนเป็นคนที่แยกกันจริงๆฉันเป็นคนวิจารณ์
Konrad Morawski

1
@Alvaro การใช้ข้อยกเว้นบ่อยครั้งไม่มีปัญหา การใช้ข้อยกเว้นไม่ดีเป็นปัญหาไม่ว่าจะใช้ภาษาใด
kdbanman

11

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

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


1
ฉันไม่คิดว่าOptionalสามารถถือเป็นโมฆะ Optional.of()ใช้การอ้างอิงแบบไม่เป็นโมฆะเท่านั้นและOptional.fromNullable()ถือว่าเป็นโมฆะเป็น "ไม่มีค่า"
Navin

1
@Navin มันไม่สามารถถือเป็นโมฆะ แต่คุณมีOptional.absent()ที่จำหน่ายของคุณ ดังนั้นOptional.fromNullable(string)จะเท่ากับOptional.<String>absent()ว่าstringเป็นโมฆะหรือOptional.of(string)ไม่
Konrad Morawski

@ KonradMorawski ฉันคิดว่าปัญหาใน OP คือว่าคุณไม่สามารถแยกแยะระหว่างสตริงว่างและสตริงที่ไม่มีอยู่ได้โดยไม่ต้องทำการยกเว้น Optional.absent()ครอบคลุมหนึ่งในสถานการณ์เหล่านี้ คุณเป็นตัวแทนของอีกคนหนึ่งได้อย่างไร
Navin

@Navin nullกับที่เกิดขึ้นจริง
Konrad Morawski

4
@ KonradMorawski: นั่นฟังดูเป็นความคิดที่แย่จริงๆ ฉันแทบจะไม่สามารถคิดวิธีที่ชัดเจนกว่าที่จะพูดว่า "วิธีนี้ไม่เคยกลับมาnull!" Optional<...>กว่าที่จะประกาศว่ามันเป็นกลับ . .
ruakh

8

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

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

ตามที่ระบุไว้โดย Mike ในความคิดเห็นมีปัญหากับสิ่งนี้; Guava และ Java 8 ไม่Optionalอนุญาตให้เก็บnulls ดังนั้นคุณจะต้องม้วนตัวเองซึ่งตรงไปตรงมา - เกี่ยวข้องกับจำนวนแผ่นพอใช้ดังนั้นจึงอาจ overkill สำหรับสิ่งที่จะใช้เพียงครั้งเดียวภายใน คุณสามารถเปลี่ยนประเภทของแผนที่เป็นMap<String, Optional<String>>แต่การจัดการOptional<Optional<String>>s จะงุ่มง่าม

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


1
ในความเป็นจริงมันเป็นเพียงMap<String, String>ในระดับตารางฐานข้อมูลเพราะvaluesคอลัมน์จะต้องเป็นประเภทหนึ่ง แม้ว่าจะมีเลเยอร์ DAO สำหรับหุ้มห่อที่มีความแข็งแกร่งในการพิมพ์แต่ละคู่ของคีย์ - ค่า แต่ฉันละเว้นสิ่งนี้เพื่อความชัดเจน (แน่นอนว่าคุณสามารถมีตารางหนึ่งแถวที่มีคอลัมน์ n แทนได้ แต่จากนั้นการเพิ่มค่าใหม่แต่ละค่าจำเป็นต้องอัปเดตสคีมาของตารางดังนั้นการลบความซับซ้อนที่เกี่ยวข้องกับการแยกวิเคราะห์จะต้องเสียค่าใช้จ่าย
Konrad Morawski

จุดที่ดีเกี่ยวกับ tuple แน่นอนสิ่งที่(false, "foo")ควรจะหมายถึงอะไร
Konrad Morawski

อย่างน้อยที่สุดด้วยตัวเลือกของ Guava พยายามใส่ค่าว่างไว้ในผลลัพธ์ยกเว้น คุณต้องใช้ Optional.absent ()
Mike Partridge

@ MikePartridge จุดที่ดี วิธีแก้ปัญหาที่น่าเกลียดจะเปลี่ยนแผนที่เพื่อและจากนั้นคุณจะจบลงด้วยMap<String, Optional<String>> Optional<Optional<String>>วิธีแก้ปัญหาที่สับสนน้อยกว่าคือการหมุนตัวเลือกของคุณเองที่อนุญาตnullหรือประเภทข้อมูลพีชคณิตที่คล้ายกันซึ่งไม่สามารถทำได้nullแต่มี 3 สถานะ - ไม่มีว่างเป็นโมฆะหรือมีอยู่ ทั้งสองเป็นเรื่องง่ายที่จะใช้งานแม้ว่าจำนวนแผ่นเหล็กที่เกี่ยวข้องนั้นสูงสำหรับบางสิ่งที่จะใช้ภายในเท่านั้น
Doval

2
"เนื่องจากไม่มีข้อควรพิจารณาเกี่ยวกับประสิทธิภาพการทำงานและเป็นรายละเอียดการนำไปใช้งานในท้ายที่สุดมันไม่สำคัญว่าโซลูชันใดที่คุณเลือก" - ยกเว้นว่าหากคุณใช้ C # โดยใช้ข้อยกเว้นจะรบกวนทุกคนที่พยายามดีบักด้วย "เปิดใช้งานสำหรับข้อยกเว้น CLR
สตีเฟ่น

5

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

ปัญหาตรงนี้ แต่คุณโพสต์โซลูชันด้วยตัวเองแล้ว:

รูปแบบของวิธีการนี้สามารถใช้ตัวเลือกของฝรั่ง (ฝรั่งถูกใช้ไปแล้วตลอดโครงการ) แทน Tuple ที่กำหนดเองบางส่วนและจากนั้นเราสามารถแยกความแตกต่างระหว่าง Optional.absent () และ "เหมาะสม" null แต่ก็ยังรู้สึกแฮ็กด้วยเหตุผลที่มองเห็นได้ง่าย - การแนะนำ "ความไร้ค่า" สองระดับดูเหมือนจะเป็นการละเมิดแนวคิดที่ยืนอยู่ข้างหลังการสร้างทางเลือกในตอนแรก

แต่ไม่ได้ใช้หรือnull Optionalคุณสามารถและควรใช้Optionalเท่านั้น สำหรับความรู้สึก "แฮ็ก" ของคุณเพียงแค่ใช้มันซ้อนกันเพื่อให้คุณจบลงด้วยการOptional<Optional<String>>ทำให้ชัดเจนว่าอาจมีคีย์ในฐานข้อมูล (เลเยอร์ตัวเลือกแรก) และมันอาจมีค่าที่กำหนดไว้ล่วงหน้า (เลเยอร์ตัวเลือกที่สอง)

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

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

  • static static <T> Optional<T> fromNullable(T nullableReference)เพื่อแปลงอินพุตฐานข้อมูลของคุณเป็นOptionalประเภท
  • abstract T or(T defaultValue) เพื่อรับค่าคีย์ของเลเยอร์ตัวเลือกภายในหรือ (ถ้าไม่มี) รับค่าคีย์เริ่มต้นของคุณ

1
Optional<Optional<String>>ดูชั่วร้ายแม้ว่ามันจะมีเสน่ห์ฉันต้องยอมรับว่า
:)

คุณช่วยอธิบายเพิ่มเติมได้ไหมว่าทำไมมันถึงดูชั่วร้าย? List<List<String>>มันเป็นจริงรูปแบบเดียวกับ แน่นอนว่าคุณสามารถ (ถ้าภาษานั้นอนุญาต) สร้างรูปแบบใหม่แบบDBConfigKeyที่ wrapps Optional<Optional<String>>และทำให้อ่านง่ายขึ้นหากคุณต้องการ
valenterry

2
@valenterry มันแตกต่างกันมาก รายการรายการเป็นวิธีที่ถูกต้องในการจัดเก็บข้อมูลบางประเภท ตัวเลือกของตัวเลือกเป็นวิธีที่ผิดปกติในการระบุปัญหาสองชนิดที่ข้อมูลอาจมี
Ypnypn

ตัวเลือกของตัวเลือกเป็นเหมือนรายการที่แต่ละรายการมีองค์ประกอบหนึ่งหรือไม่มีเลย มันเหมือนกัน เพียงเพราะมันไม่ได้ใช้บ่อยใน Javaไม่ได้หมายความว่ามันไม่ดีโดยเนื้อแท้
valenterry

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

5

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

ดูวิธีการติดตั้งใช้งานProperties.getProperty(String)(จาก Java 7):

Object oval = super.get(key);
String sval = (oval instanceof String) ? (String)oval : null;
return ((sval == null) && (defaults != null)) ? defaults.getProperty(key) : sval;

ไม่จำเป็นต้องมี "ข้อยกเว้นการละเมิดเพื่อควบคุมการไหล" ตามที่คุณอ้าง

คล้ายกัน แต่สั้นกว่าเล็กน้อยคำตอบของ@BЈовићอาจเป็น:

public String getByKey(String key) {
    if (!valuesFromDatabase.containsKey(key)) {
        valuesFromDatabase.put(key, defaultValues.get(key));
    }
    return valuesFromDatabase.get(key);
}

4

แม้ว่าฉันคิดว่าคำตอบของ@BЈовићนั้นใช้ได้ในกรณีที่getValueByKeyไม่จำเป็นต้องใช้ที่อื่นฉันไม่คิดว่าโซลูชันของคุณจะไม่ดีในกรณีที่โปรแกรมของคุณมีทั้งสองกรณีการใช้งาน:

  • ดึงข้อมูลโดยใช้กุญแจพร้อมการสร้างอัตโนมัติในกรณีที่ไม่มีรหัสไว้ล่วงหน้าและ

  • การดึงข้อมูลโดยไม่ใช้ระบบอัตโนมัตินั้นโดยไม่ต้องเปลี่ยนแปลงอะไรในฐานข้อมูลที่เก็บหรือแผนที่ที่สำคัญ (คิดว่าgetValueByKeyสาธารณะ beeing ไม่ใช่ส่วนตัว)

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

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

แน่นอนว่ายังเป็นทางเลือกที่สาม: สร้างสามวิธีเอกชนกลับมาTuple<Boolean, String>ตามที่คุณแนะนำและนำมาใช้วิธีการนี้ทั้งในและgetValueByKey getByKeyสำหรับกรณีที่ซับซ้อนมากขึ้นซึ่งอาจเป็นทางเลือกที่ดีกว่าแน่นอน แต่สำหรับกรณีง่าย ๆ ดังที่แสดงไว้ที่นี่นี่เป็นกลิ่นของการครอบงำทาง IMHO และฉันสงสัยว่ารหัสนั้นจะบำรุงรักษาได้ดีกว่าจริงๆ ฉันมาที่นี่พร้อมคำตอบสูงสุดที่นี่โดย Karl Bielefeldt :

"คุณไม่ควรรู้สึกแย่กับการใช้การยกเว้นเมื่อมันทำให้โค้ดของคุณง่ายขึ้น"


3

Optionalเป็นทางออกที่ถูกต้อง อย่างไรก็ตามหากคุณต้องการมีตัวเลือกอื่นที่ให้ความรู้สึก "null สอง" น้อยกว่าให้พิจารณา sentinel

กำหนดสตริงสแตติกส่วนตัวkeyNotFoundSentinelพร้อมค่าnew String("")*

ตอนนี้วิธีการที่เอกชนสามารถมากกว่าreturn keyNotFoundSentinel throw new KeyNotFoundException()วิธีสาธารณะสามารถตรวจสอบค่านั้นได้

String rval = getValueByKey(key);
if (rval == keyNotFoundSentinel) { // reference equality, not string.equals
    ... // generate default value
} else {
    return rval;
}

ในความเป็นจริงnullมันเป็นกรณีพิเศษของแมวมอง มันเป็นค่าแมวมองที่กำหนดโดยภาษาที่มีพฤติกรรมพิเศษบางอย่าง (เช่นพฤติกรรมที่กำหนดไว้อย่างดีถ้าคุณเรียกวิธีการในnull) มันมีประโยชน์มากถ้ามี Sentinel แบบนั้นเกือบจะทุกภาษาใช้

* เราตั้งใจใช้new String("")มากกว่าเพียง""เพื่อป้องกันไม่ให้ Java "ฝึกงาน" สตริงซึ่งจะให้การอ้างอิงเช่นเดียวกับสตริงว่างอื่น ๆ เนื่องจากทำตามขั้นตอนพิเศษนี้เราจึงรับประกันได้ว่าสตริงที่อ้างถึงโดยkeyNotFoundSentinelเป็นตัวอย่างที่ไม่ซ้ำกันซึ่งเราจำเป็นต้องตรวจสอบให้แน่ใจว่ามันไม่สามารถปรากฏในแผนที่ได้


นี่ IMHO เป็นคำตอบที่ดีที่สุด
fgp

2

เรียนรู้จากกรอบการทำงานที่เรียนรู้จากความเจ็บปวดของ Java ทุกจุด:

.NET เสนอวิธีแก้ไขปัญหาที่หรูหรากว่านี้ให้มากสองวิธีโดย:

ส่วนหลังนั้นง่ายต่อการเขียนใน Java ซึ่งในอดีตต้องการเพียงคลาสผู้ช่วย "อ้างอิงที่แข็งแกร่ง"


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