Xử lý Lỗi một cách vi diệu trong SWIFT – iOS

solid_principles_swift

Có 2 file playgrounds trong bài Tutorial này, mỗi file cho mỗi section.

Tải: Avoiding Errors with nil – Starter.playground

Avoiding Errors with Custom Handling – Starter.playground.

Mở file Avoiding Errors with nil bằng Xcode.

Đọc sơ một lượt code, các bạn sẽ thấy một vài class, struct và enum chứa đựng phép thuật trong Tutorial này đấy.

Chú ý đoạn code sau:

protocol Avatar {
  var avatar: String { get }
}

Protocol này được áp dụng cho hầu hết các class và struct được sử dụng xuyên suốt trong tutorial. Protocol này giúp biểu diễn đối tượng trong console.

enum MagicWords: String {
  case abracadbra = "abracadabra"
  case alakazam = "alakazam"
  case hocusPocus = "hocus pocus"
  case prestoChango = "presto chango"
}

Enumeration này định nghĩa nhưng câu niệm được sử dụng để tạo ra một câu thần chú – Spell.

struct Spell {
  var magicWords: MagicWords = .abracadabra
}

Đây là đoạn code cơ bản của struct Spell. Mặc định, chúng ta sẽ khởi tạo nó với câu niệm .abracadabra.

Ok, vậy là các bạn đã nắm được một số thư căn bản của thế giới siêu nhiên này rầu, bạn đã hoàn toàn sẵn sàng để tung chưởng à nhầm tung phép.

Tạo sao chúng ta phải quan tâm đến việc Xử Lý Lỗi?

“Xử lý lỗi là nghệ thuật của việc thất bại một cách duyên dáng.”

– Swift Apprentice, Chương 22 (Xử lý Lỗi)

Xử lý lỗi tốt giúp tăng cường trải nghiệm của người dùng cũng như bảo trì phần mềm bằng việc giúp chúng ta xác định được vấn đề, nguyên nhân và mức độ nghiêm trọng một cách dễ dàng. Xử lý lỗi cụ thể và xuyên suốt giúp chúng ta dễ dàng chuẩn đoán và xử lý. Việc xử lý lỗi cũng giúp cho hệ thống “sập” hay “thất bại” một cách thích hợp mà không làm bối rối hoặc mết lòng người dùng.

Nhưng lỗi không phải lúc nào cũng cần được chúng ta xử lý. Khi chúng ta không xử lý được, các tính năng của ngôn ngữ lập trình sẽ giúp chúng ta ngăn ngừa và phòng tránh một số lỗi nhất định. Theo nguyên tắc chung, nếu bạn có thể tránh được khả năng gây ra lỗi nào thì hãy làm hết mức có thể. Còn nếu bạn không thể tránh được việc gây ra những lỗi tiềm ẩn, thì xử lý chúng rõ ràng chính là giải pháp tốt nhất.

Phòng tránh lỗi bằng cách sử dụng nil

Như các bạn đã biết Swift có tính năng xử lý nil rất hiệu quả và bạn hoàn toàn có thể tránh được những trường hợp lỗi khi mà bạn đang mong đợi 1 giá trị nhưng giá trị đó lại khônh được cung cấp. Là một lập trình viên thông manh, bạn có thể xử lý trường hợp này bằng cách chủ động return nil trong trường hợp lỗi này. Phương án này rất hiệu quả khi mà bạn không làm gì cả nếu xảy ra lỗi.

Hai ví dụ điển hình cho việc phòng tránh lỗi trong Swift bằng cách sử dụng nil là khởi tạo thất bại (failable initializers) và cú pháp guard (guard statements).

Khả Lỗi Khởi Tạo – Failable Initializers

Khả lỗi khởi tạo ( Có khả năng gây ra lỗi khi khởi tạo =]] ) aka failable initializers ngăn chặn việc tạo ra một đối tượng mà không được cung cấp đủ thông tin cần thiết. Trước khi tính năng này có mặt trong Swift (hay trong các ngôn ngữ khác), tính năng này chính là sử dụng Factory Method pattern.

Ví dụ ở hàm create này

  static func create(withMagicWords words: String) -> Spell? {
    if let incantation = MagicWords(rawValue: words) {
      var spell = Spell()
      spell.magicWords = incantation
      return spell
    }
    else {
      return nil
    }
  }

Hàm khởi tạo ở trên cố gắng tạo câu thần chú từ câu niệm được cung cấp, nếu câu niệm đó sai thì chúng ta trả về nil

Xem ví dụ ở cuối bài:

Spell.create

Trong khi first tạo ra câu thần chú thành công bằng câu niệm "abracadabra", thì câu niệm "ascendio" lại không thành công, và giá trị trả về của second là nil. (Hehe, phù thủy đâu phải lúc nào cũng chiến thắng).

Oh Really?

Factory methods cổ lỗ sỉ rồi. Giờ thì có nhiều cách ngon hơn trong Swift nhiều. Bạn sẽ thay đổi extension của Spell để dùng failable initializer thay vì factory method.

Xóa create(_words:) và thay bằng đoạn code sau:

init?(words: String) {
  if let incantation = MagicWords(rawValue: words) {
    self.magicWords = incantation
  }
  else {
    return nil
  }
}

Ở đây bạn đơn giản hóa code lại nhưng không cần phải khởi tạo hay trả về một đối tượng Spell.

Dòng code ví dụ first và second giờ đã báo lỗi

let first = Spell.create(withMagicWords: "abracadabra")
let second = Spell.create(withMagicWords: "ascendio")

Vì chúng ta đã xóa hàmcreate(_words:) mà. Thay đổi chúng lại như sau:

let first = Spell(words: "abracadabra")
let second = Spell(words: "ascendio")

Xong, lỗi đã biến mất và giờ playground đã có thể compile. Thay đổi như vậy, code bạn đã gọn hơn rồi – nhưng các bạn vẫn có thể làm tốt hơn thế nữa! Ahihi

Cú pháp Guard – Guard Statements

guard là cách nhanh nhất để đảm bảo thứ gì đó đúng đắn. Ví dụ, nếu một giá trị > 0, hoặc một điều kiện có thể được unwrapped. Bạn có thể thực thi một đoạn code nếu việc kiểm tra thất bại.

guard được giới thiệu trong Swift 2 và được sử dụng trong việc xử lý lỗi. Cú pháp Guard cho phép chúng ta sớm thoát khỏi một hàm hoặc phương thức; điều này khiến cho chúng ta thấy rõ ràng hơn điều kiện nào cần và đủ để cho phần còn lại của phương thức được phép thực thi.

Cập nhật hàm khởi tạo của Spell cho đẹp hơn bằng cách sử dụng guard nào

init?(words: String) {
  guard let incantation = MagicWords(rawValue: words) else {
    return nil
  }
  self.magicWords = incantation
}

Với cách làm này, chúng ta sẽ không cần phải thêm 1  else ở 1 hàng tách biệt nữa và trường hợp thất bại cực kỳ rõ ràng khi nó nằm trên cùng của hàm khởi tạo. Các bạn nhìn xem, rõ ràng là việc đẩy lỗi lên trên cùng thì đoạn code để thực thi còn lại là hoàn toàn trơn tru, liền mạch giúp cho việc đọc code dễ hơn rất nhiều (thay vì phải if else thụt ra thụt vô như trước).

Giờ thì giá trị của first và second vẫn chưa thay đổi, nhưng mà code đã bắt đầu chuẩn hơn rồi đấy, hehe.

Phòng tránh lỗi bằng cách tùy cơ ứng biến

Ok, phần trên là chúng ta đã biết cách xử lý lỗi khởi tạo bằng việc sử dụng nil, giờ thì các bạn đã sẵn sàng để xử lý những lỗi phức tạp hơn.

Giờ thì mở file Avoiding Errors with Custom Handling – Starter.playground.

Chú ý những đoạn code sau:

struct Spell {
  var magicWords: MagicWords = .abracadbra

  init?(words: String) {
    guard let incantation = MagicWords(rawValue: words) else {
      return nil
    }
    self.magicWords = incantation
  }

  init?(magicWords: MagicWords) {
    self.magicWords = magicWords
  }
}

Đây chính là hàm khởi tạo Spell, đã được cập nhật một tí so với tutorial ở phần đầu. Chú ý Avatar protocol, và hàm khởi tạo thứ 2. Thêm vào để sử dụng sau này, tất cả đều có mục đích cả 😀

protocol Familiar: Avatar {
  var noise: String { get }
  var name: String? { get set }
  init(name: String?)
}
Familiar protocol sẽ được áp dụng lên những loài động vật (như dơi
hoặc cóc) trong tutorial này.

Ghi chú: Giải thích thêm về familiar,  đây là những con vật ma thuật 
hỗ trợ cho phù thủy. Giống như con cú Hedwig trong Harry Potter, 
hay là con khỉ trong Phủ thủy xứ Oz.

Owl

Con này chắc chắn không phải là Hedwig, nhưng vẫn ku-te mà, phải hôn?
struct Witch: Magical {
  var avatar = "*"
  var name: String?
  var familiar: Familiar?
  var spells: [Spell] = []
  var hat: Hat?

  init(name: String?, familiar: Familiar?) {
    self.name = name
    self.familiar = familiar

    if let s = Spell(magicWords: .prestoChango) {
      self.spells = [s]
    }
  }

  init(name: String?, familiar: Familiar?, hat: Hat?) {
    self.init(name: name, familiar: familiar)
    self.hat = hat
  }

  func turnFamiliarIntoToad() -> Toad {
    if let hat = hat {
      if hat.isMagical { // When have you ever seen a Witch perform a spell without her magical hat on ? :]
        if let familiar = familiar {   // Check if witch has a familiar
          if let toad = familiar as? Toad {  // If familiar is already a toad, no magic required
            return toad
          } else {
            if hasSpell(ofType: .prestoChango) {
              if let name = familiar.name {
                return Toad(name: name)
              }
            }
          }
        }
      }
    }
    return Toad(name: "New Toad")  // This is an entirely new Toad.
  }

  func hasSpell(ofType type: MagicWords) -> Bool { // Check if witch currently has an appropriate spell in their spellbook
    let change = spells.flatMap { spell in
      spell.magicWords == type
    }
    return change.count > 0
  }
}

Cuối cùng, Witch (phù thủy) cũng xuất hiện. Các bạn sẽ thấy một số thứ sau qua đoạn code trên:

  • Một Witch được khởi tạo bằng một cái tên và 1 familiar hoặc một cái tên, một familiar và một cái mũ.
  • Một Witch chỉ biết được một số hữu hạn thần chú, và được chứa trong mảng Spell.
  • Một Witch có vẻ như có một khát khao, ước ao biến familiar của mình thành 1 con cóc bằng cách sử dụng cân thần chú .prestoChango, trong hàm turnFamiliarIntoToad().

Xem kỹ hàm turnFamiliarIntoToad(). Nếu có bất kỳ gì không đúng xảy ra, thì một con cóc mới sẽ được trả về. Bối rối nhỉ? Cứ bình tĩnh, bạn sẽ xử lý lại hàm này sau ngay đây thôi.

Xử dụng Swift Errors

“Swift cung cấp cho chúng ta những công cụ hỗ trợ hàng đầu cho việc ném (throwing), bắt (catching), tuyên truyền (propagating), và phục hồi (manipulating
recoverable) lỗi trong khi chạy (runtime).”

– The Swift Programming Language (Swift 3)

 

Như các bạn thấy hàm  turnFamiliarIntoToad() có vấn đề. Vấn đề đó chính là Pyramid of Doom. Pyramid of Doom là một anti-pattern mà chúng ta rất thường gặp trong Swift và các ngôn ngữ khác. Nhìn vào hình dạng của hàm trên ta có thể thấy nó được bao bởi quá nhiều ngoặc nhọn tạo ra hình dáng như một kim tự tháp. Có đến tận 6 cái ngoặc nhọn để bao bọc toàn bộ hàm. Việc đọc 1 đoạn code như thế này hoặc kinh khủng hơn nữa quả là 1 thảm họa đối với các lập trình viên.

Pyramid of Doom

Cú pháp Guard, như các bạn thấy ở tutorial đầu tiên sẽ giúp chúng ta san phẳng cái kim tự tháp này. Tuy nhiên, việc sử dụng cơ chế do-catch sẽ giúp ta hoàn toàn giải quyết được vấn đề bằng cách tách việc control flow ra khỏi việc xử lý lỗi.

Cơ chế do-catch có liên quan đến một số keywords sau:

  • throws
  • do
  • catch
  • try
  • defer
  • Error

Để hiểu rõ hơn về các cơ chế ở trên một cách thực tế và sinh động, chúng ta sẽ tạo ra đủ các kiểu lỗi. Đầu tiên, bạn sẽ định nghĩa danh sách những trạng thái lỗi mà bạn nghĩ có thể xảy ra.

Thêm đoạn code sau phía trên Witch:

enum ChangoSpellError: Error {
  case hatMissingOrNotMagical
  case noFamiliar
  case familiarAlreadyAToad
  case spellFailed(reason: String)
  case spellNotKnownToWitch
}

Có 2 điểm cần chú ý về ChangoSpellError:

  • Nó conforms Error protocol, một protocol bắt buộc phải sử dụng để định nghĩa lỗi trong Swift.
  • Trong trường hợp spellFailed, bạn có thể mô tả  nguyên nhân mà câu thần chú thất bại với một giá trị liên kết (String).
Ghi chú: Enum ChangoSpellError được đặt tên theo câu thần chú “Presto Chango!” – thường được sử dụng bởi Witch khi cố gắng biến familiar của mình thành Cóc (Toad).
Ok, giờ sẵn sàng làm phép chưa, mấy chế? Tuyệt zời ông mặt trời.
Thêm throws vào sau chỗ khai báo tham số của hàm, để báo cho mọi người biết rằng có thể có lỗi sẽ xảy ra nếu gọi hàm này:
func turnFamiliarIntoToad() throws -> Toad {

Cập nhật luôn Magical protocol:

protocol Magical: Avatar {
  var name: String? { get set }
  var spells: [Spell] { get set }
  func turnFamiliarIntoToad() throws -> Toad
}

Giờ bạn đã có danh sách các lỗi, chúng ta sẽ code lại hàm turnFamiliarIntoToad(), từng lỗi một nhé.

Xử lý lỗi cái mũ (Hat)

Đầu tiên, cập nhật dòng code sau để đảm bảo phù thủy đang mang trên người cái mũ cực kỳ quan trọng của họ…

if let hat = hat {

…thành:

guard let hat = hat else {
  throw ChangoSpellError.hatMissingOrNotMagical
}
Lại ghi chú: Đừng quên xóa bớt 1 dấu } ở cuối hàm nhé, không thì không compiile được đâu!

Dòng tiếp theo là kiếm tra Boolean, cũng liên quan tới cãi mũ:

if hat.isMagical {

Bạn có thể thêm 1 dòng cú pháp guard nữa để kiểm tra điều kiện này nhưng mà để rõ ràng hơn thì ta nên gom nhóm nó lại và check trong 1 dòng cho tiện. Cập nhật dòng code đầu tiên thành như sau:

guard let hat = hat, hat.isMagical else {
  throw ChangoSpellError.hatMissingOrNotMagical
}

Rồi xóa dòng này if hat.isMagical { đi vì đã gom check ở trên luôn rầu.

Xử lý lỗi Thú cưng (Familiar)

Tiếp theo, thay thế đoạn code sau…

if let familiar = familiar {

… thành. Ở đây chúng ta sẽ ném một lỗi là noFamiliar ra nếu phù thủy không sở hữu con thú cưng nào để làm phép:

guard let familiar = familiar else {
  throw ChangoSpellError.noFamiliar
}

Nếu thấy XCode báo lỗi gì thì cứ kệ nhé, cứ code tiếp là nó sẽ biến mất thoai.

Xử lý lỗi con Cóc (Toad)

Trong dòng tiếp theo, đoạn code trả về 1 con cóc đang có nếu mụ phù thủy cố gắng sử dùng phép turnFamiliarIntoToad() lên con thú cưng đã là cóc rồi của mình, nhưng chúng ta nên ném lỗi ra để mụ ta sẽ biết rằng mụ ta đang mắc sai lầm. Thay đổi đoạn code:

if let toad = familiar as? Toad {
  return toad
}

…thành như sau:

if familiar is Toad {
  throw ChangoSpellError.familiarAlreadyAToad
}

Lưu ý khi thay đổi từ as? sang is cho chúng ta kiểm tra một cách súc tích, rõ ràng hơn mà không cần một kết quả trả về. Từ khóa is cũng có thể được sử dụng để so sánh kiểu một cách tổng quát hơn. Nếu bạn có hứng thứ tìm hiểu thêm về is và as, xem bài viết về ép kiểu và kiểm tra kiểu dữ liệu của Apple nhé The Swift Programming Language.

Rồi giờ thì đưa hết mọi thứ trong đoạn  else ra ngoài rồi xóa else. Không còn cần nó nữa rồi.

Xử lý lỗi Thần Chú (Spell)

Cuối cùng, hàm hasSpell(_ type:) sẽ được gọi để đảm bảo rằng mụ phù thủy biết thần chú này trong cuốn sách thần chú của mụ. Cập nhật đoạn code sau:

if hasSpell(ofType: .prestoChango) {
  if let name = familiar.name {
    return Toad(name: name)
  }
}

…bằng đoạn code này:

guard hasSpell(ofType: .prestoChango) else {
  throw ChangoSpellError.spellNotKnownToWitch
}

guard let name = familiar.name else {
  let reason = "Familiar doesn’t have a name."
  throw ChangoSpellError.spellFailed(reason: reason)
}

return Toad(name: name)

Và giờ thì bạn có thể xóa dòng code cuối cùng:

return Toad(name: "New Toad")

Bạn đang có một hàm sạch, đẹp và sẵn sàng để sử dụng rồi đấy. Mình đã bổ sung một vài comment trong đoạn code dưới đây để giải thích thêm về những gì mà hàm này thực hiện:

func turnFamiliarIntoToad() throws -> Toad {

  // Có bao giờ bạn thấy một phù thủy sử dụng phép thuật mà không đội
mũ phép không? :]
  guard let hat = hat, hat.isMagical else {
    throw ChangoSpellError.hatMissingOrNotMagical
  }

  // Kiểm tra nếu phù thủy có thú cưng (familiar)
  guard let familiar = familiar else {
    throw ChangoSpellError.noFamiliar
  }

  // Kiểm tra xem thú cưng đã là cóc chưa - nếu rồi thì niệm chú 
làm chi nữa?
  if familiar is Toad {
    throw ChangoSpellError.familiarAlreadyAToad
  }
  guard hasSpell(ofType: .prestoChango) else {
    throw ChangoSpellError.spellNotKnownToWitch
  }

  // Kiểm tra xem thú cưng có tên không
  guard let name = familiar.name else {
    let reason = "Familiar doesn’t have a name."
    throw ChangoSpellError.spellFailed(reason: reason)
  }

  // Nếu mọi thứ kiểm tra đều Ok! Trả về 1 con cóc với tên trùng
với tên của thú cưng (familiar) thoai.
  return Toad(name: name)
}

Vậy xử lý lỗi như vậy thì có ích chi?

Giờ bạn đã có một hàm để ném lỗi mà bạn đã mô tả trong Swift, bạn cần phải xử lý chúng. Cách làm thông thường để xử lý chúng là dùng cú pháp do-catch, khá là giống với cú pháp try-catch ở một số ngôn ngữ khác như Java hay C#.

Thêm đoạn code này vào dưới cùng:

func exampleOne() {
  print("") // In 1 dòng trống trong console

  // 1
  let salem = Cat(name: "Salem Saberhagen")
  salem.speak()

  // 2
  let witchOne = Witch(name: "Sabrina", familiar: salem)
  do {
    // 3
    try witchOne.turnFamiliarIntoToad()
  }
  // 4
  catch let error as ChangoSpellError {
    handle(spellError: error)
  }
  // 5
  catch {
    print("Something went wrong, are you feeling OK?")
  }
}

Sau đây là những gì mà hàm ở trên thực hiện

  1. Tạo 1 thú cưng (familiar) cho mụ phù thủy này. Một con mèo tên là Salem.
  2. Tạo 1 mụ phù thủy tên là Sabrina.
  3. Mụ phù thủy cố gắng biến con thú cưng của mình thành còn cóc.
  4. Bắt lỗi ChangoSpellError và xử lý nó một cách thích hợp.
  5. Cuối cùng, bắt những lỗi khác và in một tin nhắn dễ thương.

Sau khi bạn thêm đoạn code ở trên bạn sẽ thấy báo lỗi – giờ thì sửa lỗi thôi.

Hàm handle(spellError:) chưa được định nghĩa, cho nên thêm đoạn code sau phiên trên hàm exampleOne() :

func handle(spellError error: ChangoSpellError) {
  let prefix = "Spell Failed."
  switch error {
    case .hatMissingOrNotMagical:
      print("\(prefix) Did you forget your hat, or does it need its batteries charged?")

    case .familiarAlreadyAToad:
      print("\(prefix) Why are you trying to change a Toad into a Toad?")

    default:
      print(prefix)
  }
}

Sau cùng, chạy playground bằng việc thêm dòng code sau ở dưới cùng:

exampleOne()

Mở Debug console bằng cách bấm vào mũi tên chỉ lên ở góc trái của Xcode, giờ bạn có thể thấy những gì được in ra trong console.

Expand Debug Area

Bắt lỗi (Catches Error)

Dưới đây là mô tả ngắn những tính năng được sử dụng trong những đoạn code mẫu ở trên.

bắt – catch

Đoạn code trên đã ví dụ một số cách sử dụng catch: một là khi bạn dùng để bắt lỗi ChangoSpell, và một là khi bạn bắt các lỗi khác.

thử – try

Bạn có thể sử dụng kết hợp try trong cú pháp do-catch để chỉ rõ dòng nào hoặc đoạn code nào có thể gây ra lỗi.

Bạn có thể sử dụng try theo những cách khác nhau sau đây:

  • try: thường được sử dụng trong cú pháp do-catch. Được sử dụng ở ví dụ trên.
  • try?: xử lý lỗi bằng cách bơ nó luôn; nếu có lỗi xảy ra, kết quả của đoạn code đó sẽ là  nil.
  • try!: giống với syntax được sử dụng để force-unwrapping 1 biến, cú pháp này có nghĩa là theo giả thuyết thì có thể xảy ra lỗi nhưng trong thực tế sử dụng sẽ không bao giờ xảy ra lỗi.try! có thể được sử dụng cho những việc như load file, khi mà bạn chắc chắn ra cái tài nguyên cần load tồn tại.  Cũng giống như force-unwrap, sử dụng cú pháp này một cách thận trọng và cẩn thận nhé.

Giờ thì thử thực hành với cú pháp try? này xem sao. Cut và paste đoạn code sau vào cuối cùng của playground nào:

func exampleTwo() {
  print("") // Add an empty line in the debug area
    
  let toad = Toad(name: "Mr. Toad")
  toad.speak()
    
  let hat = Hat()
  let witchTwo = Witch(name: "Elphaba", familiar: toad, hat: hat)
    
  print("") // Add an empty line in the debug area
    
  let newToad = try? witchTwo.turnFamiliarIntoToad()
  if newToad != nil { // Same logic as: if let _ = newToad
    print("Successfully changed familiar into toad.")
  }
  else {
    print("Spell failed.")
  }
}

Chú ý rằng khác với exampleOne. Ở đây bạn không quan tâm đến giá trị trả về cũng như lỗi. Nhưng bạn vẫn kiểm soát được nếu lỗi xảy ra,Toad  không được tạo ra và giá trị của  newToad là nil.

Lan truyền lỗi (Propagating Errors)

ném – throws

Từ khóa throw cần thiết trong Swift nếu một hàm hoặc phương thức ném ra một lỗi.

ném lại – rethrows

Những ví dụ từ đầu đến giờ đều dùng throws, thế còn rethrows thì sao?

rethrows  nói với compiler rằng phương thức này chỉ ném lỗi khi và chỉ khi tham số của nó ném lỗi. Xem ngay ví dụ ở dưới nào. ( không cần phải thêm vào playground đâu):

func doSomethingMagical(magicalOperation: () throws -> MagicalResult) rethrows -> MagicalResult {
  return try magicalOperation()
}

Hàm doSomethingMagical(_:) chỉ thém nỗi khi và chỉ khi tham số (cũng là 1 hàm) magicalOperation cũng ném lỗi. Nếu thành công, nó sẽ trả về MagicalResult thay vì lỗi.

Dọn dẹp khi lỗi xảy ra

defer

Cú pháp defer là một cơ chế để thực việc ‘dọn dẹp’  (cleanup) sau khi đoạn mã của chúng ta được thực thi xong, ví dụ lúc một hàm hay phương thức return. Nó rất hữu dụng cho việc quản lý tài nguyên mà chúng ta cần phải thu dọn dù cho hàm có chạy thành công hay không, và dĩ nhiên nó cũng rất hữu dụng cho việc xử lý khi lỗi xảy ra.

Để thấy nó hoạt động thế nào, chúng ta thêm đoạn code sau vào trong struct Witch:

func speak() {
  defer {
    print("*cackles*")
  }
  print("Hello my pretties.")
}

Và thêm đoạn code dưới đây vào dưới cùng của playground:

func exampleThree() {
  print("") // Add an empty line in the debug area

  let witchThree = Witch(name: "Hermione", familiar: nil, hat: nil)
  witchThree.speak()
}

exampleThree()

Trong console, bạn sẽ thấy phù thủy *cackles* sau mỗi lần mụ ta nói (gọi hàm Speak).

Thú vị là, cú pháp defer sẽ được thực thi theo thứ tự ngược lại so với thứ tự khi mà chúng ta code.

Thêm 1 dòng defer nữa vào hàm speak() để mà mụ phú thủy *screeches*  rồi mới *cackles* sau mỗi lần mụ ta nói (gọi hàm Speak).

func speak() {
  defer {
    print("*cackles*")
  }

  defer {
    print("*screeches*")
  }

  print("Hello my pretties.")
}

Có phải thứ tự các dòng được in ra đúng như bạn mong đợi không? Haha, đó là phép thuật của cú pháp defer đấy!

More Fun with Errors

Việc đưa những cú pháp xử lý lỗi ở trên vào Swift đã đưa Swift lên cùng đẳng cấp với những ngôn ngữ phổ biển khác và tách biệt Swift ra khỏi  NSError trong Objective-C.

Mặc dù cú pháp do-catch và những tính năng liên quan khác có chi phí đáng kể trong các ngôn ngữ khác, nhưng trong Swift, nó chỉ được xem và đối xử như những cú pháp hay tính năng thông thường khác, điều này đảm bảo được hiệu quả và hiệu suất cho ngôn ngữ Swift.

Nhưng không phải vì bạn có thể tạo ra lỗi tùy thích và ném tùm lum, không có nghĩa là bạn nên làm như vậy. Bạn nên phát triển một bản quy tắc hay định nghĩa, hướng dẫn khi nào nên ném và bắt lỗi cho từng dự án mà bạn đảm nhiệm. Mình có số gợi ý và lời khuyên sau:

  • Đảm bảo kiểu lỗi được đặt tên rõ ràng và thống nhất.
  • Sử dụng optionals khi chỉ một lỗi trạng thái lỗi duy nhất.
  • Sử dụng định nghĩa lỗi (custom error) khi có nhiều hơn một trạng thái lỗi.
  • Đừng để một lỗi lan truyền quá xa nguồn gốc của chúng.

Tương lai của việc xử lý lỗi trong Swift

Có một vài ý tưởng để xứ lý lỗi nâng cao được thảo luận sôi nổi trong các diễn đàn Swift. Một trong số đó là untyped propagation – lan truyền vô kiểu :v.

“…chúng tôi tin rằng chúng tôi có thể mở rộng mô hình hiện tại để hỗ trợ việc lan truyền không kiểu (untyped propagation ) cho các lỗi toàn cục ( universal errors ). Làm tốt việc này và đặc biệt là làm được điều đó mà không phải hy sinh bất kỳ đoạn mã nào và vẫn giữ được performance, sẽ cần phải tốn khá là nhiều công sức và đầu óc.”

– trích từ Swift 2.x Error Handling

 

Cho dù bạn có cảm thấy hứng thú với các ý tưởng xử lý lỗi mới hay hoàn toàn hài lòng hạnh phúc với phiên bản hiện tại của Swift hay không, thì điều tuyệt vời rằng việc xử lý lỗi vẫn tiếp tục phát triển một cách tích cực song song với sự phát triển của Swift.

Excellent


Nguồn: Magical Error Handling in Swift

Dịch và biên tập bởi: Bùi Minh Đức

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s