Rantai centang nol vs menangkap NullPointerException


118

Sebuah layanan web mengembalikan XML yang sangat besar dan saya perlu mengakses bidang bersarangnya secara mendalam. Sebagai contoh:

return wsObject.getFoo().getBar().getBaz().getInt()

Masalahnya adalah bahwa getFoo(), getBar(), getBaz()mungkin semua kembali null.

Namun, jika saya memeriksa nulldalam semua kasus, kodenya menjadi sangat bertele-tele dan sulit dibaca. Selain itu, saya mungkin melewatkan pemeriksaan untuk beberapa bidang.

if (wsObject.getFoo() == null) return -1;
if (wsObject.getFoo().getBar() == null) return -1;
// maybe also do something with wsObject.getFoo().getBar()
if (wsObject.getFoo().getBar().getBaz() == null) return -1;
return wsObject.getFoo().getBar().getBaz().getInt();

Apakah menulis itu dapat diterima

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    return -1;
}

atau apakah itu dianggap antipattern?


29
Saya tidak akan keberatan dengan nullcek sebanyak itu, karena wsObject.getFoo().getBar().getBaz().getInt()sudah menjadi bau kode. Baca apa itu "Law of Demeter" dan pilih untuk memfaktor ulang kode Anda sesuai dengan itu. Kemudian masalah dengan nullpemeriksaan juga akan hilang. Dan pikirkan tentang menggunakan Optional.
Tom

9
Bagaimana dengan menggunakan XPath dan menyerahkannya pada evaluasi mereka?
Joop Eggen

15
Kode itu mungkin dihasilkan oleh wsdl2java, yang tidak menghormati Hukum Demeter.
Adrian Cox

Jawaban:


143

Penangkapan NullPointerExceptionadalah hal yang sangat bermasalah untuk dilakukan karena bisa terjadi hampir di mana saja. Sangat mudah untuk mendapatkannya dari bug, menangkapnya secara tidak sengaja dan melanjutkan seolah-olah semuanya normal, sehingga menyembunyikan masalah nyata. Ini sangat sulit untuk ditangani jadi yang terbaik adalah menghindari sama sekali. (Misalnya, pikirkan tentang unboxing otomatis dari sebuah null Integer.)

Saya menyarankan agar Anda menggunakan Optionalkelas sebagai gantinya. Ini sering kali merupakan pendekatan terbaik ketika Anda ingin bekerja dengan nilai-nilai yang ada atau tidak ada.

Menggunakan itu Anda bisa menulis kode Anda seperti ini:

public Optional<Integer> m(Ws wsObject) {
    return Optional.ofNullable(wsObject.getFoo()) // Here you get Optional.empty() if the Foo is null
        .map(f -> f.getBar()) // Here you transform the optional or get empty if the Bar is null
        .map(b -> b.getBaz())
        .map(b -> b.getInt());
        // Add this if you want to return null instead of an empty optional if any is null
        // .orElse(null);
        // Or this if you want to throw an exception instead
        // .orElseThrow(SomeApplicationException::new);
}

Mengapa opsional?

Menggunakan Optionals, bukannull untuk nilai yang mungkin tidak ada membuat fakta itu sangat terlihat dan jelas bagi pembaca, dan sistem tipe akan memastikan Anda tidak sengaja melupakannya.

Anda juga mendapatkan akses ke metode untuk bekerja dengan nilai-nilai seperti itu dengan lebih nyaman, seperti mapdan orElse.


Apakah ketiadaan valid atau error?

Tetapi juga pikirkan apakah itu adalah hasil yang valid untuk metode perantara untuk mengembalikan nol atau jika itu merupakan tanda kesalahan. Jika selalu merupakan kesalahan maka mungkin lebih baik melempar pengecualian daripada mengembalikan nilai khusus, atau metode perantara itu sendiri membuat pengecualian.


Mungkin lebih banyak pilihan?

Jika di sisi lain nilai yang tidak ada dari metode perantara valid, mungkin Anda dapat beralih ke Optional s untuk mereka?

Kemudian Anda bisa menggunakannya seperti ini:

public Optional<Integer> mo(Ws wsObject) {
    return wsObject.getFoo()
        .flatMap(f -> f.getBar())
        .flatMap(b -> b.getBaz())
        .flatMap(b -> b.getInt());        
}

Mengapa tidak opsional?

Satu-satunya alasan yang dapat saya pikirkan untuk tidak menggunakan Optionaladalah jika ini benar-benar merupakan bagian penting dari kinerja kode, dan jika overhead pengumpulan sampah ternyata menjadi masalah. Ini karena beberapa Optionalobjek dialokasikan setiap kali kode dijalankan, dan VM mungkin tidak dapat mengoptimalkannya. Dalam hal ini jika-tes Anda mungkin lebih baik.


Jawaban yang sangat bagus. Perlu disebutkan bahwa jika ada kondisi kegagalan yang mungkin lainnya, dan Anda perlu untuk membedakan antara mereka, Anda dapat menggunakan Trybukan Optional. Meskipun tidak ada Trydi Java API, ada banyak lib yang menyediakannya, misalnya javaslang.io , github.com/bradleyscollins/try4j , functionaljava.org atau github.com/jasongoodwin/better-java-monads
Landei

8
FClass::getBardll akan lebih pendek.
Boris the Spider

1
@BoristheSpider: Mungkin sedikit. Tapi saya biasanya lebih suka lambda daripada metode ref karena seringkali nama kelasnya lebih panjang, dan saya menemukan lambda sedikit lebih mudah dibaca.
Lii

6
@Lii cukup adil, tetapi perhatikan bahwa referensi metode mungkin sedikit lebih cepat, karena lambda mungkin memerlukan konstruksi waktu kompilasi yang lebih kompleks. Lambda akan membutuhkan pembuatan staticmetode, yang akan menimbulkan hukuman yang sangat kecil.
Boris the Spider

1
@Lii Saya benar-benar menemukan referensi metode yang lebih bersih dan lebih deskriptif, bahkan ketika mereka sedikit lebih panjang.
shmosel

14

Saya menyarankan untuk mempertimbangkan Objects.requireNonNull(T obj, String message). Anda dapat membuat rantai dengan pesan mendetail untuk setiap pengecualian, seperti

requireNonNull(requireNonNull(requireNonNull(
    wsObject, "wsObject is null")
        .getFoo(), "getFoo() is null")
            .getBar(), "getBar() is null");

Saya menyarankan Anda untuk tidak menggunakan nilai-kembali khusus, seperti -1. Itu bukan gaya Java. Java telah merancang mekanisme pengecualian untuk menghindari cara kuno yang berasal dari bahasa C.

Melempar NullPointerExceptionjuga bukan pilihan terbaik. Anda dapat memberikan pengecualian Anda sendiri (membuatnya dicentang untuk menjamin bahwa itu akan ditangani oleh pengguna atau tidak dicentang untuk memprosesnya dengan cara yang lebih mudah) atau menggunakan pengecualian tertentu dari pengurai XML yang Anda gunakan.


1
Objects.requireNonNullakhirnya melempar NullPointerException. Jadi ini tidak membuat situasinya berbeda darireturn wsObject.getFoo().getBar().getBaz().getInt()
Arka Ghosh

1
@ArkaGhosh, juga menghindari banyak hal ifseperti yang ditunjukkan OP
Andrew Tobilko

4
Ini adalah satu-satunya solusi yang waras. Semua yang lain menyarankan untuk menggunakan pengecualian untuk kontrol aliran yang merupakan bau kode. Di samping catatan: Saya menganggap metode perangkaian yang dilakukan oleh OP juga berbau. Jika dia akan bekerja dengan tiga variabel lokal dan jika sesuai situasinya akan jauh lebih jelas. Juga saya pikir masalahnya lebih dalam daripada hanya bekerja di sekitar NPE: OP harus bertanya pada dirinya sendiri mengapa getter dapat mengembalikan null. Apa arti null? Mungkin beberapa objek nol akan lebih baik? Atau crash di satu getter dengan pengecualian yang berarti? Pada dasarnya semuanya lebih baik daripada pengecualian untuk kontrol aliran.
Marius K.

1
Saran tanpa syarat untuk menggunakan pengecualian untuk menandakan tidak adanya nilai pengembalian yang valid tidak terlalu baik. Pengecualian berguna ketika metode gagal dengan cara yang sulit untuk dipulihkan oleh pemanggil dan yang lebih baik ditangani dalam pernyataan coba-tangkap di beberapa bagian lain dari program. Untuk hanya memberi sinyal tidak adanya nilai kembali, lebih baik menggunakan Optionalkelas, atau mungkin mengembalikan nullableInteger
Lii

6

Dengan asumsi struktur kelas benar-benar di luar kendali kami, seperti yang terjadi, saya pikir menangkap NPE seperti yang disarankan dalam pertanyaan memang solusi yang masuk akal, kecuali kinerja menjadi perhatian utama. Satu perbaikan kecil mungkin untuk membungkus logika lempar / tangkap untuk menghindari kekacauan:

static <T> T get(Supplier<T> supplier, T defaultValue) {
    try {
        return supplier.get();
    } catch (NullPointerException e) {
        return defaultValue;
    }
}

Sekarang Anda cukup melakukan:

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), -1);

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), "");tidak memberikan kesalahan dalam waktu kompilasi yang bisa menjadi masalah.
Philippe Gioseffi

5

Seperti yang sudah ditunjukkan oleh Tom di komentarnya,

Pernyataan berikut tidak mematuhi Hukum Demeter ,

wsObject.getFoo().getBar().getBaz().getInt()

Apa yang Anda inginkan adalah intdan Anda bisa mendapatkannya Foo. Law of Demeter mengatakan, jangan pernah berbicara dengan orang asing . Untuk kasus Anda, Anda dapat menyembunyikan implementasi aktual di bawah tenda Foodan Bar.

Sekarang, Anda dapat membuat metode dalam Foountuk mengambil intdari Baz. Pada akhirnya, Fooakan memiliki Bardan Barkita dapat mengakses Inttanpa mengekspos Bazlangsung ke Foo. Jadi, pemeriksaan null mungkin dibagi ke kelas yang berbeda dan hanya atribut yang diperlukan yang akan dibagikan di antara kelas.


4
Ini bisa diperdebatkan jika tidak mematuhi Law Of Demeter karena WsObject mungkin hanya sebuah struktur data. Lihat di sini: stackoverflow.com/a/26021695/1528880
DerM

2
@DerM Ya, itu mungkin, tetapi karena OP sudah memiliki sesuatu yang mengurai file XML-nya, dia juga dapat memikirkan tentang membuat kelas model yang sesuai untuk tag yang diperlukan, sehingga parsing library dapat memetakannya. Kemudian kelas model ini berisi logika untuk nullpemeriksaan sub tag-nya sendiri.
Tom

4

Jawaban saya hampir sama dengan @janki, tetapi saya ingin sedikit mengubah cuplikan kodenya seperti di bawah ini:

if (wsObject.getFoo() != null && wsObject.getFoo().getBar() != null && wsObject.getFoo().getBar().getBaz() != null) 
   return wsObject.getFoo().getBar().getBaz().getInt();
else
   return something or throw exception;

Anda juga dapat menambahkan pemeriksaan null wsObject, jika ada kemungkinan objek tersebut menjadi null.


4

Anda mengatakan bahwa beberapa metode "mungkin kembali null" tetapi tidak mengatakan dalam keadaan apa metode tersebut kembali null. Anda mengatakan Anda menangkapnya NullPointerExceptiontetapi Anda tidak mengatakan mengapa Anda menangkapnya. Kurangnya informasi ini menunjukkan bahwa Anda tidak memiliki pemahaman yang jelas tentang apa pengecualian itu dan mengapa mereka lebih unggul daripada alternatif.

Pertimbangkan metode kelas yang dimaksudkan untuk melakukan suatu tindakan, tetapi metode tersebut tidak dapat menjamin akan melakukan tindakan tersebut, karena keadaan di luar kendalinya (yang sebenarnya merupakan kasus untuk semua metode di Java ). Kami memanggil metode itu dan kembali. Kode yang memanggil metode itu perlu mengetahui apakah itu berhasil. Bagaimana dia bisa tahu? Bagaimana itu bisa disusun untuk mengatasi dua kemungkinan, sukses atau gagal?

Menggunakan pengecualian, kita dapat menulis metode yang berhasil sebagai kondisi posting . Jika metode kembali, itu berhasil. Jika mengeluarkan pengecualian, itu gagal. Ini adalah kemenangan besar untuk kejelasan. Kita dapat menulis kode yang memproses kasus normal, kasus sukses, dan memindahkan semua kode penanganan kesalahan ke dalam catchklausa dengan jelas. Sering kali terjadi bahwa detail tentang bagaimana atau mengapa suatu metode tidak berhasil tidak penting bagi pemanggil, jadi catchklausa yang sama dapat digunakan untuk menangani beberapa jenis kegagalan. Dan sering terjadi bahwa metode tidak perlu pengecualian menangkap sama sekali , tapi hanya dapat memungkinkan mereka untuk menyebarkan ke nya pemanggil. Pengecualian karena bug program ada di kelas terakhir itu; beberapa metode dapat bereaksi dengan tepat saat ada bug.

Jadi, metode itu kembali null.

  • Apakah nullnilai menunjukkan bug dalam kode Anda? Jika ya, Anda tidak boleh menangkap pengecualian sama sekali. Dan kode Anda tidak boleh mencoba menebak-nebak sendiri. Tulis saja apa yang jelas dan ringkas dengan asumsi itu akan berhasil. Apakah rangkaian panggilan metode jelas dan ringkas? Kemudian gunakan saja.
  • Apakah suatu nullnilai menunjukkan input yang tidak valid untuk program Anda? Jika ya, a NullPointerExceptionbukanlah pengecualian yang tepat untuk dilemparkan, karena secara konvensional itu disediakan untuk menunjukkan bug. Anda mungkin ingin menampilkan pengecualian khusus yang diturunkan dari IllegalArgumentException(jika Anda ingin pengecualian tidak dicentang ) atau IOException(jika Anda ingin pengecualian dicentang). Apakah program Anda diharuskan untuk memberikan pesan kesalahan sintaks yang terperinci ketika ada input yang tidak valid? Jika demikian, memeriksa setiap metode untuk nullnilai kembalian kemudian melemparkan pengecualian diagnostik yang sesuai adalah satu-satunya hal yang dapat Anda lakukan. Jika program Anda tidak perlu menyediakan diagnosis mendetail, merangkai panggilan metode bersama-sama, menangkap apa saja NullPointerException, lalu membuang pengecualian khusus Anda adalah yang paling jelas dan ringkas.

Salah satu jawaban mengklaim bahwa panggilan metode berantai melanggar Hukum Demeter dan karenanya buruk. Klaim itu salah.

  • Dalam hal desain program, sebenarnya tidak ada aturan mutlak tentang apa yang baik dan buruk. Hanya ada heuristik: aturan yang benar (bahkan hampir semua) sepanjang waktu. Bagian dari keterampilan pemrograman adalah mengetahui kapan boleh melanggar aturan semacam itu. Jadi pernyataan singkat bahwa "ini bertentangan dengan aturan X " sebenarnya bukanlah jawaban sama sekali. Apakah ini salah satu situasi di mana aturan seharusnya dilanggar?
  • The Law of Demeter benar-benar aturan tentang API atau desain antarmuka kelas. Saat mendesain kelas, ada gunanya memiliki hierarki abstraksi. Anda memiliki kelas tingkat rendah yang menggunakan bahasa primitif untuk secara langsung melakukan operasi dan mewakili objek dalam abstraksi yang tingkatnya lebih tinggi daripada bahasa primitif. Anda memiliki kelas tingkat menengah yang mendelegasikan ke kelas tingkat rendah, dan mengimplementasikan operasi dan representasi di tingkat yang lebih tinggi daripada kelas tingkat rendah. Anda memiliki kelas tingkat tinggi yang mendelegasikan ke kelas tingkat menengah, dan mengimplementasikan operasi dan abstraksi tingkat yang lebih tinggi. (Saya telah berbicara tentang tiga tingkat abstraksi di sini, tetapi lebih banyak lagi yang mungkin). Ini memungkinkan kode Anda untuk mengekspresikan dirinya dalam abstraksi yang sesuai di setiap level, sehingga menyembunyikan kompleksitas. Dasar pemikiran Hukum Demeteradalah jika Anda memiliki rangkaian pemanggilan metode, itu menunjukkan Anda memiliki kelas tingkat tinggi yang menjangkau melalui kelas tingkat menengah untuk menangani secara langsung detail tingkat rendah, dan oleh karena itu kelas tingkat menengah Anda belum menyediakan operasi abstrak tingkat menengah yang dibutuhkan kelas tingkat tinggi. Tetapi tampaknya bukan itu situasi yang Anda hadapi di sini: Anda tidak merancang kelas dalam rantai panggilan metode, itu adalah hasil dari beberapa kode serialisasi XML yang dibuat secara otomatis (benar?), Dan rantai panggilan tidak menurun melalui hierarki abstraksi karena XML des-serialisasi semuanya berada pada tingkat yang sama dari hierarki abstraksi (benar?)?

3

Untuk meningkatkan keterbacaan, Anda mungkin ingin menggunakan banyak variabel, seperti

Foo theFoo;
Bar theBar;
Baz theBaz;

theFoo = wsObject.getFoo();

if ( theFoo == null ) {
  // Exit.
}

theBar = theFoo.getBar();

if ( theBar == null ) {
  // Exit.
}

theBaz = theBar.getBaz();

if ( theBaz == null ) {
  // Exit.
}

return theBaz.getInt();

Ini jauh lebih sulit dibaca menurut saya. Itu mengotori metode dengan sejumlah besar logika pemeriksaan nol yang sama sekali tidak relevan dari logika metode yang sebenarnya.
Pengembang102938

2

Jangan tangkap NullPointerException. Anda tidak tahu dari mana asalnya (saya tahu itu tidak mungkin terjadi dalam kasus Anda, tetapi mungkin ada hal lain yang melemparkannya) dan lambat. Anda ingin mengakses bidang yang ditentukan dan untuk ini setiap bidang lainnya tidak boleh kosong. Ini adalah alasan valid yang sempurna untuk memeriksa setiap bidang. Saya mungkin akan memeriksanya di satu jika dan kemudian membuat metode untuk keterbacaan. Seperti yang ditunjukkan orang lain sudah kembali -1 adalah sekolah yang sangat tua tetapi saya tidak tahu apakah Anda punya alasan untuk itu atau tidak (misalnya berbicara dengan sistem lain).

public int callService() {
    ...
    if(isValid(wsObject)){
        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    return -1;
}


public boolean isValid(WsObject wsObject) {
    if(wsObject.getFoo() != null &&
        wsObject.getFoo().getBar() != null &&
        wsObject.getFoo().getBar().getBaz() != null) {
        return true;
    }
    return false;
}

Sunting: Ini bisa diperdebatkan jika tidak mematuhi Law Of Demeter karena WsObject mungkin hanya sebuah struktur data (periksa https://stackoverflow.com/a/26021695/1528880 ).


2

Jika Anda tidak ingin memfaktor ulang kode dan Anda dapat menggunakan Java 8, Anda dapat menggunakan referensi Metode.

Demo sederhana terlebih dahulu (maafkan kelas dalam statis)

public class JavaApplication14 
{
    static class Baz
    {
        private final int _int;
        public Baz(int value){ _int = value; }
        public int getInt(){ return _int; }
    }
    static class Bar
    {
        private final Baz _baz;
        public Bar(Baz baz){ _baz = baz; }
        public Baz getBar(){ return _baz; }   
    }
    static class Foo
    {
        private final Bar _bar;
        public Foo(Bar bar){ _bar = bar; }
        public Bar getBar(){ return _bar; }   
    }
    static class WSObject
    {
        private final Foo _foo;
        public WSObject(Foo foo){ _foo = foo; }
        public Foo getFoo(){ return _foo; }
    }
    interface Getter<T, R>
    {
        R get(T value);
    }

    static class GetterResult<R>
    {
        public R result;
        public int lastIndex;
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) 
    {
        WSObject wsObject = new WSObject(new Foo(new Bar(new Baz(241))));
        WSObject wsObjectNull = new WSObject(new Foo(null));

        GetterResult<Integer> intResult
                = getterChain(wsObject, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);

        GetterResult<Integer> intResult2
                = getterChain(wsObjectNull, WSObject::getFoo, Foo::getBar, Bar::getBar, Baz::getInt);


        System.out.println(intResult.result);
        System.out.println(intResult.lastIndex);

        System.out.println();
        System.out.println(intResult2.result);
        System.out.println(intResult2.lastIndex);

        // TODO code application logic here
    }

    public static <R, V1, V2, V3, V4> GetterResult<R>
            getterChain(V1 value, Getter<V1, V2> g1, Getter<V2, V3> g2, Getter<V3, V4> g3, Getter<V4, R> g4)
            {
                GetterResult result = new GetterResult<>();

                Object tmp = value;


                if (tmp == null)
                    return result;
                tmp = g1.get((V1)tmp);
                result.lastIndex++;


                if (tmp == null)
                    return result;
                tmp = g2.get((V2)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g3.get((V3)tmp);
                result.lastIndex++;

                if (tmp == null)
                    return result;
                tmp = g4.get((V4)tmp);
                result.lastIndex++;


                result.result = (R)tmp;

                return result;
            }
}

Keluaran

241
4

nol
2

Antarmuka Getterhanyalah antarmuka fungsional, Anda dapat menggunakan yang setara.
GetterResultkelas, pengakses dilucuti untuk kejelasan, memegang hasil dari rantai pengambil, jika ada, atau indeks pengambil terakhir yang dipanggil.

Metode getterChainini adalah potongan kode sederhana yang dapat dibuat secara otomatis (atau secara manual jika diperlukan).
Saya menyusun kodenya sehingga blok yang berulang menjadi bukti dengan sendirinya.


Ini bukan solusi yang tepat karena Anda masih perlu menentukan satu kelebihan muatan getterChain per jumlah getter.

Saya akan merefaktor kode sebagai gantinya, tetapi jika tidak bisa dan Anda menemukan diri Anda menggunakan rantai pengambil panjang sering Anda dapat mempertimbangkan untuk membangun kelas dengan beban berlebih yang mengambil dari 2 menjadi, katakanlah, 10, pengambil.


2

Seperti yang dikatakan orang lain, menghormati Hukum Demeter jelas merupakan bagian dari solusi. Bagian lain, jika memungkinkan, adalah mengubah metode yang dirantai tersebut sehingga tidak dapat kembali null. Anda dapat menghindari kembali nulldengan mengembalikan objek kosong String, kosong Collection, atau objek dummy lain yang berarti atau melakukan apa pun yang akan dilakukan pemanggil null.


2

Saya ingin menambahkan jawaban yang berfokus pada arti kesalahan . Pengecualian nol itu sendiri tidak memberikan arti penuh kesalahan. Jadi saya menyarankan untuk menghindari berurusan dengan mereka secara langsung.

Ada ribuan kasus di mana kode Anda bisa salah: tidak dapat terhubung ke database, IO Exception, Network error ... Jika Anda menanganinya satu per satu (seperti centang null di sini), itu akan terlalu merepotkan.

Di dalam kode:

wsObject.getFoo().getBar().getBaz().getInt();

Bahkan ketika Anda tahu bidang mana yang nol, Anda tidak tahu apa yang salah. Mungkin Bar adalah null, tetapi apakah itu diharapkan? Atau apakah itu kesalahan data? Pikirkan tentang orang yang membaca kode Anda

Seperti dalam jawaban xenteros, saya akan mengusulkan menggunakan pengecualian khusus yang tidak dicentang . Misalnya, dalam situasi ini: Foo bisa menjadi null (data valid), tetapi Bar dan Baz tidak boleh null (data tidak valid)

Kode dapat ditulis ulang:

void myFunction()
{
    try 
    {
        if (wsObject.getFoo() == null)
        {
          throw new FooNotExistException();
        }

        return wsObject.getFoo().getBar().getBaz().getInt();
    }
    catch (Exception ex)
    {
        log.error(ex.Message, ex); // Write log to track whatever exception happening
        throw new OperationFailedException("The requested operation failed")
    }
}


void Main()
{
    try
    {
        myFunction();
    }
    catch(FooNotExistException)
    {
        // Show error: "Your foo does not exist, please check"
    }
    catch(OperationFailedException)
    {
        // Show error: "Operation failed, please contact our support"
    }
}

Pengecualian yang tidak dicentang menunjukkan bahwa pemrogram menyalahgunakan API. Masalah eksternal seperti "tidak dapat terhubung ke database, IO Exception, Network error" harus ditunjukkan dengan pengecualian yang dicentang.
Kevin Krumwiede

Itu sangat tergantung pada kebutuhan pemanggil. Memeriksa bantuan pengecualian karena memaksa Anda untuk memproses kesalahan. Namun, dalam kasus lain, ini tidak diperlukan dan dapat mencemari kode. Misalnya, Anda memiliki IOException di lapisan Data Anda, akankah Anda membuangnya ke lapisan Presentasi? Itu berarti Anda harus menangkap pengecualian dan melempar ulang ke setiap penelepon. Saya lebih suka membungkus IOException dengan BusinessException kustom, dengan pesan yang relevan, dan membiarkannya muncul melalui stacktrace, sampai filter global menangkapnya dan menampilkan pesan tersebut kepada pengguna.
HoĂ ng Long

Penelepon tidak perlu menangkap dan melempar kembali pengecualian yang dicentang, cukup nyatakan bahwa pengecualian tersebut akan dilempar.
Kevin Krumwiede

@KevinKrumwiede: Anda benar, kami hanya perlu mendeklarasikan pengecualian yang akan dilempar. Kami masih perlu menyatakannya. Sunting: Melihat kedua kalinya, ada cukup banyak perdebatan tentang penggunaan pengecualian yang dicentang vs yang tidak dicentang (misalnya: programmers.stackexchange.com/questions/121328/… ).
HoĂ ng Long

2

NullPointerException adalah pengecualian waktu proses, jadi secara umum tidak disarankan untuk menangkapnya, tetapi untuk menghindarinya.

Anda harus menangkap pengecualian di mana pun Anda ingin memanggil metode (atau itu akan menyebarkan tumpukan). Namun demikian, jika dalam kasus Anda, Anda dapat terus bekerja dengan hasil itu dengan nilai -1 dan Anda yakin itu tidak akan menyebar karena Anda tidak menggunakan salah satu "potongan" yang mungkin nol, maka tampaknya tepat bagi saya untuk tangkap

Edit:

Saya setuju dengan jawaban selanjutnya dari @xenteros, akan lebih baik untuk meluncurkan pengecualian Anda sendiri daripada mengembalikan -1 Anda dapat memanggilnya InvalidXMLExceptionmisalnya.


3
Apa yang Anda maksud dengan "tidak masalah jika Anda menangkapnya, itu dapat menyebar ke bagian lain dari kode"?
Hulk

Jika null ada dalam kalimat ini wsObject.getFoo () Dan di bagian kode selanjutnya Anda menjalankan lagi kueri itu atau menggunakan wsObject.getFoo (). GetBar () (misalnya) itu akan memunculkan lagi NullPointerException.
SCouto

Itu adalah kata-kata yang tidak biasa untuk "Anda harus menangkap pengecualian di mana pun Anda ingin memanggil metode (atau itu akan menyebarkan tumpukan)." jika saya mengerti dengan benar. Saya setuju dengan itu (dan itu mungkin menjadi masalah), saya hanya menemukan kata-katanya yang membingungkan.
Hulk

Saya akan memperbaikinya, maaf, bahasa Inggris bukan bahasa pertama saya jadi terkadang hal ini mungkin terjadi :) Terima kasih
SCouto

2

Telah mengikuti posting ini sejak kemarin.

Saya telah mengomentari / memberi suara pada komentar yang mengatakan, menangkap NPE itu buruk. Inilah mengapa saya melakukan itu.

package com.todelete;

public class Test {
    public static void main(String[] args) {
        Address address = new Address();
        address.setSomeCrap(null);
        Person person = new Person();
        person.setAddress(address);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            try {
                System.out.println(person.getAddress().getSomeCrap().getCrap());
            } catch (NullPointerException npe) {

            }
        }
        long endTime = System.currentTimeMillis();
        System.out.println((endTime - startTime) / 1000F);
        long startTime1 = System.currentTimeMillis();
        for (int i = 0; i < 1000000; i++) {
            if (person != null) {
                Address address1 = person.getAddress();
                if (address1 != null) {
                    SomeCrap someCrap2 = address1.getSomeCrap();
                    if (someCrap2 != null) {
                        System.out.println(someCrap2.getCrap());
                    }
                }
            }
        }
        long endTime1 = System.currentTimeMillis();
        System.out.println((endTime1 - startTime1) / 1000F);
    }
}

  public class Person {
    private Address address;

    public Address getAddress() {
        return address;
    }

    public void setAddress(Address address) {
        this.address = address;
    }
}

package com.todelete;

public class Address {
    private SomeCrap someCrap;

    public SomeCrap getSomeCrap() {
        return someCrap;
    }

    public void setSomeCrap(SomeCrap someCrap) {
        this.someCrap = someCrap;
    }
}

package com.todelete;

public class SomeCrap {
    private String crap;

    public String getCrap() {
        return crap;
    }

    public void setCrap(String crap) {
        this.crap = crap;
    }
}

Keluaran

3.216

0,002

Saya melihat pemenang yang jelas di sini. Memiliki pemeriksaan if jauh lebih murah daripada menangkap pengecualian. Saya telah melihat cara kerja Java-8 itu. Mengingat 70% dari aplikasi saat ini masih berjalan di Java-7, saya menambahkan jawaban ini.

Intinya Untuk aplikasi misi kritis apa pun, menangani NPE itu mahal.


Tiga detik tambahan untuk satu juta permintaan dalam kasus terburuk dapat diukur, tetapi jarang menjadi pemecah kesepakatan, bahkan dalam "aplikasi penting misi". Ada sistem di mana menambahkan 3,2 mikrodetik ke permintaan adalah masalah besar, dan jika Anda memiliki sistem seperti itu, pikirkan baik-baik tentang pengecualian. Tetapi memanggil layanan web dan menghilangkan derialisasi keluarannya, sesuai pertanyaan awal, mungkin membutuhkan waktu lebih lama dari itu, dan mengkhawatirkan kinerja penanganan pengecualian adalah hal yang tidak penting.
Jeroen Mostert

@JeroenMostert: 3 Detik per cek / Juta. Jadi, jumlah pemeriksaan akan meningkatkan biaya
Pengguna Baru

Benar. Meski dengan itu aku masih menganggapnya sebagai kasus "profil dulu". Anda akan membutuhkan lebih dari 300 pemeriksaan dalam satu permintaan sebelum permintaan membutuhkan ekstra milidetik penuh. Pertimbangan desain akan membebani jiwa saya lebih cepat dari itu.
Jeroen Mostert

@JeroenMostert: :) Setuju! Saya ingin menyerahkan hasilnya kepada programmer dan membiarkan mereka menerima telepon!
Pengguna Baru

1

Jika efisiensi menjadi masalah maka opsi 'tangkap' harus dipertimbangkan. Jika 'catch' tidak dapat digunakan karena akan menyebar (seperti yang disebutkan oleh 'SCouto') maka gunakan variabel lokal untuk menghindari beberapa panggilan ke metode getFoo(), getBar()dan getBaz().


1

Sebaiknya pertimbangkan untuk membuat Pengecualian Anda sendiri. Sebut saja MyOperationFailedException. Anda dapat membuangnya alih-alih mengembalikan nilai. Hasilnya akan sama - Anda akan keluar dari fungsinya, tetapi Anda tidak akan mengembalikan nilai hard-code -1 yang merupakan anti-pola Java. Di Jawa kami menggunakan Pengecualian.

try {
    return wsObject.getFoo().getBar().getBaz().getInt();
} catch (NullPointerException ignored) {
    throw new MyOperationFailedException();
}

EDIT:

Menurut diskusi di komentar izinkan saya menambahkan sesuatu ke pemikiran saya sebelumnya. Dalam kode ini ada dua kemungkinan. Salah satunya adalah Anda menerima nol dan yang lainnya adalah, bahwa itu adalah kesalahan.

Jika ini adalah kesalahan dan itu terjadi, Anda dapat men-debug kode Anda menggunakan struktur lain untuk tujuan debugging ketika breakpoint tidak cukup.

Jika dapat diterima, Anda tidak peduli di mana null ini muncul. Jika ya, Anda pasti tidak boleh merantai permintaan tersebut.


2
Tidakkah menurut Anda menekan pengecualian itu adalah ide yang buruk? Dalam waktu nyata, jika kita kehilangan jejak pengecualian, rasa sakit yang nyata di bagian bawah untuk mengetahui apa yang sedang terjadi! Saya selalu menyarankan untuk tidak menggunakan rantai. Masalah kedua yang saya lihat adalah: Kode ini tidak dapat diberikan kepada penerima pada titik waktu, yang mana hasilnya adalah nol.
Pengguna Baru

Tidak, Pengecualian Anda dapat memiliki pesan yang pasti akan menunjukkan tempat pesan itu dilemparkan. Saya setuju merangkai bukanlah solusi terbaik :)
xenteros

3
Tidak, itu hanya tentang nomor baris. Jadi, salah satu panggilan dalam rantai tersebut dapat menyebabkan pengecualian.
Pengguna Baru

"Jika ini adalah kesalahan dan itu terjadi, Anda dapat men-debug kode Anda" - bukan dalam produksi. Saya lebih suka tahu APA yang gagal ketika yang saya miliki hanyalah sebatang kayu daripada mencoba menebak apa yang terjadi yang menyebabkannya gagal. Dengan saran itu (dan kode itu), yang benar-benar Anda ketahui adalah bahwa salah satu dari 4 hal itu null, tetapi bukan yang mana atau mengapa.
VLAZ

1

Metode yang Anda miliki sangat panjang, tetapi sangat mudah dibaca. Jika saya adalah pengembang baru yang datang ke basis kode Anda, saya dapat melihat apa yang Anda lakukan dengan cukup cepat. Sebagian besar jawaban lain (termasuk menangkap pengecualian) tampaknya tidak membuat segala sesuatunya lebih mudah dibaca dan beberapa membuatnya kurang dapat dibaca menurut saya.

Mengingat bahwa Anda kemungkinan tidak memiliki kendali atas sumber yang dihasilkan dan dengan asumsi Anda benar-benar hanya perlu mengakses beberapa bidang bersarang di sana-sini, saya akan merekomendasikan untuk membungkus setiap akses yang sangat bersarang dengan sebuah metode.

private int getFooBarBazInt() {
    if (wsObject.getFoo() == null) return -1;
    if (wsObject.getFoo().getBar() == null) return -1;
    if (wsObject.getFoo().getBar().getBaz() == null) return -1;
    return wsObject.getFoo().getBar().getBaz().getInt();
}

Jika Anda menemukan diri Anda menulis banyak metode ini atau jika Anda tergoda untuk membuat metode statis publik ini maka saya akan membuat model objek terpisah, bersarang seperti yang Anda inginkan, hanya dengan bidang yang Anda pedulikan, dan mengonversi dari web model objek layanan ke model objek Anda.

Saat Anda berkomunikasi dengan layanan web jarak jauh, biasanya memiliki "domain jauh" dan "domain aplikasi" dan beralih di antara keduanya. Domain jarak jauh sering kali dibatasi oleh protokol web (misalnya, Anda tidak dapat mengirim metode pembantu bolak-balik dalam layanan RESTful murni dan model objek bertingkat sangat umum untuk menghindari beberapa panggilan API) sehingga tidak ideal untuk penggunaan langsung di klien Anda.

Sebagai contoh:

public static class MyFoo {

    private int barBazInt;

    public MyFoo(Foo foo) {
        this.barBazInt = parseBarBazInt();
    }

    public int getBarBazInt() {
        return barBazInt;
    }

    private int parseFooBarBazInt(Foo foo) {
        if (foo() == null) return -1;
        if (foo().getBar() == null) return -1;
        if (foo().getBar().getBaz() == null) return -1;
        return foo().getBar().getBaz().getInt();
    }

}

1
return wsObject.getFooBarBazInt();

dengan menerapkan Hukum Demeter,

class WsObject
{
    FooObject foo;
    ..
    Integer getFooBarBazInt()
    {
        if(foo != null) return foo.getBarBazInt();
        else return null;
    }
}

class FooObject
{
    BarObject bar;
    ..
    Integer getBarBazInt()
    {
        if(bar != null) return bar.getBazInt();
        else return null;
    }
}

class BarObject
{
    BazObject baz;
    ..
    Integer getBazInt()
    {
        if(baz != null) return baz.getInt();
        else return null;
    }
}

class BazObject
{
    Integer myInt;
    ..
    Integer getInt()
    {
        return myInt;
    }
}

0

Memberi jawaban yang nampaknya berbeda dari yang lainnya.

Saya sarankan Anda untuk check- NULLin if.

Alasan:

Kita tidak boleh menyisakan satu kesempatan pun agar program kita macet. NullPointer dibuat oleh sistem. Perilaku pengecualian yang dihasilkan Sistem tidak dapat diprediksi . Anda tidak boleh meninggalkan program Anda di tangan Sistem ketika Anda sudah memiliki cara untuk menanganinya sendiri. Dan menempatkan mekanisme penanganan Exception untuk keamanan ekstra. !!

Untuk membuat kode Anda mudah dibaca, coba ini untuk memeriksa ketentuan:

if (wsObject.getFoo() == null || wsObject.getFoo().getBar() == null || wsObject.getFoo().getBar().getBaz() == null) 
   return -1;
else 
   return wsObject.getFoo().getBar().getBaz().getInt();

EDIT:

Di sini Anda perlu menyimpan nilai-nilai ini wsObject.getFoo(), wsObject.getFoo().getBar() , wsObject.getFoo().getBar().getBaz()dalam beberapa variabel. Saya tidak melakukannya karena saya tidak tahu jenis kembalian dari fungsi itu.

Setiap saran akan dihargai .. !!


Apakah Anda menganggap getFoo () sebagai operasi yang sangat memakan waktu? Anda harus menyimpan nilai yang dikembalikan dalam variabel, namun itu hanya membuang-buang memori. Metode Anda sempurna untuk pemrograman C.
xenteros

tapi kadang lebih baik terlambat 1 milidetik kemudian program macet @xenteros .. !!
Janki Gadhiya

getFoo () mungkin mendapatkan nilai dari server yang berlokasi di benua lain. Itu dapat berlangsung kapan saja: menit / jam ...
xenteros

wsObjectakan berisi nilai yang dikembalikan dari Webservice .. !! Service akan dipanggil dan wsObjectakan mendapatkan XMLdata yang panjang sebagai respon webservice .. !! Jadi tidak ada yang seperti server yang terletak di benua lain karena getFoo()hanyalah sebuah metode pengambil elemen bukan panggilan Webservice .. !! @xenteros
Jank Gadhiya

1
Nah dari nama-nama pengambil saya akan berasumsi bahwa mereka mengembalikan objek Foo, Bar dan Baz: P Pertimbangkan juga untuk menghapus keamanan ganda yang disebutkan dari jawaban Anda. Saya tidak berpikir itu memberikan nilai nyata selain polusi kode. Dengan variabel lokal yang waras dan pemeriksaan nol, kami telah melakukan lebih dari cukup untuk memastikan kebenaran kode. Jika pengecualian mungkin terjadi, itu harus diperlakukan sebagai satu kesatuan.
Marius K.

0

Saya menulis kelas yang disebut Snagyang memungkinkan Anda menentukan jalur untuk menavigasi melalui pohon objek. Berikut ini contoh penggunaannya:

Snag<Car, String> ENGINE_NAME = Snag.createForAndReturn(Car.class, String.class).toGet("engine.name").andReturnNullIfMissing();

Artinya, instance tersebut ENGINE_NAMEakan secara efektif memanggil Car?.getEngine()?.getName()instance yang diteruskan kepadanya, dan dikembalikan nulljika ada referensi yang dikembalikan null:

final String name =  ENGINE_NAME.get(firstCar);

Ini tidak dipublikasikan di Maven tetapi jika ada yang menganggap ini berguna, ini ada di sini (tentu saja tanpa jaminan!)

Ini agak mendasar tetapi tampaknya melakukan pekerjaan itu. Jelas itu lebih usang dengan versi Java yang lebih baru dan bahasa JVM lain yang mendukung navigasi aman atau Optional.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.