<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                窮舉的思想是如此的有用,依據這個原理,我們可以推出一些基本原則,它們可以讓你無懈可擊的處理null指針。 首先你應該知道,許多語言(C,C++,Java,C#,……)的類型系統對于null的處理,其實是完全錯誤的。這個錯誤源自于[Tony Hoare](https://en.wikipedia.org/wiki/Tony_Hoare)最早的設計,Hoare把這個錯誤稱為自己的“[billion dollar mistake](http://www.infoq.com/presentations/Null-References-The-Billion-Dollar-Mistake-Tony-Hoare)”,因為由于它所產生的財產和人力損失,遠遠超過十億美元。 這些語言的類型系統允許null出現在任何對象(指針)類型可以出現的地方,然而null其實根本不是一個合法的對象。它不是一個String,不是一個Integer,也不是一個自定義的類。null的類型本來應該是NULL,也就是null自己。根據這個基本觀點,我們推導出以下原則: * 盡量不要產生null指針。盡量不要用null來初始化變量,函數盡量不要返回null。如果你的函數要返回“沒有”,“出錯了”之類的結果,盡量使用Java的異常機制。雖然寫法上有點別扭,然而Java的異常,和函數的返回值合并在一起,基本上可以當成union類型來用。比如,如果你有一個函數find,可以幫你找到一個String,也有可能什么也找不到,你可以這樣寫: ~~~ public String find() throws NotFoundException { if (...) { return ...; } else { throw new NotFoundException(); } } ~~~ Java的類型系統會強制你catch這個NotFoundException,所以你不可能像漏掉檢查null一樣,漏掉這種情況。Java的異常也是一個比較容易濫用的東西,不過我已經在上一節告訴你如何正確的使用異常。 Java的try...catch語法相當的繁瑣和蹩腳,所以如果你足夠小心的話,像`find`這類函數,也可以返回null來表示“沒找到”。這樣稍微好看一些,因為你調用的時候不必用try...catch。很多人寫的函數,返回null來表示“出錯了”,這其實是對null的誤用。“出錯了”和“沒有”,其實完全是兩碼事。“沒有”是一種很常見,正常的情況,比如查哈希表沒找到,很正常。“出錯了”則表示罕見的情況,本來正常情況下都應該存在有意義的值,偶然出了問題。如果你的函數要表示“出錯了”,應該使用異常,而不是null。 * 不要把null放進“容器數據結構”里面。所謂容器(collection),是指一些對象以某種方式集合在一起,所以null不應該被放進Array,List,Set等結構,不應該出現在Map的key或者value里面。把null放進容器里面,是一些莫名其妙錯誤的來源。因為對象在容器里的位置一般是動態決定的,所以一旦null從某個入口跑進去了,你就很難再搞明白它去了哪里,你就得被迫在所有從這個容器里取值的位置檢查null。你也很難知道到底是誰把它放進去的,代碼多了就導致調試極其困難。 解決方案是:如果你真要表示“沒有”,那你就干脆不要把它放進去(Array,List,Set沒有元素,Map根本沒那個entry),或者你可以指定一個特殊的,真正合法的對象,用來表示“沒有”。 需要指出的是,類對象并不屬于容器。所以null在必要的時候,可以作為對象成員的值,表示它不存在。比如: ~~~ class A { String name = null; ... } ~~~ 之所以可以這樣,是因為null只可能在A對象的name成員里出現,你不用懷疑其它的成員因此成為null。所以你每次訪問name成員時,檢查它是否是null就可以了,不需要對其他成員也做同樣的檢查。 * 函數調用者:明確理解null所表示的意義,盡早檢查和處理null返回值,減少它的傳播。null很討厭的一個地方,在于它在不同的地方可能表示不同的意義。有時候它表示“沒有”,“沒找到”。有時候它表示“出錯了”,“失敗了”。有時候它甚至可以表示“成功了”,…… 這其中有很多誤用之處,不過無論如何,你必須理解每一個null的意義,不能給混淆起來。 如果你調用的函數有可能返回null,那么你應該在第一時間對null做出“有意義”的處理。比如,上述的函數`find`,返回null表示“沒找到”,那么調用`find`的代碼就應該在它返回的第一時間,檢查返回值是否是null,并且對“沒找到”這種情況,作出有意義的處理。 “有意義”是什么意思呢?我的意思是,使用這函數的人,應該明確的知道在拿到null的情況下該怎么做,承擔起責任來。他不應該只是“向上級匯報”,把責任踢給自己的調用者。如果你違反了這一點,就有可能采用一種不負責任,危險的寫法: ~~~ public String foo() { String found = find(); if (found == null) { return null; } } ~~~ 當看到find()返回了null,foo自己也返回null。這樣null就從一個地方,游走到了另一個地方,而且它表示另外一個意思。如果你不假思索就寫出這樣的代碼,最后的結果就是代碼里面隨時隨地都可能出現null。到后來為了保護自己,你的每個函數都會寫成這樣: ~~~ public void foo(A a, B b, C c) { if (a == null) { ... } if (b == null) { ... } if (c == null) { ... } ... } ~~~ * 函數作者:明確聲明不接受null參數,當參數是null時立即崩潰。不要試圖對null進行“容錯”,不要讓程序繼續往下執行。如果調用者使用了null作為參數,那么調用者(而不是函數作者)應該對程序的崩潰負全責。上面的例子之所以成為問題,就在于人們對于null的“容忍態度”。 上面這種“保護式”的寫法,試圖“容錯”,試圖“優雅的處理null”,其結果是讓調用者更加肆無忌憚的傳遞null給你的函數。到后來,你的代碼里出現一堆堆nonsense的情況,null可以在任何地方出現,都不知道到底是哪里產生出來的。誰也不知道出現了null是什么意思,該做什么,所有人都把null踢給其他人。最后這null像瘟疫一樣蔓延開來,到處都是,成為一場噩夢。 正確的做法,其實是強硬的態度。你要告訴函數的使用者,我的參數全都不能是null,如果你給我null,程序崩潰了該你自己負責。至于調用者代碼里有null怎么辦,他自己該知道怎么處理(參考以上幾條),不應該由函數作者來操心。 * 使用@NotNull和@Nullable標記。IntelliJ提供了@NotNull和@Nullable兩種標記,加在類型前面,這樣可以比較可靠地防止null指針的出現。IntelliJ本身會對含有這種標記的代碼進行靜態分析,指出運行時可能出現`NullPointerException`的地方。在運行時,會在null指針不該出現的地方產生`IllegalArgumentException`,即使那個null指針你從來沒有deference。這樣你可以在盡量早期發現并且防止null指針的出現。 * 使用Optional類型。Java 8和Swift之類的語言,提供了一種叫Optional的類型。正確的使用這種類型,可以在很大程度上避免null的問題。null指針的問題之所以存在,是因為你可以在沒有“檢查”null的情況下,“訪問”對象的成員。 Optional類型的設計原理,就是把“檢查”和“訪問”這兩個操作合二為一,成為一個“原子操作”。這樣你沒法只訪問,而不進行檢查。這種做法其實是ML,Haskell等語言里的模式匹配(pattern matching)的一個特例。模式匹配使得類型判斷和訪問成員這兩種操作合二為一,所以你沒法犯錯。 比如,在Swift里面,你可以這樣寫: ~~~ let found = find() if let content = found { print("found: " + content) } ~~~ 你從`find()`函數得到一個Optional類型的值`found`。假設它的類型是`String?`,那個問號表示它可能包含一個String,也可能是nil。然后你就可以用一種特殊的if語句,同時進行null檢查和訪問其中的內容。這個if語句跟普通的if語句不一樣,它的條件不是一個Bool,而是一個變量綁定`let content = found`。 我不是很喜歡這語法,不過這整個語句的含義是:如果found是nil,那么整個if語句被略過。如果它不是nil,那么變量content被綁定到found里面的值(unwrap操作),然后執行`print("found: " + content)`。由于這種寫法把檢查和訪問合并在了一起,你沒法只進行訪問而不檢查。 Java 8的做法比較蹩腳一些。如果你得到一個Optional類型的值found,你必須使用“函數式編程”的方式,來寫這之后的代碼: ~~~ Optional<String> found = find(); found.ifPresent(content -> System.out.println("found: " + content)); ~~~ 這段Java代碼跟上面的Swift代碼等價,它包含一個“判斷”和一個“取值”操作。ifPresent先判斷found是否有值(相當于判斷是不是null)。如果有,那么將其內容“綁定”到lambda表達式的content參數(unwrap操作),然后執行lambda里面的內容,否則如果found沒有內容,那么ifPresent里面的lambda不執行。 Java的這種設計有個問題。判斷null之后分支里的內容,全都得寫在lambda里面。在函數式編程里,這個lambda叫做“[continuation](https://en.wikipedia.org/wiki/Continuation)”,Java把它叫做 “[Consumer](https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html)”,它表示“如果found不是null,拿到它的值,然后應該做什么”。由于lambda是個函數,你不能在里面寫`return`語句返回出外層的函數。比如,如果你要改寫下面這個函數(含有null): ~~~ public static String foo() { String found = find(); if (found != null) { return found; } else { return ""; } } ~~~ 就會比較麻煩。因為如果你寫成這樣: ~~~ public static String foo() { Optional<String> found = find(); found.ifPresent(content -> { return content; // can't return from foo here }); return ""; } ~~~ 里面的`return a`,并不能從函數`foo`返回出去。它只會從lambda返回,而且由于那個lambda([Consumer.accept](https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html#accept-T-))的返回類型必須是`void`,編譯器會報錯,說你返回了String。由于Java里closure的自由變量是只讀的,你沒法對lambda外面的變量進行賦值,所以你也不能采用這種寫法: ~~~ public static String foo() { Optional<String> found = find(); String result = ""; found.ifPresent(content -> { result = content; // can't assign to result }); return result; } ~~~ 所以,雖然你在lambda里面得到了found的內容,如何使用這個值,如何返回一個值,卻讓人摸不著頭腦。你平時的那些Java編程手法,在這里幾乎完全廢掉了。實際上,判斷null之后,你必須使用Java 8提供的一系列古怪的[函數式編程操作](http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html):`map`,`flatMap`,?`orElse`之類,想法把它們組合起來,才能表達出原來代碼的意思。比如之前的代碼,只能改寫成這樣: ~~~ public static String foo() { Optional<String> found = find(); return found.orElse(""); } ~~~ 這簡單的情況還好。復雜一點的代碼,我還真不知道怎么表達,我懷疑Java 8的Optional類型的方法,到底有沒有提供足夠的表達力。那里面少數幾個東西表達能力不咋的,論工作原理,卻可以扯到functor,continuation,甚至monad等高深的理論…… 仿佛用了Optional之后,這語言就不再是Java了一樣。 所以Java雖然提供了Optional,但我覺得可用性其實比較低,難以被人接受。相比之下,Swift的設計更加簡單直觀,接近普通的過程式編程。你只需要記住一個特殊的語法`if let content = found {...}`,里面的代碼寫法,跟普通的過程式語言沒有任何差別。 總之你只要記住,使用Optional類型,要點在于“原子操作”,使得null檢查與取值合二為一。這要求你必須使用我剛才介紹的特殊寫法。如果你違反了這一原則,把檢查和取值分成兩步做,還是有可能犯錯誤。比如在Java 8里面,你可以使用`found.get()`這樣的方式直接訪問found里面的內容。在Swift里你也可以使用`found!`來直接訪問而不進行檢查。 你可以寫這樣的Java代碼來使用Optional類型: ~~~ Option<String> found = find(); if (found.isPresent()) { System.out.println("found: " + found.get()); } ~~~ 如果你使用這種方式,把檢查和取值分成兩步做,就可能會出現運行時錯誤。`if (found.isPresent())`本質上跟普通的null檢查,其實沒什么兩樣。如果你忘記判斷`found.isPresent()`,直接進行`found.get()`,就會出現`NoSuchElementException`。這跟`NullPointerException`本質上是一回事。所以這種寫法,比起普通的null的用法,其實換湯不換藥。如果你要用Optional類型而得到它的益處,請務必遵循我之前介紹的“原子操作”寫法。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看