関数は「単一責任の原則」に従って設計すべし

black and white cat lying on brown bamboo chair inside room

一般に、関数は単一責任の原則(Single Responsibility Principle)に従って設計されるべきで、これは関数が一つのことだけを行うべきだという原則です。

ChatGPT

前回記事の続きだ。

さて、コード整理にあたって真っ先に手を付けたくなったのが次の関数だ。

JavaScript
function validatePostalCodeAndUpdateAddress(inputElementId) {
            
    // 入力内容をクリアする
    document.getElementById('address').textContent = '';
    document.getElementById('english_address').textContent = '';
    document.getElementById('postal_code_error').textContent = '';

    // 郵便番号を検証する
    validatePostCode().then(validatedCode => {
        // 郵便番号の検証が成功したら
        // setFieldsDisabled(false); // 他のinputを入力可にする
        return getAddressFromPostalCode(validatedCode); // 住所を取得する
    }).then(address => {
        // 住所情報が取得できたら、mainAddressを更新し、
        // updateAddressDisplay関数を使って画面上に表示する
        mainAddress = {
            postalCode: address.postalCode,
            prefecture: address.prefectureName,
            city: address.cityName,
            choiki: address.choikiName
        };
        let formattedPostalCode = mainAddress.postalCode.replace(/^(\d{3})(\d{4})$/, '$1-$2');
        mainAddressString = '' + formattedPostalCode + ' ' + mainAddress.prefecture + mainAddress.city + mainAddress.choiki;
        updateAddressDisplay();
    }).catch(error => {
        // エラーが発生したら、エラーメッセージを表示する
        displayError(error.code, error.field);
        setFieldsDisabled(true);

        // 清除 address 和 english_address 的内容
        document.getElementById('address').textContent = '';
        document.getElementById('english_address').textContent = '';
    });
}

validatePostalCodeAndUpdateAddressという長大な名前からもわかるように、これは郵便番号(PostalCode)を検証(validate)し、画面上で日本語の住所(Address)を更新(Update)するという、まさに多機能な関数である。

郵便番号を検証する機能と、画面表示を更新する機能に分離させ、可能な限りシンプルな機能を持つ関数に分解してから、パズルのように各機能を組み合わせ直していきたいと思う。

入力があるかどうかだけをチェックする関数の作成

まず、次のように頼んでみた。▼

郵便番号入力のありなしをチェックする関数をChatGPTに作ってもらう。

郵便番号を検証する機能をさらに、入力の有無をチェックする機能と、入力有りの場合に7桁の数字であるかどうかをチェックする機能に分けようと思った。上ではまずは前者を頼んだのである。

ChatGPTが郵便番号入力の有無をチェックする関数を作成してくれる。

うーん、長い。単純な機能の関数にしては長すぎる。おまけに頼んでいないのにclearPostalCodeErrorsという別の関数まで作っている!

郵便番号の入力がある場合は「Yes」(True)を、ない場合は「No」(False)を教えてくれる関数がほしいだけなので、これでは駄目である。

郵便番号入力の有無をチェックする関数を論理値で返す仕様に修正
ChatGPTが郵便番号入力の有無をチェックする関数を理論値を返す仕様に修正

望む形になってきた。次に、関数の上につけるかっこいい説明がほしい。言い方がよくわからないのでアバウトに頼んでみた。わかってくれるかな?

関数の説明コメントをChatGPTに書いてもらう
郵便番号入力の有無をチェックする関数にJSDocコメントをChatGPTが書いてくれる

ちゃんと理解してくれた。「/**」で始まり、「*/」で終わる部分がほしかったかっこいい説明である。JSDoc形式というんだね。

要素の値を関数に渡すように修正

望む形になってきたけど、関数内でフォーム要素から値を直接取ってくる形がちょっと望ましくないと思った。次のコードのことである。

JavaScript
let postalCode = document.getElementById('postal_code').value.trim();

なぜ望ましくないかというと、仮に後でフォーム要素「postal_code」(上のコードの中の黄色い部分)の名前を変えたら、関数内でも修正しなければならない。この関数だけならまだしも、他にも同じ仕組みの関数があれば、それぞれ修正しなければならず、面倒だし、ミスも起こりやすい。それにそもそもこの形がちょっとダサい。

郵便番号を引数として渡すことにしようと思う。自分で修正してみた。

修正した郵便番号入力の有無をチェックする関数が正しいかをChatGPTにチェックを依頼する。
ChatGPTが郵便番号入力の有無をチェックする関数を修正してくれる。

//でコメントアウトした行を削除し、JSDocコメントも更新してくれた。

コメントを少し変えて、最終的に次のコードになった。

JavaScript
        /**
         * 郵便番号入力の有無をチェック
         *
         * @param {string} - postalCode チェックする郵便番号
         * @return {boolean} 入力ありでtrue、なしでfalseを返す
         */
        function checkPostalCodeInput(postalCode) {   

            return postalCode.trim() !== "";

        }

これで、フォームのpostal_code欄に入力があった場合はtrueを返し、ない場合(半角スペースだけなどの場合を含む)はfalseを返す関数が出来上がった。

単一責任の原則で関数を設計すべし

ここで自分のやったことが正当かどうかについて確認したくなり、下記のごとく聞いてみた。

ChatGPTにエラー表示の処理を関数の外ですべきかについて質問
ChatGPTが「単一責任の原則」について教えてくれる。

相変わらず回答が長いが、関数は「単一責任の原則」に従って設計されるべきだということで、私の取り組みが正しかったと理解して良かろう。