ハードルは下げる。ネガティブなことは書かない。

Interfaceの戻り値を制限したい

2021.02.09

エンジニアリングとはなにか。
それは「曖昧さ」を減らし、「具体性」・「明確さ」を増やす行為です。
Interfaceの戻り値を制限し、 世の中の情報エントロピーを減少させましょう!

背景

最近、私はこんなInterfaceに出会いました。

interface CoffeeMilkAnalyzer {
    fun getCoffeeContentRate(target: CoffeeMilk): BigDecimal
}

そこで私が思ったのは、 getCoffeeContentRate って、

  • 少数?(0 <= x <= 1)
  • パーセント?(0 <= x <= 100)

まぁ十中八九、前者なんですが、若干、お前のこと信じてるぞ。みたいな気分になりますよね?
今日はそんな不確実性と戦うため、3つの対策を紹介します。

対策1:コメントを入れる

/**
 * @return 0 <= value <= 1
 */
interface CoffeeMilkAnalyzer {
    fun getCoffeeContentRate(target: CoffeeMilk): BigDecimal
}

もうこれで、99.99%明確になりました。
でも、実装者が本当にこのコメント見て実装してくれてるかは謎です。

まぁ、この周辺でバグったら、とりあえずログ出してみる?とかなりますよね。

対策2:DataClassを使う


interface CoffeeMilkAnalyzer {
    fun getCoffeeContentRate(target: CoffeeMilk): CoffeeContentRate
    
    data class CoffeeContentRate(
           val value: BigDecimal
    ) {
        init {
            require(value >= BigDecimal.ZERO)
            require(value <= BigDecimal.ONE)
        }
    }
}

完璧に制限できてます!
使う側の実装が若干汚くなっちゃうのが気になります。

対策3:BigDecimalを継承する

interface CoffeeMilkAnalyzer {
    fun getCoffeeContentRate(target: CoffeeMilk): CoffeeContentRate
    
    class CoffeeContentRate(val decimal: String)
        :BigDecimal(decimal) {
        init {
            require(this >= BigDecimal.ZERO)
            require(this <= BigDecimal.ONE)
        }

        override fun toByte(): Byte {
            throw UnsupportedOperationException()
        }
        override fun toChar(): Char {
            throw UnsupportedOperationException()
        }
        override fun toShort(): Short {
            throw UnsupportedOperationException()
        }
    }
}

完璧に制限できてます!
毎回定義が面倒とか、コンストラクタが文字列だけとかの問題は、 共通のMyDecimal型を定義して頑張ればいいかなと思います。

ただ、
toByte toChar toShort
これなんだ?

これについては次回、調べて報告します。

以上