LINEヤフー Tech Blog

LINEヤフー株式会社のサービスを支える、技術・開発文化を発信しています。

コード品質向上のテクニック:第39回 作業の前に連絡を

こんにちは。コミュニケーションアプリ「LINE」のモバイルクライアントを開発している石川です。

この記事は、毎週木曜の定期連載 "Weekly Report" 共有の第 39 回です。 LINEヤフー社内には、高い開発生産性を維持するための Review Committee という活動があります。ここで集まった知見を、Weekly Report と称して毎週社内に共有しており、その一部を本ブログ上でも公開しています。(Weekly Report の詳細については、過去の記事一覧を参照してください)

作業の前に連絡を

以下の FooMediaPlayer は、メディアを再生するためのクラスです。メインとなる関数は play で、その中の initMedia によってメディアがロードされ、playMedia で実際の再生処理を行います。また、再生状態は、playingState によって公開されます。

class FooMediaPlayer {
    private var playingMediaMetaData: MediaMetaData? = null
    private var playingMedia: Media? = null

    var playingState: PlayingState = PlayingState.None
        private set

    fun play(mediaMetaData: MediaMetaData) {
        playingMediaMetaData = mediaMetaData
        initMedia()
        playMedia()
    }

    private fun initMedia() {
        val mediaPath = playingMediaMetaData?.filePath
        if (mediaPath != null && !isValidMediaFile(mediaPath)) {
            playingMedia = null
            return
        }

        ...
        playingMedia = ...
    }

    private fun playMedia() {
        val media = playingMedia
        if (media == null) {
            playingState = PlayingState.None
            return
        }

        ...
        playingState = PlayingState(media, ...)
        ...
    }
}

このコードに、何か改善すべき点はありますか?

「連絡」できる依存関係

initMediaplayMedia には、「先に initMedia を呼ばなければならない」という制約があります。しかし、呼び出し順を変えてもコンパイルは通ってしまい、実行時にエラーが発生することになります。このように 呼び出し順に制約がある場合は、引数などを使って、制約を明示的にする と良いでしょう。別の言い方をすると、正しい順序が強制されるような設計にできると良いです。

以下の実装では、playMedia の引数に fetchMedia の戻り値を要求しているため、fetchMediaplayMedia の呼び出し順が間違いにくいようになっています。

class FooMediaPlayer {
    var playingState: PlayingState = PlayingState.None
        private set

    fun play(mediaMetaData: MediaMetaData) {
        val media = fetchMedia(mediaMetaData)
        playingState = playMedia(media)
    }

    private fun fetchMedia(metaData: MediaMetaData): Media? {
        if (!isValidMediaFile(metaData.filePath)) {
            return null
        }

        val media = ...
        ...
        return media
    }

    private fun playMedia(media: Media?): PlayingState {
        if (media == null) {
            return PlayingState.None
        }

        ...
        return PlayingState(media, ...)
    }
}

凝集度と結合度の観点から

順序を明示するために引数を使うことは、凝集度 (strength/cohesion) をより強くし、結合度 (coupling) をより弱くすることも意味します。

凝集度: 改善前の play は、順序が決まっている複数の動作で構成されているため、手続き的強度 (procedural strength) の一種になっています。一方で改善後は、戻り値と引数で情報を受け渡しする関係になっているので、連絡的強度 (communicational strength) という、より強い凝集度になっています。

結合度: 改善前の initMediaplayMedia では、インスタンスプロパティで情報を共有しているので、クラスのスコープで見たとき 共通結合 (common coupling) になっていると言えます。一方で、改善後のコードは、構造化された値を引数として受け渡ししているので、スタンプ結合 (stamp coupling) という、より弱い結合度になっています。結合度については、 https://speakerdeck.com/munetoshi/code-readability-session-6-ver-2-ja も併せてご参照ください。

一言まとめ

動作の順序を明確にするために、引数と戻り値を効果的に使う。

キーワード: implicit dependency, function flow, strength

コード品質向上のテクニックの他の記事を読む

コード品質向上のテクニックの記事一覧