ทางเลือกในการลองจับซ้อนสำหรับทางเลือก


14

ฉันมีสถานการณ์ที่ฉันพยายามดึงวัตถุ หากการค้นหาล้มเหลวฉันมีข้อผิดพลาดหลายประการซึ่งแต่ละข้ออาจล้มเหลว ดังนั้นรหัสดูเหมือนว่า:

try {
    return repository.getElement(x);
} catch (NotFoundException e) {
    try {
        return repository.getSimilarElement(x);
    } catch (NotFoundException e1) {
        try {
            return repository.getParentElement(x);
        } catch (NotFoundException e2) {
            //can't recover
            throw new IllegalArgumentException(e);
        }
    }
}

มันดูน่าเกลียดมาก ๆ ฉันเกลียดที่จะคืนค่าว่าง แต่ในสถานการณ์นี้ดีกว่าไหม

Element e = return repository.getElement(x);
if (e == null) {
    e = repository.getSimilarElement(x);
}
if (e == null) {
    e = repository.getParentElement(x);
}
if (e == null) {
    throw new IllegalArgumentException();
}
return e;

มีทางเลือกอื่นหรือไม่?

การใช้ try-catch ที่ซ้อนกันจะบล็อกการป้องกันแบบ มีความเกี่ยวข้อง แต่คำตอบนั้นอยู่ในแนวของ "บางครั้ง แต่มักจะหลีกเลี่ยงได้" โดยไม่บอกว่าควรหลีกเลี่ยงเมื่อใดหรืออย่างไร


1
เป็นNotFoundExceptionสิ่งที่ยอดเยี่ยมจริงหรือ

ฉันไม่รู้และนั่นอาจเป็นสาเหตุที่ฉันมีปัญหา สิ่งนี้อยู่ในบริบทอีคอมเมิร์ซซึ่งผลิตภัณฑ์ถูกยกเลิกทุกวัน หากมีใครคั่นหน้าผลิตภัณฑ์ที่จะหยุดผลิตในภายหลังและจากนั้นพยายามเปิดที่คั่นหน้า ...
Alex Wittig

@ FiveNine ในความคิดของฉันไม่แน่นอน - เป็นที่คาดหวัง ดูstackoverflow.com/questions/729379/…
Konrad Morawski

คำตอบ:


17

วิธีปกติในการกำจัดการซ้อนคือใช้ฟังก์ชัน:

Element getElement(x) {
    try {
        return repository.getElement(x);
    } catch (NotFoundException e) {
        return fallbackToSimilar(x);
    }  
}

Element fallbackToSimilar(x) {
    try {
        return repository.getSimilarElement(x);
     } catch (NotFoundException e1) {
        return fallbackToParent(x);
     }
}

Element fallbackToParent(x) {
    try {
        return repository.getParentElement(x);
    } catch (NotFoundException e2) {
        throw new IllegalArgumentException(e);
    }
}

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


1
ในบริบทนี้จะเป็นคำที่ดีกว่าmethod function
Sulthan

12

นี่จะเป็นเรื่องง่ายกับบางสิ่งบางอย่างเช่นตัวเลือก monad น่าเสียดายที่ Java ไม่มีอยู่ ใน Scala ฉันจะใช้Tryประเภทเพื่อค้นหาวิธีแก้ปัญหาที่ประสบความสำเร็จเป็นครั้งแรก

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

interface ElementSource {
    public Element get();
}

...

final repository = ...;

// this could be simplified a lot using Java 8's lambdas
List<ElementSource> sources = Arrays.asList(
    new ElementSource() {
        @Override
        public Element get() { return repository.getElement(); }
    },
    new ElementSource() {
        @Override
        public Element get() { return repository.getSimilarElement(); }
    },
    new ElementSource() {
        @Override
        public Element get() { return repository.getParentElement(); }
    }
);

Throwable exception = new NoSuchElementException("no sources set up");
for (ElementSource source : sources) {
    try {
        return source.get();
    } catch (NotFoundException e) {
        exception = e;
    }
}
// we end up here if we didn't already return
// so throw the last exception
throw exception;

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


+1 สำหรับการกล่าวถึงTryประเภทใน Scala สำหรับการกล่าวถึง monads และสำหรับการแก้ปัญหาโดยใช้การวนซ้ำ
Giorgio

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

1
ที่จริงแล้วตามเวลาที่คำตอบนี้โพสต์, Java 8 พร้อมการสนับสนุนสำหรับOptionalmonad ( พิสูจน์ ) ได้เปิดตัวแล้ว
mkalkov

3

หากคุณคาดว่าจะมีการเรียกที่เก็บจำนวนมากNotFoundExceptionคุณสามารถใช้ wrapper รอบ ๆ ที่เก็บเพื่อปรับปรุงรหัส ฉันจะไม่แนะนำสิ่งนี้สำหรับการดำเนินงานปกติโปรดทราบ:

public class TolerantRepository implements SomeKindOfRepositoryInterfaceHopefully {

    private Repository repo;

    public TolerantRepository( Repository r ) {
        this.repo = r;
    }

    public SomeType getElement( SomeType x ) {
        try {
            return this.repo.getElement(x);
        }
        catch (NotFoundException e) {
            /* For example */
            return null;
        }
    }

    // and the same for other methods...

}

3

ที่ข้อเสนอแนะของ @ amon นี่คือคำตอบที่เป็น monadic มากกว่า เป็นรุ่นที่ลวกมากซึ่งคุณต้องยอมรับสมมติฐานสองสามข้อ:

  • ฟังก์ชัน "unit" หรือ "return" เป็นตัวสร้างคลาส

  • การดำเนินการ "ผูก" เกิดขึ้นในเวลารวบรวมดังนั้นจึงซ่อนตัวจากการเรียกใช้

  • ฟังก์ชั่น "การกระทำ" จะถูกผูกไว้กับชั้นเรียนในเวลารวบรวม

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

ด้วยการพิจารณาเหล่านั้น monad แปลเป็นคลาส wrapper คล่องแคล่ว (แม้ว่าคุณจะให้ความยืดหยุ่นมากที่คุณจะได้รับในภาษาทำงานได้อย่างหมดจด):

public class RepositoryLookup<E> {
    private String source;
    private E answer;
    private Exception exception;

    public RepositoryLookup<E>(String source) {
        this.source = source;
    }

    public RepositoryLookup<E> fetchElement() {
        if (answer != null) return this;
        if (! exception instanceOf NotFoundException) return this;

        try {
            answer = lookup(source);
        }
        catch (Exception e) {
            exception = e;
        }

        return this;
    }

    public RepositoryLookup<E> orFetchSimilarElement() {
        if (answer != null) return this; 
        if (! exception instanceOf NotFoundException) return this;

        try {
            answer = lookupVariation(source);
        }
        catch (Exception e) {
            exception = e;
        }

        return this;
    }

    public RepositoryLookup<E> orFetchParentElement() {
        if (answer != null) return this; 
        if (! exception instanceOf NotFoundException) return this;

        try {
            answer = lookupParent(source);
        }
        catch (Exception e) {
            exception = e;
        }

        return this;
    }

    public boolean failed() {
        return exception != null;
    }

    public Exception getException() {
        return exception;
    }

    public E getAnswer() {
        // better to check failed() explicitly ;)
        if (this.exception != null) {
            throw new IllegalArgumentException(exception);
        }
        // TODO: add a null check here?
        return answer;
    }
}

(สิ่งนี้จะไม่คอมไพล์ ... รายละเอียดบางอย่างยังไม่เสร็จเพื่อให้ตัวอย่างเล็ก)

และการภาวนาจะมีลักษณะเช่นนี้:

Repository<String> repository = new Repository<String>(x);
repository.fetchElement().orFetchParentElement().orFetchSimilarElement();

if (repository.failed()) {
    throw new IllegalArgumentException(repository.getException());
}

System.err.println("Got " + repository.getAnswer());

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

ฉันทำมันเร็วมาก มันไม่ถูกต้องนัก แต่หวังว่าจะสื่อความคิด


1
repository.fetchElement().fetchParentElement().fetchSimilarElement();- ในความคิดของฉัน: รหัสความชั่วร้าย (ในความรู้สึกที่กำหนดโดย Jon Skeet)
Konrad Morawski

บางคนไม่ชอบสไตล์นั้น แต่ใช้return thisในการสร้างการโทรวัตถุผูกมัดได้รับรอบนาน เนื่องจาก OO เกี่ยวข้องกับวัตถุที่ไม่แน่นอนreturn thisจึงมีค่าเทียบเท่าหรือน้อยกว่าreturn nullโดยไม่มีการผูกมัด อย่างไรก็ตามreturn new Thing<E>เปิดประตูสู่ความสามารถอื่นที่ตัวอย่างนี้ไม่เข้าดังนั้นจึงเป็นสิ่งสำคัญสำหรับรูปแบบนี้หากคุณเลือกที่จะลงเส้นทางนี้
Rob

1
แต่ฉันชอบสไตล์นั้นและไม่ขัดกับสายเรียกเข้าหรืออินเทอร์เฟซที่คล่องแคล่วเช่นนี้ อย่างไรก็ตามมีความแตกต่างระหว่างCustomerBuilder.withName("Steve").withID(403)และรหัสนี้เพราะเพิ่งเห็น.fetchElement().fetchParentElement().fetchSimilarElement()มันไม่ชัดเจนว่าเกิดอะไรขึ้นและนั่นคือสิ่งสำคัญที่นี่ พวกเขาทั้งหมดได้รับการเรียก? มันไม่ได้สะสมในกรณีนี้และดังนั้นจึงไม่ใช่เรื่องง่าย ฉันต้องการเห็นสิ่งนั้นif (answer != null) return thisก่อนที่ฉันจะรับมันอย่างแท้จริง อาจเป็นเพียงเรื่องของการตั้งชื่อที่เหมาะสม ( orFetchParent) แต่มันเป็น "เวทมนต์" ต่อไป
Konrad Morawski

1
โดยวิธีการ (ฉันรู้ว่ารหัสของคุณมีขนาดใหญ่เกินไปและเป็นเพียงหลักฐานของแนวคิด) มันจะดีกว่าที่จะคืนค่าโคลนanswerในgetAnswerและรีเซ็ต (ล้าง) answerฟิลด์ตัวเองก่อนที่จะส่งคืนค่าของมัน ไม่เช่นนั้นจะเป็นการแบ่งหลักการของการแยกคำสั่ง / แบบสอบถามเนื่องจากการขอให้ดึงองค์ประกอบ (การสอบถาม) จะเปลี่ยนสถานะของวัตถุที่เก็บข้อมูลของคุณ ( answerจะไม่ถูกรีเซ็ต) และส่งผลต่อพฤติกรรมของfetchElementเมื่อคุณเรียกใช้ครั้งต่อไป ใช่ฉันวางยานิดหน่อยฉันคิดว่าคำตอบนั้นถูกต้องฉันไม่ใช่คนที่ลงคะแนน
Konrad Morawski

1
นั่นเป็นจุดที่ดี อีกวิธีหนึ่งคือ "tryToFetch ... " จุดสำคัญคือในกรณีนี้ทั้ง 3 วิธีจะถูกเรียกใช้ แต่ในอีกกรณีหนึ่งลูกค้าอาจใช้ "tryFetch (). tryFetchParent ()" นอกจากนี้การเรียกมันว่า "Repository" นั้นผิดเพราะมันเป็นแบบจำลองการดึงข้อมูลเดียว บางทีฉันอาจจะยุ่งกับชื่อที่เรียกว่า "RepositoryLookup" และ "พยายาม" เพื่อให้ชัดเจนว่านี่เป็นสิ่งประดิษฐ์แบบชั่วคราวที่มีภาพเดียวที่ให้ความหมายบางอย่างเกี่ยวกับการค้นหา
Rob

2

อีกวิธีในการจัดโครงสร้างชุดของเงื่อนไขเช่นนี้คือการพกพาธงหรืออื่น ๆ ทดสอบหาค่าว่าง (ยังดีกว่าให้ใช้ตัวเลือกของ Guava เพื่อพิจารณาว่าคำตอบที่ดีอยู่เมื่อใด) เพื่อเชื่อมโยงเงื่อนไขเข้าด้วยกัน

Element e = null;

try {
    e = repository.getElement(x);
} catch (NotFoundException e) {
    // nope -- try again!
}

if (e == null) {  // or ! optionalElement.isPresent()
    try {
        return repository.getSimilarElement(x);
    } catch (NotFoundException e1) {
        // nope -- try again!
    }
}

if (e == null) {  // or ! optionalElement.isPresent()
    try {
        return repository.getParentElement(x);
    } catch (NotFoundException e2) {
        // nope -- try again!
    }
}

if (e == null) {  // or ! optionalElement.isPresent()
    //can't recover
    throw new IllegalArgumentException(e);
}

return e;

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

(ฉันเห็นด้วยกับ @amon ฉันขอแนะนำให้ดูที่รูปแบบ Monad โดยมีวัตถุห่อหุ้มอย่างclass Repository<E>ที่มีสมาชิกE answer;และException error;ในแต่ละขั้นตอนตรวจสอบเพื่อดูว่ามีข้อยกเว้นหรือไม่และข้ามขั้นตอนที่เหลือแต่ละที่ ท้ายที่สุดคุณจะเหลือทั้งคำตอบไม่มีคำตอบหรือข้อยกเว้น & คุณสามารถตัดสินใจว่าจะทำอย่างไรกับเรื่องนั้น)


-2

อันดับแรกฉันคิดว่าควรมีฟังก์ชั่นเช่นrepository.getMostSimilar(x)(คุณควรเลือกชื่อที่เหมาะสมกว่า) เนื่องจากดูเหมือนว่ามีตรรกะที่ใช้เพื่อค้นหาองค์ประกอบที่ใกล้เคียงที่สุดหรือคล้ายกันมากที่สุดสำหรับองค์ประกอบที่กำหนด

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

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


คำตอบคือการตอบคำถามไม่ขอคำชี้แจง
ริ้น

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