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

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # **第 10 章 錯誤處理與異常** 程序在運行時會伴隨著各種各樣的錯誤發生。如果我們在編寫程序時不犯任何錯誤,并且所有處理都能正常執行的話,那么就不會產生程序錯誤,但實際上并不可能有這么完美的程序。在本章中,我們將圍繞著程序錯誤及其應對方法,向大家介紹一下 Ruby 異常處理的相關內容。 ### **10.1 關于錯誤處理** 在介紹實際的程序例子前,我們先來了解一下程序錯誤相關的基礎知識。在程序執行的過程中,通常會有以下錯誤發生: - **數據錯誤** 在計算家庭收支的時候,若在應該寫金額的一欄上填上了商品名,那么就無法計算。此外,HTML 這種格式的數據的情況下,如果存在沒有關閉標簽等語法錯誤,也會導致無法處理數據。 - **系統錯誤** 硬盤故障等明顯的故障,或者沒把 CD 插入到驅動器等程序無法恢復的問題。 - **程序錯誤** 因調用了不存在的方法、弄錯參數值或算法錯誤而導致錯誤結果等,像這樣,程序本身的缺陷也可能會導致錯誤。 程序在運行時可能會遇到各種各樣的錯誤。如果對這些錯誤放任不管,大部分程序都無法正常運行,因此我們需要對這些錯誤做相應的處理。 - **排除錯誤的原因** 在文件夾中創建文件時,如果文件夾不存在,則由程序本身創建文件夾。如果程序無法創建文件夾,則需要再考慮其他解決方法。 - **忽略錯誤** 程序有時候也會有一些無傷大雅的錯誤。例如,假設運行程序時需要讀取某個配置文件,如果我們事前已經在程序中準備好了相應配置的默認值,那么即使無法讀取該設定文件,程序也可以忽略這個錯誤。 - **恢復錯誤發生前的狀態** 向用戶提示程序發生錯誤,指導用戶該如何進行下一步處理。 - **重試一次** 曾經執行失敗的程序,過一段時間后再重新執行可能就會成功。 - **終止程序** 只是自己一個人用的小程序,也許本來就沒必要做錯誤處理。 而至于實際應該采取何種處理,則要根據程序代碼的規模、應用程序的性質來決定,不能一概而論。但是,對于可預期的錯誤,我們需要留意以下兩點: - **是否破壞了輸入的數據,特別是人工制作的數據。** - **是否可以對錯誤的內容及其原因做出相應的提示。** 覆蓋了原有文件、刪除了花費大量時間輸入的數據等,像這樣的重要數據的丟失、破壞可以說是災難性的錯誤。另外,如果錯誤是由用戶造成的,或者程序自身不能修復的話,給用戶簡明易懂的錯誤提示,會大大提升程序的用戶體驗。 Ruby 為我們提供了異常處理機制,可以使我們非常方便地應對各種錯誤。 ### **10.2 異常處理** 在程序執行的過程中,如果程序出現了錯誤就會發生異常。異常發生后,程序會暫時停止運行,并尋找是否有對應的異常處理程序。如果有則執行,如果沒有,程序就會顯示類似以下信息并終止運行。 > **執行示例** ~~~ > ruby test.rb test.rb:2:in `initialize': No such file or directory - /no/file(Errno::ENOENT) from test.rb:2:in `open' from test.rb:2:in `foo' from test.rb:2:in `bar' from test.rb:9:in `main' ~~~ 該信息的格式如下: **文件名: 行號`:in` 方法名: 錯誤信息(異常類名)     `from` 文件名: 行號:`in` 方法名     ┊** 以 `from` 開頭的行表示發生錯誤的位置。 沒有異常處理的編程語言的情況下,編程時就需要逐個確認每個處理是否已經處理完畢(圖 10.1)。在這類編程語言中,大部分程序代碼都被花費在錯誤處理上,因此往往會使程序變得繁雜。 ![{%}](https://box.kancloud.cn/2015-10-26_562e01e8f1ad6.png) **圖 10.1 異常處理** 異常處理有以下優點: - **程序不需要逐個確認處理結果,也能自動檢查出程序錯誤** - **會同時報告發生錯誤的位置,便于排查錯誤** - **正常處理與錯誤處理的程序可以分開書寫,使程序便于閱讀** ### **10.3 異常處理的寫法** Ruby 中使用 `begin ~ rescue ~ end` 語句描述異常處理。 **`begin`  可能會發生異常的處理 `rescue`  發生異常時的處理 `end`** 在 Ruby 中,異常及其相關信息都是被作為對象來處理的。在 `rescue` 后指定變量名,可以獲得異常對象。 **`begin`  可能會發生異常的處理 `rescue =>` 引用異常對象的變量  發生異常時的處理 `end`** 即使不指定變量名,Ruby 也會像表 10.1 那樣把異常對象賦值給變量 `$!`。不過,把變量名明確地寫出來會使程序更加易懂。 **表 10.1 異常發生時被自動賦值的變量** <table border="1" data-line-num="92 93 94 95 96" width="90%"><thead><tr><th> <p class="表頭單元格">變量</p> </th> <th> <p class="表頭單元格">意義</p> </th> </tr></thead><tbody><tr><td> <p class="表格單元格"><code>$!</code></p> </td> <td> <p class="表格單元格">最后發生的異常(異常對象)</p> </td> </tr><tr><td> <p class="表格單元格"><code>$@</code></p> </td> <td> <p class="表格單元格">最后發生的異常的位置信息</p> </td> </tr></tbody></table> 此外,通過調用表 10.2 中的異常對象的方法,就可以得到相關的異常信息。 **表 10.2 異常對象的方法** <table border="1" data-line-num="101 102 103 104 105 106" width="90%"><thead><tr><th> <p class="表頭單元格">方法名</p> </th> <th> <p class="表頭單元格">意義</p> </th> </tr></thead><tbody><tr><td> <p class="表格單元格"><code>class</code></p> </td> <td> <p class="表格單元格">異常的種類</p> </td> </tr><tr><td> <p class="表格單元格"><code>message</code></p> </td> <td> <p class="表格單元格">異常信息</p> </td> </tr><tr><td> <p class="表格單元格"><code>backtrace</code></p> </td> <td> <p class="表格單元格">異常發生的位置信息(<code>$@</code> 與 <code>$!.backtrace</code> 是等價的)</p> </td> </tr></tbody></table> 代碼清單 10.1 是 Unix 的 wc 命令的簡易版。結果會輸出參數中指定的各文件的行數、單詞數、字數(字節數),最后輸出全部文件的統計結果。 > **執行示例** ~~~ > ruby wc.rb intro.rb sec01.rb sec02.rb 50 67 1655 intro.rb 81 92 3455 sec01.rb 123 162 3420 sec02.rb 254 321 8520 total ~~~ **代碼清單 10.1 wc.rb** ~~~ ltotal=0 # 行數合計 wtotal=0 # 單詞數合計 ctotal=0 # 字數合計 ARGV.each do |file| begin input = File.open(file) # 打開文件(A) l=0 # file 內的行數 w=0 # file 內的單詞數 c=0 # file 內的字數 input.each_line do |line| l += 1 c += line.size line.sub!(/^\s+/, "") # 刪除行首的空白符 ary = line.split(/\s+/) # 用空白符分解 w += ary.size end input.close # 關閉文件 printf("%8d %8d %8d %s\n", l, w, c, file) # 整理輸出格式 ltotal += l wtotal += w ctotal += c rescue => ex print ex.message, "\n" # 輸出異常信息(B) end end printf("%8d %8d %8d %s\n", ltotal, wtotal, ctotal, "total") ~~~ 在(A)處無法打開文件時,程序會跳到 `rescue` 部分。這時,異常對象被賦值給變量 `ex`,(B)部分的處理被執行。 如果程序中指定了不存在的文件,則會提示發生錯誤,如下所示。提示發生錯誤后,并不會馬上終止程序,而是繼續處理下一個文件。 > **執行示例** ~~~ > ruby wc.rb intro.rb sec01.rb sec02.rb sec03.rb 50 67 1655 intro.rb 81 92 3455 sec01.rb 123 188 3729 sec02.rb No such file or directory - sec03.rb 254 321 8520 total ~~~ 如果發生異常的方法中沒有 `rescue` 處理,程序就會逆向查找調用者中是否定義了異常處理。下面來看看圖 10.2 這個例子。調用 `foo` 方法,嘗試打開一個不存在的文件。若 `File.open` 方法發生異常,那么該異常就會跳過 `foo` 方法以及 `bar` 方法,被更上一層的 `rescue` 捕捉。 ![{%}](https://box.kancloud.cn/2015-10-26_562e01e911202.png) **圖 10.2 異常處理的流程** 然而,并不是說每個方法都需要做異常處理,只需根據實際情況在需要留意的地方做就可以了。在并不特別需要解決錯誤的情況下,也可以不捕捉異常。當然,不捕捉異常就意味著如果有問題發生程序就會馬上終止。 ### **10.4 后處理** 不管是否發生異常都希望執行的處理,在 Ruby 中可以用 `ensure` 關鍵字來定義。 **`begin`  有可能發生異常的處理 `rescue` => 變量  發生異常后的處理 `ensure`  不管是否發生異常都希望執行的處理 `end`** 現在,假設我們要實現一個拷貝文件的方法,如下所示。下面的 `copy` 方法是把文件從 `from` 拷貝到 `to`。 ~~~ def copy(from, to) src = File.open(from) # 打開原文件from(A) begin dst = File.open(to, "w") # 打開目標文件to(B) data = src.read dst.write(data) dst.close ensure src.close # (C) end end ~~~ 在(A)部分,如果程序不能打開原文件,那么就會發生異常并把異常返回給調用者。這時,不管接下來的處理是否能正常執行,`src` 都必須得關閉。關閉 `src` 的處理在(C)部分執行。`ensure` 中的處理,在程序跳出 `begin ~ end` 部分時一定會被執行。即使(B)中的目標文件無法打開,(C)部分的處理也同樣會被執行。 ### **10.5 重試** 在 `rescue` 中使用 `retry` 后,`begin` 以下的處理會再重做一遍。 在下面的例子中,程序每隔 10 秒執行一次 `File.open`,直到能成功打開文件為止,打開文件后再讀取其內容。 ~~~ file = ARGV[0] begin io = File.open(file) rescue sleep 10 retry end data = io.read io.close ~~~ 不過需要注意的是,如果指定了無論如何都不能打開的文件,程序就會陷入死循環中。 ### **10.6 rescue 修飾符** 與 `if` 修飾符、`unless` 修飾符一樣,`rescue` 也有對應的修飾符。 **表達式 `1 rescue` 表達式 `2`** 如果表達式 1 中發生異常,表達式 2 的值就會成為整體表達式的值。也就是說,上面的式子與下面的寫法是等價的: **`begin`  表達式 `1` `rescue`  表達式 `2` `end`** 我們再來看看下面的例子: ~~~ n = Integer(val) rescue 0 ~~~ `Integer` 方法當接收到 `"123"` 這種數值形式的字符串參數時,會返回該字符串表示的整數值,而當接收到 `"abc"` 這種非數值形式的字符串參數時,則會拋出異常(在判斷字符串是否為數值形式時經常用到此方法)。在本例中,如果 `val` 是不正確的數值格式,就會拋出異常,而 0 則作為 = 右側整體表達式的返回值。像這樣,這個小技巧經常被用在不需要過于復雜的處理,只是希望簡單地對變量賦予默認值的時候。 ### **10.7 異常處理語法的補充** 如果異常處理的范圍是整個方法體,也就是說整個方法內的程序都用 `begin ~ end` 包含的話,我們就可以省略 `begin` 以及 `end`,直接書寫 `rescue` 與 `ensure` 部分的程序。 **`def foo`  方法體 `rescue` => `ex`  異常處理 `ensure`  后處理 `end`** 同樣,我們在類定義中也可以使用 `rescue` 以及 `ensure`。但是,如果類定義途中發生異常,那么異常發生部分后的方法定義就不會再執行了,因此一般我們不會在類定義中使用它們。 **`class Foo`  類定義 `rescue` => `ex`  異常處理 `ensure`  后處理 `end`** ### **10.8 指定需要捕捉的異常** 當存在多個種類的異常,且需要按異常的種類分別進行處理時,我們可以用多個 `rescue` 來分開處理。 **`begin`  可能發生異常的處理 `rescue Exception1, Exception2` => 變量  對`Exception1` 或者`Exception2` 的處理 `rescue Exception3` => 變量  對`Exception3` 的處理 `rescue`  對上述異常以外的異常的處理 `end`** 通過直接指定異常類,可以只捕捉我們希望處理的異常。 ~~~ file1 = ARGV[0] file2 = ARGV[1] begin io = File.open(file1) rescue Errno::ENOENT, Errno::EACCES io = File.open(file2) end ~~~ 在本例中,程序如果無法打開 `file1` 就會打開 `file2`。程序中捕捉的 `Errno::ENOENT` 以及 `Errno::EACCES`,分別是文件不存在以及沒權限打開文件時發生的異常。 ### **10.9 異常類** 之前我們提到過異常也是對象。Ruby 中所有的異常都是 `Exception` 類的子類,并根據程序錯誤的種類來定義相應的異常。圖 10.3 為 Ruby 標準庫中的異常類的繼承關系。 ![{%}](https://box.kancloud.cn/2015-10-26_562e01e92ba32.png) **圖 10.3 異常類的繼承關系** 在 `rescue` 中指定的異常的種類實際上就是異常類的類名。`rescue` 中不指定異常類時,程序會默認捕捉 `StandardError` 類及其子類的異常。 `rescue` 不只會捕捉指定的異常類,同時還會捕捉其子類。因此,我們在自己定義異常時,一般會先定義繼承 `StandardError` 類的新類,然后再繼承這個新類。 ~~~ MyError = Class.new(StandardError) # 新的異常類 MyError1 = Class.new(MyError) MyError2 = Class.new(MyError) MyError3 = Class.new(MyError) ~~~ 這樣定義后,通過以下方式捕捉異常的話,同時就會捕捉 `MyError` 類的子類 `MyError1`、`MyError2`、`MyError3` 等。 ~~~ begin ┊ rescue MyError ┊ end ~~~ 在本例中, ~~~ MyError = Class.new(StandardError) ~~~ 上述寫法的作用是定義一個繼承 `StandardError` 類的新類,并將其賦值給 `MyError` 常量。這與使用在第 8 章中介紹過的 `class` 語句定義類的效果是一樣的。 ~~~ class MyError < StandardError end ~~~ 使用 `class` 語句,我們可以進行定義方法等操作,但在本例中,由于我們只需要生成繼承 `StandardError` 類的新類就可以了,所以就向大家介紹了這個只需 1 行代碼就能實現類的定義的簡潔寫法。 ### **10.10 主動拋出異常** 使用 `raise` 方法,可以使程序主動拋出異常。在基于自己判定的條件拋出異常,或者把剛捕捉到的異常再次拋出并通知異常的調用者等情況下,我們會使用 `raise` 方法。 `raise` 方法有以下 4 種調用方式: - **raise message** 拋出 `RuntimeError` 異常,并把字符串作為 message 設置給新生成的異常對象。 - **raise 異常類** 拋出指定的異常。 - **raise 異常類,message** 拋出指定的異常,并把字符串作為 message 設置給新生成的異常對象。 - **raise** 在 `rescue` 外拋出 `RuntimeError`。在 `rescue` 中調用時,會再次拋出最后一次發生的異常(`$!`)。
                  <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>

                              哎呀哎呀视频在线观看