Help us understand the problem. What is going on with this article?

[Swift] 例外を投げるためのカスタムオペレータ

More than 1 year has passed since last update.

これはなに?

例外を投げる関数を作る時、単純な条件で例外を投げるためにguard文を書くのが煩わしいことがありませんか?
その煩わしさを軽減させるカスタムオペレータを作りました。

使用する場面

JSONが入っているであろうDataから特定の値を取り出す場面を考えましょう。
普通ならこのようになるでしょう。

仕様

  1. Dataを受け取る
  2. JSONは { "identifier": "hogehoge"; }
  3. 戻り値として"hogehoge"を返す
  4. 問題があった場合は例外を投げる

例外

enum MyError: Error {
  case couldNotParseJSON
  case jsonNotDictionay
  case jsonHasNotIdentifier
}

通常のコード

func getIdentifier(_ data: Data) throws -> String {

  guard let json = try? JSONSerialization.jsonObject(with: data) else {
    throw MyError.couldNotParseJSON
  }

  guard let dict = json as? [String: Any] else {
    throw MyError.jsonNotDictionay
  }

  guard let identifier = dict["identifier"] as? String else {
    throw MyError.jsonHasNotIdentifier
  }

  return identifier
}

やっていることは単純なのですが、すごく長いです。

カスタムオペレータを使ってみる

上の関数をカスタムオペレータを導入して簡略化します。

func getIdentifier(_ data: Data) throws -> String {

  let json = try JSONSerialization.jsonObject(with: data) !!! MyError.couldNotParseJSON
  let dict = try json as? [String: Any] ??! MyError.jsonNotDictionay

  return try dict["identifier"] as? String ??! MyError.jsonHasNotIdentifier
}

3行になりました。

カスタムオペレータ

カスタムオペレータは以下の通りです。

precedencegroup ThrowPrecedence {
    associativity: left
    higherThan: NilCoalescingPrecedence
}
infix operator !!! : ThrowPrecedence
infix operator ?!! : ThrowPrecedence
infix operator ??! : ThrowPrecedence


/// 左辺値が例外を投げた場合に右辺値の例外を投げる
///
/// - Parameters:
///   - value: 例外が発生しうる値
///   - throwsError: 投げられる例外
/// - Returns: 例外が発生しなければ左辺値
/// - Throws: 右辺値の例外が投げられる
func !!! <T, E: Error> (_ value: @autoclosure () throws -> T, _ throwsError: E) throws -> T {

    do {

        return try value()
    }
    catch {

        throw throwsError
    }
}


/// 左辺値がnilまたは例外を投げた場合に右辺値の例外を投げる
///
/// - Parameters:
///   - optionalValue: nilまたは例外が発生しうる値
///   - throwsError: 投げられる例外
/// - Returns: 例外が発生しなく、かつnilでなければ左辺値
/// - Throws: 右辺値の例外が投げられる
func ?!! <T, E: Error> (_ optionalValue: @autoclosure () throws -> T?, _ throwsError: E) throws -> T {

    guard let returnValue = try optionalValue() !!! throwsError else {

        throw throwsError
    }

    return returnValue
}


/// 左辺値がnilならば右辺値の例外を投げる
///
/// - Parameters:
///   - optionalValue: nilになりうる値
///   - throwsError: 投げられる例外
/// - Returns: nilでなければ左辺値
/// - Throws: 右辺値の例外が投げられる
func ??! <T, E: Error> (_ optionalValue: @autoclosure () -> T?, _ throwsError: E) throws -> T {

    return try optionalValue() ?!! throwsError
}

Errorをジェネリクスを使って書いてますが、今のSwiftだと意味ないです。
投げる例外の型を指定できるようになるといいですね。

使いどころ

Errorの型を指定するResultやErrorの型を指定するFutureと組み合わせると最強です。
個人の感想です

ご注意

カスタムオペレータは諸刃の剣です。
多用すると意味不明になることがままあるので注意しましょう。

masakihori
非えんじにあ / 素人が雰囲気で書いてます
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした