Kotlin: 関数(Function)とプロパティ(Property)、どちらを使うべき?【翻訳】

5 min read読了の目安(約4700字

この記事は著者 Igor Wojda(@igorwojda) 氏の許可を得て翻訳したものです。

Original article: Kotlin: should I define Function or Property? - Kt. Academy


最近、プロパティ(property)と関数(function)の使いどころがややこしいことに気づいた。Kotlinがプロパティのコンセプトを取り入れたのには理由がある。しかし、それぞれいつ使うべきなのか?その答えとしておすすめの、最もシンプルなルールが以下だ。

  • プロパティ(property) は状態を定義する
  • 関数(function) は振る舞いを定義する

もっと詳細に見ていこう。プロパティ(property) はオブジェクトの状態を定義する設計になっている。例えば、Personオブジェクト には namelastNameweight のプロパティを持てる。

class Person (var name:String, var lastName:String, var weight:Double)

from gist

また、その場で計算される派生プロパティ(derived properties)も持てる。例えば、namelastName を組み合わせることで作る、fullName というプロパティがそれだ。

class Person (var name:String, var lastName:String, var weight:Double) {
    val fullName = "$name $lastName"
}

from gist

しかし上の例では、プロパティはPersonオブジェクトが作られたときに一度しか評価されない。時にはその動きで問題ない場合もある。しかし今回はnameやlastNameを変更したとき、fullNameが変更されないのは問題だ。幸運なことに、Kotlinにはプロパティにgetterやsetterを定義できる仕組みがある。下記のサンプルのように書けば、fullNameの値はプロパティにアクセスするたびに評価される。

class Person (var name:String, var lastName:String, var weight:Double) {
    val fullName
        get() = "$name $lastName"
}

from gist

一方で、関数(Function) はオブジェクトが取り得るすべての振る舞いや行動だ。これまでに作ってきたPersonクラスrun(), walk(), jump() のメソッドを持っている。

class Person () {
    fun run() { /*doSth*/ }

    fun walk() { /*doSth*/ }

    fun jump() { /*doSth*/ }
}

from gist

重要なポイントとして、メソッドは実行されると間接的にオブジェクトの状態を書き換えることがある。例えば下記のコードは、jump()を実行するたびにPersonオブジェクトのweightが0.1ずつ減っていく。

class Person (var name:String, var lastName:String, var weight:Double) {
    //..

    fun jump() { 
        weight -= 0.1   
        /*doSth*/ 
    }
}

from gist

もしまだ、プロパティと関数どちらをいつ使うべきかがわからないなら、外からの呼び出しを考えてみるのが助けになるかもしれない。一度、内部の実装について(プロパティがどこに値を持ち、どのように振る舞うように実装されているか)は忘れて、単純に外部のAPIとして(サードパーティのライブラリをプロジェクトに入れて、APIにだけアクセスできるような感じで)考えてみよう。

person.name = "Igor"
person.weight = 79
person.jump()
person.jump()
person.jump()

from gist

Kotlinがプロパティを取り入れたのには理由がある。ひとつはより簡潔な property access syntax (getHeight/setHeightの代わりにheightを使える)だ。Javaのようにプロパティの宣言をソースの上の方に書いて、getter/setterを下の方に書くようなことはせずに、プロパティの宣言のすぐ近くにgetterとsetterを書くことができる。委譲プロパティ(property delegates)は私のお気に入りで、コードの再利用性を上げてくれる。必要なときに変数を初期化できるし(lazy delegate)、なにかのアクションをトリガーにしてプロパティの値を変えることもできる(observable delegate)。また、カスタムデリゲート(custom delegate)を使って、シンプルに他のオブジェクト(Android shared preferences, map, browser session, database…)に値を入れることができる。

Kotlinが関数もプロパティも両方使えるようになっていることはとても良いことだ。

Java(のようなプロパティのコンセプトを持たない言語)から来た開発者は関数を使いすぎる傾向がある。下記のコードは、プロパティにリファクタリングできる良い例だ。

//BAD
class Person () {
    private var weight:Double = 0

    fun setWeight(weight:Double) {
        this.weight = weight
    }

    fun getWeight(): Double {
        return weight
    }
}

//GOOD
class Person (var weight:Double)

from gist

setで始まり、ひとつのパラメータをプライベートフィールドに代入していたり、getで始まり、プライベートフィールドの値を返すようなメソッドがあったりする場合、おそらく代わりにプロパティを使うべきだ。

ガイドライン:どうやって決めればよいか?

新しい関数を書こうとするときに、毎回2つの質問に答えてみよう。

  • 「それは振る舞いを書くのか?」— 振る舞いを定義する場合は関数(function) が向いている。例: run(), walk(), jump()
  • 「それは状態を書くのか?」— 状態を定義する場合はプロパティ(property) が向いている。 例: name, lastName, weight

さらに、関数よりもプロパティを使う方が良い条件が以下のようにある。(記憶が正しければ、Effective Javaに載っていた)

  • 例外を投げない
  • 計算の負荷が少ない(もしくは最初の実行でキャッシュする)
  • 複数回実行しても同じ値を返す

上記のガイドラインは、特別でないメンバー変数を関数にすべきかプロパティにすべきかの参考になる。最初は、(特にJava開発者にとって)プロパティを定義することは少し不自然に感じるかもしれない。しかし信じてほしい、しばらくすれば明確にどちらで書くべきかを決められるようになる。


この記事は著者 Igor Wojda(@igorwojda) 氏の許可を得て翻訳/意訳したものです。間違いがある場合はコメントか、@d_forestまでお願いします。

Original article: Kotlin: should I define Function or Property? - Kt. Academy

Thank you Igor!