Chuỗi kiểm tra Null và bắt NullPulumException


118

Một dịch vụ web trả về một XML khổng lồ và tôi cần truy cập vào các trường được lồng sâu của nó. Ví dụ:

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

Vấn đề là getFoo(), getBar(), getBaz()có thể tất cả trở lại null.

Tuy nhiên, nếu tôi kiểm tra nulltrong mọi trường hợp, mã sẽ trở nên rất dài dòng và khó đọc. Hơn nữa, tôi có thể bỏ lỡ kiểm tra cho một số lĩnh vực.

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();

Viết có được chấp nhận không

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

hoặc đó sẽ được coi là một antipotype?


29
Tôi sẽ không quan tâm đến việc nullkiểm tra nhiều như vậy, vì wsObject.getFoo().getBar().getBaz().getInt()đã là một mùi mã. Đọc "Luật của Demeter" là gì và thích cấu trúc lại mã của bạn cho phù hợp. Sau đó, vấn đề với các nullkiểm tra cũng sẽ biến mất. Và suy nghĩ về việc sử dụng Optional.
Tom

9
Điều gì về việc sử dụng XPath và để nó đánh giá của họ?
Eggen

15
Mã đó có thể được tạo ra bởi wsdl2java, không có sự tôn trọng đối với Luật Demeter.
Adrian Cox

Câu trả lời:


143

Bắt NullPointerExceptionlà một điều thực sự có vấn đề vì chúng có thể xảy ra ở hầu hết mọi nơi. Rất dễ dàng để có được một từ một lỗi, bắt nó một cách tình cờ và tiếp tục như thể mọi thứ đều bình thường, do đó che giấu một vấn đề thực sự. Thật khó khăn để đối phó với nó vì vậy tốt nhất là tránh hoàn toàn. (Ví dụ: suy nghĩ về việc tự động hủy hộp thư của null Integer.)

Tôi đề nghị bạn sử dụng Optionallớp thay thế. Đây thường là cách tiếp cận tốt nhất khi bạn muốn làm việc với các giá trị hiện diện hoặc vắng mặt.

Sử dụng mà bạn có thể viết mã của bạn như thế này:

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);
}

Tại sao không bắt buộc?

Sử dụng Optionals thay vì nullcho các giá trị có thể vắng mặt làm cho thực tế đó rất rõ ràng và rõ ràng đối với người đọc và hệ thống loại sẽ đảm bảo bạn không vô tình quên nó.

Bạn cũng có quyền truy cập vào các phương thức để làm việc với các giá trị như vậy thuận tiện hơn, thích maporElse.


Sự vắng mặt hợp lệ hay lỗi?

Nhưng cũng nghĩ xem liệu đó có phải là kết quả hợp lệ để các phương thức trung gian trả về null hoặc nếu đó là dấu hiệu của lỗi. Nếu nó luôn luôn là một lỗi thì có lẽ tốt hơn là ném một ngoại lệ hơn là trả về một giá trị đặc biệt hoặc cho chính các phương thức trung gian để ném một ngoại lệ.


Có thể nhiều lựa chọn hơn?

Nếu mặt khác các giá trị vắng mặt trong các phương thức trung gian là hợp lệ, có lẽ bạn cũng có thể chuyển sang Optionals cho chúng không?

Sau đó, bạn có thể sử dụng chúng như thế này:

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

Tại sao không bắt buộc?

Lý do duy nhất tôi có thể nghĩ đến cho việc không sử dụng Optionallà nếu đây là phần quan trọng thực sự của mã và nếu chi phí thu gom rác hóa ra là một vấn đề. Điều này là do một vài Optionalđối tượng được phân bổ mỗi khi mã được thực thi và VM có thể không thể tối ưu hóa chúng. Trong trường hợp đó, bài kiểm tra if gốc của bạn có thể tốt hơn.


Câu trả lời rất hay. Cần phải đề cập rằng nếu có các điều kiện thất bại có thể khác, và bạn cần phân biệt giữa chúng, bạn có thể sử dụng Trythay vì Optional. Mặc dù không có TryAPI Java, nhưng có rất nhiều lib cung cấp một, ví dụ như javaslang.io , github.com/bradleyscollins/try4j , functionjava.org hoặc github.com/jasongoodwin/better-java-monads
Landei

8
FClass::getBarvv sẽ ngắn hơn.
nhện của

1
@BoristheSpider: Có thể một chút. Nhưng tôi thường thích lambdas hơn các phương thức ref vì thường tên lớp dài hơn nhiều và tôi thấy lambdas dễ đọc hơn một chút.
Lii

6
@Lii đủ công bằng, nhưng lưu ý rằng một tham chiếu phương thức có thể nhanh hơn một chút, vì lambda có thể yêu cầu các cấu trúc thời gian biên dịch phức tạp hơn. Lambda sẽ yêu cầu tạo ra một staticphương thức, sẽ phải chịu một hình phạt rất nhỏ.
nhện của

1
@Lii Tôi thực sự tìm thấy các tham chiếu phương thức để sạch hơn và mô tả hơn, ngay cả khi chúng dài hơn một chút.
shmosel

14

Tôi đề nghị xem xét Objects.requireNonNull(T obj, String message). Bạn có thể xây dựng chuỗi với thông báo chi tiết cho từng ngoại lệ, như

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

Tôi sẽ đề nghị bạn không sử dụng các giá trị trả về đặc biệt, như -1. Đó không phải là phong cách Java. Java đã thiết kế cơ chế ngoại lệ để tránh cách thức lỗi thời này xuất phát từ ngôn ngữ C.

Ném NullPointerExceptioncũng không phải là lựa chọn tốt nhất. Bạn có thể cung cấp ngoại lệ của riêng mình (làm cho nó được kiểm tra để đảm bảo rằng nó sẽ được xử lý bởi người dùng hoặc không được kiểm tra để xử lý nó theo cách dễ dàng hơn) hoặc sử dụng một ngoại lệ cụ thể từ trình phân tích cú pháp XML mà bạn đang sử dụng.


1
Objects.requireNonNullcuối cùng ném NullPointerException. Vì vậy, điều này không làm cho tình huống trở nên khác hơnreturn wsObject.getFoo().getBar().getBaz().getInt()
Arka Ghosh

1
@ArkaGhosh, cũng tránh được nhiều thứ ifnhư OP đã thể hiện
Andrew Tobilko

4
Đây là giải pháp lành mạnh duy nhất. Tất cả những người khác khuyên nên sử dụng ngoại lệ để kiểm soát luồng là mùi mã. Một lưu ý phụ: Tôi coi phương pháp xâu chuỗi được thực hiện bởi OP là một mùi. Nếu anh ta làm việc với ba biến cục bộ và tương ứng nếu tình hình sẽ rõ ràng hơn nhiều. Ngoài ra tôi nghĩ vấn đề còn sâu sắc hơn là chỉ làm việc xung quanh một NPE: OP nên tự hỏi tại sao các getters có thể trả về null. Null có nghĩa là gì? Có lẽ một số đối tượng null sẽ tốt hơn? Hoặc sụp đổ trong một getter với một ngoại lệ có ý nghĩa? Về cơ bản mọi thứ đều tốt hơn ngoại lệ cho kiểm soát dòng chảy.
Marius K.

1
Lời khuyên vô điều kiện để sử dụng các ngoại lệ để báo hiệu sự vắng mặt của giá trị trả lại hợp lệ là không tốt. Các ngoại lệ rất hữu ích khi một phương thức thất bại theo cách mà người gọi khó có thể phục hồi và được xử lý tốt hơn trong một câu lệnh try-Catch-in trong một số phần khác của chương trình. Để đơn giản báo hiệu sự vắng mặt của giá trị trả về, tốt hơn là sử dụng Optionallớp hoặc có thể trả về giá trị Integer
null

6

Giả sử cấu trúc lớp thực sự nằm ngoài tầm kiểm soát của chúng tôi, dường như là trường hợp, tôi nghĩ rằng việc bắt NPE như đề xuất trong câu hỏi thực sự là một giải pháp hợp lý, trừ khi hiệu suất là mối quan tâm chính. Một cải tiến nhỏ có thể là bọc logic ném / bắt để tránh lộn xộn:

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

Bây giờ bạn có thể chỉ cần làm:

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

return get(() -> wsObject.getFoo().getBar().getBaz().getInt(), "");không đưa ra một lỗi trong thời gian biên dịch có thể có vấn đề.
Philippe Gioseffi

5

Như đã được Tom chỉ ra trong bình luận,

Sau tuyên bố không tuân theo Luật Demeter ,

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

Những gì bạn muốn là intvà bạn có thể lấy nó từ Foo. Law of Demeter nói, đừng bao giờ nói chuyện với người lạ . Đối với trường hợp của bạn, bạn có thể ẩn việc thực hiện thực tế dưới mui xe của FooBar.

Bây giờ, bạn có thể tạo phương thức Foođể tìm nạp inttừ Baz. Cuối cùng, chúng ta Foosẽ có BarBartruy cập Intmà không cần Baztiếp xúc trực tiếp Foo. Vì vậy, kiểm tra null có thể được chia cho các lớp khác nhau và chỉ các thuộc tính bắt buộc mới được chia sẻ giữa các lớp.


4
Thật đáng tranh luận nếu nó không tuân theo Luật của Demeter vì WsObject có lẽ chỉ là một cấu trúc dữ liệu. Xem tại đây: stackoverflow.com/a/26021695/1528880
DerM

2
@DerM Có, điều đó là có thể, nhưng vì OP đã có một cái gì đó phân tích tệp XML của anh ấy, anh ấy cũng có thể nghĩ về việc tạo các lớp mô hình phù hợp cho các thẻ bắt buộc, vì vậy thư viện phân tích cú pháp có thể ánh xạ chúng. Sau đó, các lớp mô hình này chứa logic để nullkiểm tra các thẻ phụ của chính nó.
Tom

4

Câu trả lời của tôi gần như cùng dòng với @janki, nhưng tôi muốn sửa đổi đoạn mã một chút như dưới đây:

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;

Bạn cũng có thể thêm một kiểm tra null wsObject, nếu có bất kỳ cơ hội nào cho đối tượng đó là null.


4

Bạn nói rằng một số phương pháp "có thể trở lại null" nhưng không nói trong trường hợp nào chúng trở lại null. Bạn nói bạn bắt được NullPointerExceptionnhưng bạn không nói tại sao bạn bắt được nó. Việc thiếu thông tin này cho thấy bạn không hiểu rõ về ngoại lệ là gì và tại sao chúng lại vượt trội so với giải pháp thay thế.

Hãy xem xét một phương thức lớp có nghĩa là để thực hiện một hành động, nhưng phương thức đó không thể đảm bảo nó sẽ thực hiện hành động đó, vì các trường hợp nằm ngoài sự kiểm soát của nó (thực tế là trường hợp của tất cả các phương thức trong Java ). Chúng tôi gọi phương thức đó và nó trả về. Mã gọi phương thức đó cần biết liệu nó có thành công hay không. Làm sao nó biết được? Làm thế nào nó có thể được cấu trúc để đối phó với hai khả năng, thành công hay thất bại?

Sử dụng các ngoại lệ, chúng ta có thể viết các phương thức có thành công như một điều kiện bài . Nếu phương thức trả về, nó đã thành công. Nếu nó ném một ngoại lệ, nó đã thất bại. Đây là một chiến thắng lớn cho sự rõ ràng. Chúng ta có thể viết mã xử lý rõ ràng trường hợp bình thường, thành công và chuyển tất cả mã xử lý lỗi thành catchmệnh đề. Nó thường truyền tải rằng các chi tiết về cách thức hoặc lý do tại sao một phương thức không thành công không quan trọng đối với người gọi, do đó, catchmệnh đề tương tự có thể được sử dụng để xử lý một số loại lỗi. Và nó thường xảy ra rằng một phương pháp không cần phải ngoại lệ bắt ở tất cả , nhưng chỉ có thể cho phép họ để truyền cho gọi. Các ngoại lệ do lỗi chương trình nằm trong lớp sau đó; một vài phương pháp có thể phản ứng thích hợp khi có lỗi.

Vì vậy, những phương pháp mà trở lại null.

  • Có một nullgiá trị chỉ ra một lỗi trong mã của bạn? Nếu vậy, bạn hoàn toàn không nên bắt ngoại lệ. Và mã của bạn không nên cố gắng đoán lần thứ hai. Chỉ cần viết những gì rõ ràng và súc tích trên giả định rằng nó sẽ hoạt động. Là một chuỗi các cuộc gọi phương thức rõ ràng và súc tích? Sau đó, chỉ cần sử dụng chúng.
  • Có một nullgiá trị chỉ ra đầu vào không hợp lệ cho chương trình của bạn? Nếu có, đó NullPointerExceptionkhông phải là một ngoại lệ thích hợp để ném, bởi vì thông thường nó được dành riêng để chỉ ra lỗi. Bạn có thể muốn ném một ngoại lệ tùy chỉnh xuất phát từ IllegalArgumentException(nếu bạn muốn một ngoại lệ không được kiểm tra ) hoặc IOException(nếu bạn muốn một ngoại lệ được kiểm tra). Chương trình của bạn có bắt buộc phải cung cấp thông báo lỗi cú pháp chi tiết khi có đầu vào không hợp lệ không? Nếu vậy, kiểm tra từng phương thức để tìm nullgiá trị trả về thì ném ngoại lệ chẩn đoán thích hợp là điều duy nhất bạn có thể làm. Nếu chương trình của bạn không cần cung cấp chẩn đoán chi tiết, kết nối các phương thức gọi lại với nhau, bắt bất kỳ NullPointerExceptionvà sau đó ném ngoại lệ tùy chỉnh của bạn là rõ ràng và ngắn gọn nhất.

Một trong những câu trả lời cho rằng phương thức bị xiềng xích vi phạm Luật Demeter và do đó là xấu. Yêu cầu đó là sai lầm.

  • Khi nói đến thiết kế chương trình, thực sự không có bất kỳ quy tắc tuyệt đối nào về điều gì là tốt và điều gì là xấu. Chỉ có heuristic: quy tắc đúng nhiều (thậm chí gần như tất cả) của thời gian. Một phần của kỹ năng lập trình là biết khi nào nên chấp nhận những quy tắc đó. Vì vậy, một khẳng định ngắn gọn rằng "điều này trái với quy tắc X " thực sự không phải là một câu trả lời. Đây có phải là một trong những tình huống mà quy tắc nên được phá vỡ?
  • Các Luật của Demeter thực sự là một quy tắc về API hoặc thiết kế giao diện lớp. Khi thiết kế các lớp, sẽ rất hữu ích khi có một hệ thống trừu tượng. Bạn có các lớp cấp thấp sử dụng các nguyên hàm ngôn ngữ để trực tiếp thực hiện các thao tác và biểu diễn các đối tượng trong một mức độ trừu tượng cao hơn các nguyên thủy ngôn ngữ. Bạn có các lớp cấp trung ủy cho các lớp cấp thấp và triển khai các hoạt động và biểu diễn ở cấp cao hơn so với các lớp cấp thấp. Bạn có các lớp cấp cao ủy quyền cho các lớp cấp trung bình và triển khai các hoạt động và trừu tượng cấp cao hơn. (Tôi đã nói về chỉ ba mức độ trừu tượng ở đây, nhưng nhiều khả năng hơn). Điều này cho phép mã của bạn thể hiện chính nó theo các khái niệm trừu tượng phù hợp ở mỗi cấp độ, do đó ẩn đi sự phức tạp. Cơ sở lý luận của Luật Demeterlà nếu bạn có một chuỗi các cuộc gọi phương thức, điều đó cho thấy rằng bạn có một lớp cấp cao tiếp cận thông qua một lớp cấp trung bình để xử lý trực tiếp các chi tiết cấp thấp, và do đó, lớp cấp trung bình của bạn đã không cung cấp một hoạt động trừu tượng cấp trung bình mà lớp cấp cao cần. Nhưng có vẻ như đó không phải là tình huống bạn gặp ở đây: bạn không thiết kế các lớp trong chuỗi các cuộc gọi phương thức, chúng là kết quả của một số mã tuần tự hóa XML được tạo tự động (phải không?) Và chuỗi các cuộc gọi không giảm dần thông qua một hệ thống phân cấp trừu tượng bởi vì XML được tuần tự hóa tất cả đều ở cùng một mức phân cấp trừu tượng hóa (phải không?)?

3

Để cải thiện khả năng đọc, bạn có thể muốn sử dụng nhiều biến, như

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();

Điều này là rất ít đọc theo ý kiến ​​của tôi. Nó tạo ra phương thức với toàn bộ logic kiểm tra null hoàn toàn không liên quan đến logic thực tế của phương thức.
Nhà phát

2

Đừng bắt NullPointerException. Bạn không biết nó đến từ đâu (tôi biết nó không có khả năng xảy ra trong trường hợp của bạn nhưng có thể có thứ gì đó đã ném nó) và nó rất chậm. Bạn muốn truy cập vào trường được chỉ định và đối với trường này, mọi trường khác phải không có giá trị. Đây là một lý do hợp lệ hoàn hảo để kiểm tra mọi lĩnh vực. Tôi có thể sẽ kiểm tra nó trong một nếu và sau đó tạo ra một phương thức để dễ đọc. Như những người khác đã chỉ ra rằng -1 đã trở lại rất cũ nhưng tôi không biết liệu bạn có lý do cho việc đó hay không (ví dụ như nói chuyện với một hệ thống khác).

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;
}

Chỉnh sửa: Thật đáng tranh luận nếu nó không tuân theo Law Of Demeter vì WsObject có lẽ chỉ là một cấu trúc dữ liệu (kiểm tra https://stackoverflow.com/a/26021695/1528880 ).


2

Nếu bạn không muốn cấu trúc lại mã và bạn có thể sử dụng Java 8, có thể sử dụng các tham chiếu Phương thức.

Một bản demo đơn giản đầu tiên (xin lỗi các lớp bên trong tĩnh)

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;
            }
}

Đầu ra

241
4

null
2

Giao diện Getterchỉ là một giao diện chức năng, bạn có thể sử dụng bất kỳ tương đương.
GetterResultlớp, bộ truy cập loại bỏ cho rõ ràng, giữ kết quả của chuỗi getter, nếu có, hoặc chỉ mục của getter cuối cùng được gọi.

Phương pháp getterChainnày là một đoạn mã đơn giản, soạn sẵn, có thể được tạo tự động (hoặc thủ công khi cần).
Tôi cấu trúc mã để khối lặp lại là hiển nhiên.


Đây không phải là một giải pháp hoàn hảo vì bạn vẫn cần xác định một tình trạng quá tải getterChain cho mỗi số lượng getters.

Tôi sẽ cấu trúc lại mã thay vào đó, nhưng nếu không thể và bạn thấy mình sử dụng chuỗi getter dài thường thì bạn có thể xem xét việc xây dựng một lớp với các tình trạng quá tải mất từ ​​2 đến 10, getters.


2

Như những người khác đã nói, tôn trọng Luật Demeter chắc chắn là một phần của giải pháp. Một phần khác, bất cứ khi nào có thể, là thay đổi các phương thức xích đó để chúng không thể quay lại null. Bạn có thể tránh quay lại nullbằng cách trả lại một sản phẩm trống String, một sản phẩm Collectiongiả hoặc một số đối tượng giả khác có nghĩa là hoặc làm bất cứ điều gì người gọi sẽ làm với null.


2

Tôi muốn thêm một câu trả lời tập trung vào ý nghĩa của lỗi . Ngoại lệ Null tự nó không cung cấp bất kỳ lỗi đầy đủ ý nghĩa. Vì vậy, tôi khuyên bạn nên tránh giao dịch trực tiếp với họ.

Có hàng ngàn trường hợp mã của bạn có thể bị lỗi: không thể kết nối với cơ sở dữ liệu, Ngoại lệ IO, Lỗi mạng ... Nếu bạn xử lý từng cái một (như kiểm tra null ở đây), điều đó sẽ quá rắc rối.

Trong mã:

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

Ngay cả khi bạn biết lĩnh vực nào là null, bạn không biết gì về những gì sai. Có lẽ Bar là null, nhưng nó có được mong đợi không? Hay là một lỗi dữ liệu? Hãy nghĩ về những người đọc mã của bạn

Giống như trong câu trả lời của xenteros, tôi đề xuất sử dụng ngoại lệ không được kiểm tra tùy chỉnh . Ví dụ: trong tình huống này: Foo có thể là null (dữ liệu hợp lệ), nhưng Bar và Baz không bao giờ nên là null (dữ liệu không hợp lệ)

Mã này có thể được viết lại:

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"
    }
}

Các trường hợp ngoại lệ không được kiểm tra cho thấy rằng lập trình viên đang sử dụng sai API. Các vấn đề bên ngoài như "không thể kết nối với cơ sở dữ liệu, Ngoại lệ IO, Lỗi mạng" phải được chỉ định bằng các ngoại lệ được kiểm tra.
Kevin Krumwiede

Nó thực sự phụ thuộc vào nhu cầu của người gọi. Đã kiểm tra trợ giúp ngoại lệ vì nó buộc bạn xử lý lỗi. Tuy nhiên, trong các trường hợp khác, nó không cần thiết và có thể gây ô nhiễm mã. Ví dụ: bạn có IOException trong lớp Dữ liệu của mình, bạn sẽ ném nó vào lớp Trình bày chứ? Điều đó có nghĩa là bạn phải bắt ngoại lệ và ném lại vào mỗi người gọi. Tôi muốn bọc IOException bằng một BusinessException tùy chỉnh, với một thông báo có liên quan và để nó bật qua stacktrace, cho đến khi bộ lọc toàn cầu bắt được nó và hiển thị thông báo cho người dùng.
Hoàng Long

Người gọi không phải bắt và ném lại các ngoại lệ được kiểm tra, chỉ cần khai báo chúng sẽ bị ném.
Kevin Krumwiede

@KevinKrumwiede: bạn đã đúng, chúng tôi chỉ cần khai báo ngoại lệ được ném. Chúng tôi vẫn cần phải khai báo mặc dù. Chỉnh sửa: Nhìn lần thứ 2 vào nó, có khá nhiều cuộc tranh luận về việc sử dụng ngoại lệ được kiểm tra và không được kiểm tra (ví dụ: lập trình viên.stackexchange.com/questions / 121328 / .).
Hoàng Long

2

NullPointerException là một ngoại lệ trong thời gian chạy, vì vậy nói chung không nên bắt nó, nhưng để tránh nó.

Bạn sẽ phải bắt ngoại lệ bất cứ nơi nào bạn muốn gọi phương thức (hoặc nó sẽ truyền lên ngăn xếp). Tuy nhiên, nếu trong trường hợp của bạn, bạn có thể tiếp tục làm việc với kết quả đó với giá trị -1 và bạn chắc chắn rằng nó sẽ không lan truyền vì bạn không sử dụng bất kỳ "phần" nào có thể là null, thì có vẻ như tôi đúng bắt nó

Biên tập:

Tôi đồng ý với câu trả lời sau từ @xenteros, sẽ tốt hơn nếu khởi chạy ngoại lệ của riêng bạn thay vì trả về -1, bạn có thể gọi nó là InvalidXMLExceptionví dụ.


3
Bạn có ý nghĩa gì với "không có vấn đề gì nếu bạn bắt được nó, nó có thể lan truyền đến các phần khác của mã"?
Hulk

Nếu null có trong câu này wsObject.getFoo () Và trong các phần sau của mã, bạn chạy lại truy vấn đó hoặc sử dụng wsObject.getFoo (). GetBar () (ví dụ), nó sẽ đưa ra một NullPulumException.
SCouto

Đó là một từ ngữ bất thường cho "Bạn sẽ phải bắt ngoại lệ bất cứ nơi nào bạn muốn gọi phương thức (hoặc nó sẽ truyền lên ngăn xếp)." nếu tôi hiểu chính xác. Tôi đồng ý với điều đó (và đó có thể là một vấn đề), tôi chỉ thấy từ ngữ gây nhầm lẫn.
Hulk

Tôi sẽ sửa nó, xin lỗi, tiếng Anh không phải là ngôn ngữ đầu tiên của tôi nên đôi khi điều này có thể xảy ra :) Cảm ơn
SCouto

2

Đã theo dõi bài này từ hôm qua.

Tôi đã bình luận / bỏ phiếu cho các bình luận cho biết, bắt NPE là xấu. Đây là lý do tại sao tôi đã làm điều đó.

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;
    }
}

Đầu ra

3.216

0,002

Tôi thấy một người chiến thắng rõ ràng ở đây. Có nếu kiểm tra là cách quá ít tốn kém hơn bắt một ngoại lệ. Tôi đã thấy cách làm Java-8 đó. Xem xét rằng 70% các ứng dụng hiện tại vẫn chạy trên Java-7, tôi đang thêm câu trả lời này.

Tóm lại Đối với bất kỳ ứng dụng quan trọng nào, việc xử lý NPE rất tốn kém.


Ba giây nữa cho một triệu yêu cầu trong trường hợp xấu nhất là có thể đo lường được, nhưng nó hiếm khi là một công cụ thỏa thuận, ngay cả trong "các ứng dụng quan trọng". Có những hệ thống trong đó việc thêm 3,2 micro giây vào một yêu cầu là một vấn đề lớn và nếu bạn có một hệ thống như vậy, bằng mọi cách hãy suy nghĩ cẩn thận về các ngoại lệ. Nhưng việc gọi một dịch vụ web và khử lưu lượng đầu ra của nó, theo câu hỏi ban đầu, có thể mất nhiều thời gian hơn thế, và lo lắng về hiệu suất xử lý ngoại lệ nằm bên cạnh điểm đó.
Jeroen Mostert

@JeroenMostert: 3 giây mỗi lần kiểm tra / Triệu. Vì vậy, số lượng kiểm tra sẽ làm tăng chi phí
newuser

Thật. Mặc dù vậy, tôi vẫn coi đó là một trường hợp "hồ sơ đầu tiên". Bạn cần hơn 300 kiểm tra trong một yêu cầu trước khi yêu cầu mất thêm một phần nghìn giây. Cân nhắc thiết kế sẽ đè nặng lên tâm hồn tôi sớm hơn thế.
Jeroen Mostert

@JeroenMostert: :) Đồng ý! Tôi muốn để lại cho các lập trình viên với kết quả và để họ thực hiện cuộc gọi!
newuser

1

Nếu hiệu quả là một vấn đề thì nên xem xét tùy chọn 'bắt'. Nếu 'bắt' không thể được sử dụng bởi vì nó sẽ lan truyền (như đã đề cập bởi 'SCouto') sau đó sử dụng các biến địa phương để tránh nhiều cuộc gọi đến các phương pháp getFoo(), getBar()getBaz().


1

Thật đáng để xem xét để tạo Ngoại lệ của riêng bạn. Hãy gọi nó là MyOperationFailsException. Bạn có thể ném nó thay vì trả lại một giá trị. Kết quả sẽ giống nhau - bạn sẽ thoát khỏi hàm, nhưng bạn sẽ không trả về giá trị được mã hóa cứng -1, đó là kiểu chống Java. Trong Java, chúng tôi sử dụng Ngoại lệ.

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

BIÊN TẬP:

Theo các cuộc thảo luận trong các ý kiến ​​cho phép tôi thêm một cái gì đó vào suy nghĩ trước đây của tôi. Trong mã này có hai khả năng. Một là bạn chấp nhận null và một là, đó là một lỗi.

Nếu đó là một lỗi và nó xảy ra, Bạn có thể gỡ lỗi mã của mình bằng các cấu trúc khác cho mục đích gỡ lỗi khi điểm dừng không đủ.

Nếu nó được chấp nhận, bạn không quan tâm đến nơi null này xuất hiện. Nếu bạn làm thế, bạn chắc chắn không nên xâu chuỗi những yêu cầu đó.


2
Bạn không nghĩ rằng đó là một ý tưởng tồi để ngăn chặn ngoại lệ? Trong thời gian thực, nếu chúng ta mất dấu vết của một ngoại lệ, nỗi đau thực sự của nó ở phía dưới để tìm hiểu xem cái quái gì đang xảy ra! Tôi sẽ luôn đề nghị không sử dụng chuỗi. Vấn đề thứ hai tôi thấy là: Mã này không thể được cấp tại thời điểm đó, kết quả là null.
newuser

Không, Ngoại lệ của bạn có thể có một thông điệp chắc chắn sẽ chỉ ra nơi nó được ném. Tôi đồng ý xâu chuỗi không phải là giải pháp tốt nhất :)
xenteros

3
Không, nó sẽ chỉ nói về số dòng. Vì vậy, bất kỳ cuộc gọi nào trong chuỗi có thể dẫn đến một ngoại lệ.
newuser

"Nếu đó là một lỗi và nó xảy ra, Bạn có thể gỡ lỗi mã của mình" - không có trong sản xuất. Tôi muốn biết rõ hơn những gì thất bại khi tất cả những gì tôi có là một bản ghi hơn là cố gắng đoán ra những gì đã xảy ra để khiến nó thất bại. Với lời khuyên đó (và mã đó), tất cả những gì bạn thực sự biết là một trong 4 thứ là null, nhưng không phải cái nào hay tại sao.
VLAZ

1

Phương pháp bạn có dài, nhưng rất dễ đọc. Nếu tôi là một nhà phát triển mới đến cơ sở mã của bạn, tôi có thể thấy những gì bạn đang làm khá nhanh. Hầu hết các câu trả lời khác (bao gồm cả việc bắt ngoại lệ) dường như không làm cho mọi thứ dễ đọc hơn và theo tôi, một số làm cho nó ít dễ đọc hơn.

Cho rằng bạn có thể không có quyền kiểm soát đối với nguồn được tạo và giả sử bạn thực sự chỉ cần truy cập vào một vài trường được lồng sâu ở đây và sau đó tôi sẽ khuyên bạn nên bọc mỗi truy cập lồng nhau sâu bằng một phương thức.

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();
}

Nếu bạn thấy mình viết rất nhiều phương thức này hoặc nếu bạn thấy mình muốn tạo ra các phương thức tĩnh công khai này thì tôi sẽ tạo một mô hình đối tượng riêng biệt, lồng nhau theo cách bạn muốn, chỉ với các lĩnh vực bạn quan tâm và chuyển đổi từ web mô hình đối tượng dịch vụ cho mô hình đối tượng của bạn.

Khi bạn liên lạc với một dịch vụ web từ xa, sẽ rất điển hình khi có "miền từ xa" và "miền ứng dụng" và chuyển đổi giữa hai dịch vụ. Miền từ xa thường bị giới hạn bởi giao thức web (ví dụ: bạn không thể gửi các phương thức trợ giúp qua lại trong một dịch vụ RESTful thuần túy và các mô hình đối tượng được lồng sâu là phổ biến để tránh nhiều lệnh gọi API) và vì vậy không lý tưởng để sử dụng trực tiếp trong khách hàng của bạn

Ví dụ:

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();

bằng cách áp dụng Luật 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

Đưa ra câu trả lời có vẻ khác với tất cả những người khác.

Tôi khuyên bạn nên kiểm tra NULLtrong ifs.

Lý do:

Chúng ta không nên để một cơ hội duy nhất cho chương trình của chúng ta bị sập. NullPulum được tạo bởi hệ thống. Hành vi của các ngoại lệ do Hệ thống tạo ra không thể dự đoán được . Bạn không nên để chương trình của mình trong tay Hệ thống khi bạn đã có cách tự xử lý nó. Và đặt cơ chế xử lý Ngoại lệ cho an toàn hơn. !!

Để làm cho mã của bạn dễ đọc, hãy thử điều này để kiểm tra các điều kiện:

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

BIÊN TẬP :

Ở đây bạn cần lưu trữ các giá trị wsObject.getFoo(), wsObject.getFoo().getBar(), wsObject.getFoo().getBar().getBaz()trong một số biến. Tôi không làm điều đó bởi vì tôi không biết các kiểu trả về của hàm đó.

Bất kỳ đề xuất sẽ được đánh giá cao..!!


Bạn có coi getFoo () là một hoạt động rất đúng lúc không? Bạn nên lưu trữ các giá trị được trả về trong các biến, tuy nhiên việc này rất lãng phí bộ nhớ. Phương pháp của bạn là hoàn hảo cho lập trình C.
xenteros

nhưng đôi khi tốt hơn là trễ 1 mili giây sau đó chương trình gặp sự cố @xenteros .. !!
Janki Gadhiya

getFoo () có thể nhận được giá trị từ một máy chủ nằm ở lục địa khác. Nó có thể kéo dài bất cứ lúc nào: phút / giờ ...
xenteros

wsObjectsẽ chứa giá trị được trả về từ Webservice .. !! Dịch vụ sẽ được gọi và wsObjectsẽ nhận được XMLdữ liệu dài dưới dạng phản hồi của dịch vụ web .. !! Vì vậy, không có gì giống như máy chủ nằm ở lục địa khác bởi vì getFoo()đây chỉ là một yếu tố nhận được phương thức getter chứ không phải là một cuộc gọi Webservice .. !! @xenteros
Janki Gadhiya

1
Vâng, từ tên của các getters tôi sẽ cho rằng họ trả lại các đối tượng Foo, Bar và Baz: P Cũng xem xét loại bỏ sự an toàn gấp đôi được đề cập khỏi câu trả lời của bạn. Tôi không nghĩ rằng nó cung cấp bất kỳ giá trị thực sự nào ngoài sự ô nhiễm của mã. Với các biến cục bộ lành mạnh và kiểm tra null, chúng tôi đã làm quá đủ để đảm bảo tính chính xác của mã. Nếu một ngoại lệ có thể xảy ra, nó nên được coi là một.
Marius K.

0

Tôi đã viết một lớp được gọi là Snagcho phép bạn xác định một đường dẫn để điều hướng qua một cây các đối tượng. Dưới đây là một ví dụ về việc sử dụng nó:

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

Có nghĩa là cá thể ENGINE_NAMEsẽ gọi Car?.getEngine()?.getName()một cá thể được truyền cho nó một cách hiệu quả và trả về nullnếu có bất kỳ tham chiếu nào được trả về null:

final String name =  ENGINE_NAME.get(firstCar);

Nó không được xuất bản trên Maven nhưng nếu có ai thấy nó hữu ích thì nó ở đây (tất nhiên không có bảo hành!)

Đó là một chút cơ bản nhưng có vẻ như để thực hiện công việc. Rõ ràng là nó đã lỗi thời hơn với các phiên bản Java và các ngôn ngữ JVM khác hỗ trợ điều hướng an toàn hoặc Optional.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.