こんにちは。コミュニケーションアプリ「LINE」のモバイルクライアントを開発している石川です。
この記事は、毎週木曜の定期連載 "Weekly Report" 共有の第 62 回です。 LINEヤフー社内には、高い開発生産性を維持するための Review Committee という活動があります。ここで集まった知見を、Weekly Report と称して毎週社内に共有しており、その一部を本ブログ上でも公開しています。(Weekly Report の詳細については、過去の記事一覧を参照してください)
二匹目の close
以下のコードは、あるリソースを管理するためのクラスです。リソースの使用後は close を呼ぶことでリソースの開放をすることを期待しています。close を呼び出したときにリソースがすでに解放済みだった場合は、error によってランタイムエラーが投げられます。
class FooResource {
var isClosed = false
private set()
...
fun close() {
if (isClosed) {
error(...)
}
... // Release resources
isClosed = true
}
}
このコードに何か改善点はありますか?
同じことを聞く
上記のコードで close を安全に呼ぶためには、isClosed の確認が必要になります。
if (!fooResource.isClosed) {
fooResource.close()
}
この close を呼ぶ箇所が増えてくると、isClosed のチェックを漏らしてしまい、意図せずランタイムエラーを発生させてしまう可能性があります。
FooResource.close に関しては、Java や Kotlin の AutoCloseable と同様に 冪等 (idempotent) にすると良いでしょう。冪等とは、1 回呼び出した結果と、2 回以上呼び出した結果が同じになるという性質です。以下の実装では、close を 1 回呼び出した場合と、close を 2 回以上呼び出した場合で、結果は等しく「リソースをリリースし、isClosed を true にする」というものになります。
class FooResource {
var isClosed = false
private set()
...
fun close() {
if (isClosed) {
return
}
... // Release resources
isClosed = true
}
}
関数が冪等性 (idempotency) を満たすことで、以下のような利点を享受できる可能性があります。
- 関数を呼び出す前に状態チェックを行う必要がなくなる
- 関数呼び出し後の状態が予測しやすくなる
- 「初期状態」が存在することを隠蔽できる
この内、「関数を呼び出す前に状態チェックを行う必要がなくなる」と「関数呼び出し後の状態が予測しやすくなる」という利点は、冪等でない関数にも応用できます。エラーとなる呼び出しで、「例外を使う代わりに、何もしない」という動作にするだけで良いです。
戻る場合でも
状態遷移が巡回する場合でも、「状態チェックを行う必要がなくなる」と「関数呼び出し後の状態が予測しやすくなる」の 2 つの利点を活かすことができます。以下の Animation クラスには "running" と"not-running" の 2 つの状態があり、それぞれ setRunning(true) と setRunning(false) を呼ぶことで相互に遷移可能です。このとき、すでに "running"/"not-running" であるときに setRunning(true)/setRunning(false) を呼び出した場合の動作は、「何もしない」になります。
class Animation {
private var isRunning = false
fun setRunning(isRunning: Boolean) {
if (this.isRunning == isRunning) {
return
}
this.isRunning = isRunning
...
}
}
状態遷移は次のようになります。

この状態遷移では、setRunning(true) と setRunning(false) をそれぞれ別の関数とみなせば、冪等な関数と言うことができます。
このように、すでに遷移済みの場合は何もしないという挙動にすることで、複数のイベントハンドラが setRunning を呼び出す場合に、コードを単純にしやすくなります。現在の状態にかかわらず、setRunning の引数によって最新の状態が決定されるということが保証されているためです。
3つの状態でも
この考え方は、2 つの関数をもつ 3 状態の場合にも適用できます。代表的な例として、次の 2 つを紹介します。
- 特定の関数が他の関数よりも優先される場合
- 最初に呼び出された関数が他の関数よりも優先される場合