Create a gist now

Instantly share code, notes, and snippets.

@koher /q1.md Secret
Created Sep 15, 2017

What would you like to do?

Q1. リバーシのボード

リバーシというのはオセロのことですが、オセロは商標なのでここではリバーシと呼びます。

リバーシのボードは 8 × 8 の 64 のマスがあり、そこにディスクを置いていきます。ですので、 Board 型を作って下記のように subscriptxy を渡して Disk を取得したくなります。もしそのマスにディスクが存在しない場合は nil を返します。

let disk: Disk? = board[x, y]

しかし、このとき、 xy に負の数や 8 以上の数を渡すとエラーになるはずです。そのエラーをどのように表すか考えてみて下さい。

1. Simple domain error

xy がボードからはみ出した場合には、ディスクがなかったとき同様に nil を返すようにします。

struct Board {
  subscript(x: Int, y: Int) -> Disk? {
    guard (0..<8).contains(x) else { return nil }
    guard (0..<8).contains(y) else { return nil }
    ...
  }

  ...
}

2. Universal error

ボードからはみ出してしまった場合をハンドリングすることはできないとして、 fatalError でクラッシュさせます。

struct Board {
  subscript(x: Int, y: Int) -> Disk? {
    guard (0..<8).contains(x) else { fatalError() }
    guard (0..<8).contains(y) else { fatalError() }
    ...
  }

  ...
}

3. Logic failure

同じく回復不能ですが、 precondition でチェックして、コードのミスとしてコードの修正を促します。

struct Board {
  subscript(x: Int, y: Int) -> Disk? {
    precondition((0..<8).contains(x))
    precondition((0..<8).contains(y))
    ...
  }

  ...
}

Q2. アプリにバンドルされたデータ

アプリを作ってるとデータをアプリにバンドルすることはよくあります。今、何らかのファイルがアプリにバンドルされていて、それを読み込んで Foo 型の値を返す関数、 loadFoo を作りたいとしましょう。その読み込みに失敗したとき、エラーをどのように表せば良いでしょうか?

let foo: Foo = loadFoo()

1. Recoverable error

読み込みに失敗したことを Errorthrow して表します。

func loadFoo() throws -> Foo {
  guard let path = Bundle.main.path(forResource: "Foo", ofType: "json") else {
    throw ...
  }
  let url = URL(fileURLWithPath: path)
  let data = try Data(contentsOf: url)
  return try JSONDecoder().decode(Foo.self, from: data)
}

2. Universal error

読み込みに失敗したら回復不能としてクラッシュさせます。

func loadFoo() -> Foo {
  guard let path = Bundle.main.path(forResource: "Foo", ofType: "json") else {
    fatalError()
  }
  let url = URL(fileURLWithPath: path)
  do {
    let data = try Data(contentsOf: url)
    return try JSONDecoder().decode(Foo.self, from: data)
  } catch {
    fatalError()
  }
}

3. Logic failure

プログラムのミスとしてプログラム自体の修正を促します。

func loadFoo() -> Foo {
  let path = Bundle.main.path(forResource: "Foo", ofType: "json")!
  let url = URL(fileURLWithPath: path)
  let data = try! Data(contentsOf: url)
  return try! JSONDecoder().decode(Foo.self, from: data)
}

Q3. センサーからのデータ取得

画像でも位置情報でもなんでもいいですが、端末搭載のセンサーを使って Foo 型のデータを取得しようとする場合を考えて下さい。もしそのセンサーがバックグラウンドでも動作するなら、他のアプリと競合してデータを取得できないという場合も考えられます。そんなとき、どのようにエラーを表わせば良いでしょう?

let foo: Foo = readFoo()

1. Recoverable error

センサーからデータを取得できなかったことを Errorthrow することで表現します。

func readFoo() throws -> Foo {
  guard isFooSensorAvailable() else {
    throw ...
  }
  ...
}

2. Universal error

センサーからデータを取得できないという状態はハンドリング不能としてクラッシュさせます。

func readFoo() -> Foo {
  guard isFooSensorAvailable() else {
    fatalError()
  }
  ...
}

3. Logic failure

センサーがデータを取得できないような状態でデータを取得しようとすること自体が間違えているとしてコードの修正をうながします。

func readFoo() -> Foo {
  precondition(isFooSensorAvailable())
  ...
}

Q4. ゲームのセーブデータ

↓のように、ゲームのセーブデータをロードする関数 loadGame を考えます。戻り値が Optional なのは、初回はセーブデータが存在しないからです。

let game: Game? = loadGame()

セーブデータはファイルか何かで永続化されているとします。 iOS アプリだと通常はアプリ外からデータにアクセスできないですが、 Jailbreak されるとセーブデータにアクセスして改造されてしまうかもしれません。

チートを防ぐためには、セーブデータにソルトを加えてハッシュをとるなどして一緒に保存しておいて、セーブデータが改造されて整合が取れなくなってしまった場合にはエラーにするような方法が考えられます。そのような場合、エラーはどう表現すれば良いでしょうか?

1. Recoverable error

実行時のエラーハンドリングを可能にするためにハッシュとの整合がとれないと Errorthrow します。

func loadGame() throws -> Game? {
  ...
  guard hash == savedHash else {
    throw ...
  }
  ...
}

2. Universal error

回復不能としてクラッシュさせます。

func loadGame() -> Game? {
  ...
  guard hash == savedHash else {
    fatalError()
  }
  ...
}

3. Logic failure

バグとしてコードの修正をうながします。

func loadGame() -> Game? {
  ...
  precondition(hash == savedHash)
  ...
}

Q5. 画像の生成

画像を表す Image 型のイニシャライザを考えます。

let image = Image(
  width: width,
  height: height,
  pixels: pixels
)

幅、高さ、ピクセル列を引数として画像インスタンスを生成します。幅と高さは当然負の数にはならないのですが、符号なし整数は取り扱いが難しいので Swift ではたとえ負の数が入らない場合でも UInt を使うことはまれです。 Foundation で Objective-C で NSUInteger だったところも、 Swift から見ると Int になってたりします。

ということは、型の上では負の数を渡されてしまう可能性があるわけで、当然エラーになります。また、 pixels の要素数は幅×高さと一致するか、少なくともそれより長い必要がありますが、もし pixels が幅×高さより短ければエラーです。そんなとき、エラーをどのように表現すれば良いでしょうか。

1. Simple domain error

実行時にエラーハンドリングを可能にするためにエラー時は nil を返します。

struct Image<Pixel> {
  init?(width: Int, height: Int, pixels: [Pixel]) {
    guard width >= 0 else { return nil }
    guard height >= 0 else { return nil }
    guard pixels.count == width * height else { return nil }
    ...
  }
}

2. Universal error

回復不能としてクラッシュさせます。

struct Image<Pixel> {
  init(width: Int, height: Int, pixels: [Pixel]) {
    guard width >= 0 else { return fatalError() }
    guard height >= 0 else { return fatalError() }
    guard pixels.count == width * height else { return fatalError() }
    ...
  }
}

3. Logic failure

バグとしてコードの修正をうながします。

struct Image<Pixel> {
  init(width: Int, height: Int, pixels: [Pixel]) {
    precondition(width >= 0)
    precondition(height >= 0)
    precondition(pixels.count == width)
    ...
  }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment