Morichan
@Morichan

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

メソッドの引数に指定するものは、オブジェクト全体とオブジェクト一部のどちらが良いですか?

引数を必要とするメソッドを定義してください。
定義するメソッドの引数は、呼出す側が持つオブジェクトの一部の要素だけを必要とします。

その時、メソッドの引数として、次のどれかを選び、理由を説明してください。

  • 呼出す側が持つオブジェクト全体
  • 呼出す側が持つオブジェクトのうち必要とする一部の要素
  • その他

理由の観点は、凝集度/結合度/保守性/可読性/個人的趣味など、一定の支持が得られるものであれば自由とします。
また、プログラミング言語固有の書き方を元に理由とする場合、対象のプログラミング言語も指定してください。

背景と説明

先日、メソッドの引数としてオブジェクト全体とオブジェクト一部、どちらが良いのか、という話がありました。
対象は JavaScript (Node.js) です(が、どのようなプログラミング言語でも通じるかなと思い、あえてタグ付けはしていません)。

登場人物は、以下の通りです。

  • use_child メソッド: child オブジェクトを用いて処理を実行するメソッド。ただし、将来的にも child オブジェクトだけを見ればいいだけと断定はできない(かなり不明瞭)
  • main メソッド: use_child メソッドを呼出すメソッド。 use_child 以外の、 parent オブジェクトが保持するオブジェクトを処理するメソッドを呼出す
  • child オブジェクト: use_child メソッドが処理に必要とするオブジェクト
  • parent オブジェクト: child オブジェクトを保持するオブジェクト。プログラミング言語のコア機能で生成される(ため、このオブジェクト自体の修正は難しい)。 child オブジェクト以外にも多くのオブジェクトを持ち、それぞれの階層はバラバラである

私は、下のようなソースコードのようが好きです。

オブジェクト全体を求めるメソッド
function main() {
    // 呼出し側が持つオブジェクト
    const parent = {
        child: "wanna use item" // 存在保証は無い
    }

    // 問題のメソッド呼出し
    use_child(parent)
}

function use_child(parent) {
    // parent.child のバリデーションチェック

    const child = parent.child

    // child を用いた処理
}

理由は、次の点です。

  • main メソッドに、 parent.child のバリデーションチェックをしてほしくない
    • use_child メソッドを呼出す側で parent.child の存在判定や正常判定などをするのは、責務として違う(気がする)
    • parent.child の場所が変わった場合、 use_child メソッドではなく main メソッドを修正する必要があるため、凝集度が下がる(気がする)
    • main メソッドには use_child 以外にもメソッドがあるため、 main メソッドでのバリデーションチェック処理が肥大化する(気がする)
  • main メソッドに限らず)呼出し側が use_child メソッドの実装を理解する必要がない
    • use_child メソッドで parent.child を呼出していることを、呼出し側が理解して引数に入れるべきではない

一方、レビュアーからは、次のコードを提案されました。

オブジェクト一部を求めるメソッド
function main() {
    // 呼出し側が持つオブジェクト
    const parent = {
        child: "wanna use item" // 存在保証は無い
    }

    // parent.child のバリデーションチェック

    // 問題のメソッド呼出し
    use_child(parent.child)
}

function use_child(child) {
    // child を用いた処理
}

理由は、次の点です。

  • main メソッドと use_child メソッドの結合度を下げたい
  • JavaScriptでは parent.child が存在しない場合でも、呼出し自体は実行時エラーとならないため、 use_child メソッド内で child のバリデーションチェックを行うだけで良い

結局、そこまで大きな問題とはならないだろうということで、話自体は終わりましたが、実際のところ、どっちがいいのかモヤモヤしていたので、質問してみました。

個人的には、両者言い分は間違っていないと思っています。
そのため、片方を糾弾するような答えは控えていただけると助かります。
自分の主張の方が正しい!ということを言いたいわけではありません。

わかりづらい点などありましたら、追加で説明させていただきます。

2

コードの抽象的な構造だけでは、どちらを採用すべきかは決められず、コードの意味・内容によって書くべきコードは変わってくると思います。
例えば、use_childが他の場所でどれぐらい呼び出されるのか、等によって変わります。

以下、すべて極端な例です。

例えば、

shisan = {
    genkin: 100
    yokin: 150
}

のとき、

get_soushisan(genkin, yokin) {
    return genkin + yokin
}

とするよりは、

get_soushisan(shisan) {
    return shisan.genkin + shisan.yokin
}

としておいた方が、get_soushisanが他の場所で呼び出される場合には、今後shisanに株や不動産などが増えた場合には修正しやすいでしょう。その意味で、例えばchild以外の引数が追加される可能性があるというような場合に、それがすべて親の要素で収まるならば、親を渡す方が良いかなと思います。

一方で、main()と書かれているものがshisanの初期化に相当する処理であったとすれば、そもそも分割をする必要が無い可能性もあります。

function main() {
    // main()という名前だが、実際にはshisanの初期化をしている
    shisan.soushisan = get_soushisan(shisan)
}

と書かずに、

function main() {
    shisan.soushisan = shisan.genkin + shisan.yokin
}

と直接書いた方がよい可能性もあります。
しかし、このように書くのは、+(shisan.genkin, shisan.yokin)という関数をget_soushisanの代わりに使っているのと同じです。つまり、ある場面においては、親を渡す関数を定義するのではなくて、子を渡す関数を定義する方が自然という事にもなり得ます(もっとも、+は標準で定義されていると思いますが。)

構造から少し離れて、元の例の命名に関して考えます。仮に、"use_child"という名前が適切な関数であるとします。
このとき、childがChildという型であると思うなら、use_child(child: Child)つまり子を引数とする関数と思う事が自然なので、childを渡す方がよいのかなと思います。
一方、childというプロパティを使うという意味であれば、use_child(parent: MayHaveChild)つまり子を持ち得るMayHaveChild型の引数を受け取る関数と思う事が自然なので、parentを渡す方がよいのかなと思います。
本当は違う名前だと思いますが、極論でいうと、「関数の名前による」という事もあり得ると思います。
関数の名前が構造に影響するというのは変に見えるかもしれませんが、関数の名前というのは処理構造を端的に表現するもののはずなので、もし先に名前が浮かぶような構造であったとすれば、名前に従うのがベターな選択という事はあり得ます。
もっとも、この例の場合は、上述の通りどちらとも取れるような気もするので、もっと正しい名前を付けた方がよいような気はしますが。

また、バリデーションの責務などをどちらが持つか?という事ですが、これもuse_child()の意味に依存すると思います。parentがchildを持つかどうかをmain()側で本当に何もケアする必要がなければ、use_child()の中で存在チェックをした方がmain()は見通しがよくなりますが、main()側でケアする必要があるならば、use_child()の前にチェックをした方が分岐等は制御しやすいかと思います。
例えば、今はuse_child()という関数で済んでいたのが、様々な理由で
use_child_1(), use_child_2(), ..., use_child_n()
みたいに増える可能性があったり、null(undefined)か否かによって処理フローが変わる可能性があるならば、そもそもmain()でチェックをして適切な関数呼び出しを行うようにした方が見通しが良いように思われます。

その他の観点でいうと、個人的には引数にあまりOptional(nullable)を増やしたくないので、その意味ではnullが入る関数呼び出しはあまり作りたくないな、とは思います。JSの場合、たとえTSで書いていたとしても簡単にnullや指定した型以外のものが入ってくることはあるので、結局子を使う関数の中でチェックをした方が安全というのはありますが...

9Like

@sasanquaneuf

コードの意味・内容によって書くべきコードは変わってくる

やはり、結論はこれですよね。
難しい問だと思いましたが、意見をいただきありがとうございます。

関数の名前というのは処理構造を端的に表現するもののはずなので、もし先に名前が浮かぶような構造であったとすれば、名前に従うのがベター
もっとも、この例の場合は、上述の通りどちらとも取れるような気もするので、もっと正しい名前を付けた方がよい

関数が構造を表すという意見は、むしろ同意です。
use_child という名前は適当に付けすぎました。
例題として child だけを使うかのような名前を明記すべきではなかったですね、すみません。

個人的には引数にあまりOptional(nullable)を増やしたくない

同意です。
しかし JavaScript という性質上、関数内での null チェックは必須レベルですね......
おのれ JavaScript

1Like

引数を何にするかという観点より、ある処理をどこで行うかという観点で考えれば、自ずと引数は決まってくると思います。

以下、いくつかの例。

class Child {
    do_somethig() {
    }
}

ParentがChildを必ず持ち、parentが持つchildを使う処理を実行する場合

class Parent {
    constructor(child) {
        this.child = child;
    }

    use_child() {
        this.child.do_something();
    }
}

// 呼び出し側コード
parent = new Parent(new Child());
parent.use_child();

ParentはChildを持つが、必ず持つとは限らない場合

class Parent {
    constructor() {
        this.child = null;
    }

    set_child(child) {
        this.child = child;
    }

    use_child() {
        // childがあれば処理し、なければなにもしない
        if (!this.child) {
            return;
        }
        this.child.do_something();
    }
}

// 呼び出し側コード
parent = new Parent();
parent.use_child();

Parentの処理ではあるが、その処理にChildが必要で呼び出し時に変更したい場合

strategyパターンとか。

class Parent {
    use_child(child) {
        // do somethig with child and my properties
    }
}

// 呼び出し側コード
parent = new Parent();
child = new Child();
parent.use_child(child);

Childが持つとは限らない属性を使う処理の場合

グローバルなメソッド、あるいは全く別のクラスのメソッドとして実装する。

function do_something(foo, bar) {
   // do something with foo, bar
}

// 呼び出し側コード
child = new Child();
use_child(child.getFoo(), child.getBar());
0Like

レビュアーに少し共感しました:sweat_smile:

呼出す側が持つオブジェクト全体

の場合、意図があるかを考えやすい ため、理由を聞いてしまうかもしれません。

その理由が、

  • 他もオブジェクトを渡しているのが多い
  • メソッドの意味がわかり易いと思う

等の曖昧なものでも、構わないと思います。
しかし、全く理由がないのであれば、

呼出す側が持つオブジェクトのうち必要とする一部の要素

のほうが、コミュニケーションの発生しない 省エネ なコードだと思います。

(性格のねじ曲がった人だと、コピペ&削除を疑われるかも:confounded:

どっちがいいのかモヤモヤしていた

ということであれば、些細な事でも理由があったのではないかと思いますので、それを素直に表現できると良かったかも:rolling_eyes:

0Like

↑すいません。理由はしっかり書いていただいてましたね。
モヤモヤしたのは(私のように、)レビュアー側のコミュニケーション能力の問題ですね:sweat_smile:

0Like

@error_401
やっぱり、Parentオブジェクトがメソッドを持つのが正式な気がしますよね
ただ、 プログラミング言語のコア機能で生成される(ため、このオブジェクト自体の修正は難しい) という制約があるので、どのようにグローバル関数化すれば良いのかなぁと思った次第です

ただ、今思えば、JavaScriptって関数オブジェクトを追加しようと思えばできますね
そういう作り方を考えてみるのも面白いかもしれません


@j5c8k6m8

意図があるかを考えやすい

言われてみればそうですね
自明であれば聞くことがないことも、ちょっと「ん?」って思うようなことは聞きたくなりますね

些細な事でも理由があったのではないかと思いますので、それを素直に表現できると良かったかも
コミュニケーション能力の問題

理由なのですが、実は当日はここまで詳細に書いてませんでした(この質問を書いているうちに綺麗にしたものです)
という意味では、こちらのコミュニケーション不足とも言えそうです

2Like

Your answer might help someone💌