web-dev-qa-db-ja.com

ネストされた関数呼び出しの使用は悪いことですか?

最近の宿題で、私は醜い方法で関数を呼び出すことになりましたuglyReceipt(cashParser(cashInput()))プログラム自体は完全に機能しましたが、それでも何か間違ったことをしているように感じました。

関数の呼び出しはこのような悪い習慣であり、そうであれば:代わりに何をすべきですか?

9
Carl groth

これは実際に使用するネストの量に依存します。結局のところ、読みやすくするために、関数の結果を式で直接使用することが許可されています。ネストされた式を使用しないコード(アセンブラコードなど)と、ネストされた式が多すぎるコードはどちらも読みにくいです。優れたコードは、極端なものの間でバランスをとろうとします。

それでは、いくつかの例を見てみましょう。あなたがあなたの質問で与えたものは私にはかなり合法であるため、ここで心配する必要はありません。ただし、次のような行

_foo(bar(baz(moo, fab), bim(bam(ext, rel, woot, baz(moo, fab)), noob), bom, zak(bif)));
_

絶対に耐えられないでしょう。同様に、次のようなコード

_double xsquare = x*x;
double ysquare = y*y;
double zsquare = z*z;
double xysquare = xsquare + ysquare;
double xyzsquare = xysquare + zsquare;
double length = sqrt(xyzsquare);
_

同様に非常に読みにくいでしょう。 sqrt(x*x + y*y + z*z)は、1つの式に合計6つの異なる操作を組み合わせたものですが、はるかに理解しやすくなっています。

私のアドバイスは、頭の中で簡単に解析できる表現に注目することです。単一の式が何をするかを理解するために再検討する必要がある瞬間、追加の変数を導入する時が来ました。

それが良いか悪いかは状況に大きく依存すると思います。これが悪いと考えられる主な理由は、コードの読み取りとデバッグが(間違いなく)難しくなるためです。これは、プログラミングを初めて学ぶときに特に当てはまります。コーディングスキル(およびコード)がより高度になるにつれて、これが許容できる場合があります。

たとえば、次のようなエラーメッセージを考えてみます。

line 1492: missing argument "foo"

欠落している引数がcashInputcashParser、またはuglyReceiptに対するものであるかどうかをどのようにしてわかりますか?言語によっては、エラーメッセージで通知される場合と通知されない場合があります。

これらの関数呼び出しを分解しても、エラーメッセージが1492行を示している場合は、問題の場所がすぐにわかります。

1491: input = cashInput()
1492: parsed_value = cashParser(input)
1493: receipt = uglyReceipt(parsed_value)

ステップを個別に分割すると、任意のステップでブレークポイントを設定できるため、デバッグがはるかに簡単になり、ローカル変数の値を変更することで値を簡単に挿入できます。

4
Bryan Oakley

あなたの質問の根底にあるコンセプトは非常に重要なので、コメントだけではなく別の答えが必要だと思います(私が始めたように)。

これまでの他の3つの答えは、特定の状況が「ネストされた関数呼び出し」と呼ばれるものを使用してメリットがあるかどうかについて、いくつかの有用な考慮点を提供します。しかし、おそらくより重要な点が質問の下のコメントに隠されています:エルディットの人々が示唆していることの微妙さを逃した場合は、カール、あなたは実際に関数と呼ばれるトピックを発見しましたプログラミング。この用語を見たことがない場合は、@ HighPerformanceMarkのコメントでそれが本当に「もの」であるとは思わなかったかもしれません。

しかし、確かにそうです!関数型プログラミングは、ジョンヒューズの独創的な論文 関数型プログラミングが重要な理由 以来、数十年にわたって書かれてきました。関数型言語(つまり、関数型プログラミングスタイルでのみ記述できる)、Erlang、LISP、OCaml、Haskellなどの言語があります。しかし、命令型/関数型のハイブリッド言語である言語はもっとたくさんあります。つまり、これらは伝統的に命令型言語ですが、Perl、C++、Java、C#など、関数型プログラミングもある程度サポートしています。 Wikipediaの 関数型プログラミング に関するエントリは、いくつかの言語の関数型スタイルと命令型スタイルの比較を示す素晴らしいセクションを提供します。

命令型と関数型のスタイルの違いについては言うべきことがたくさんありますが、重要な出発点は、関数型プログラミングでは、関数またはメソッドに副作用がないことであり、一般的に簡単になります。プログラムの理解とデバッグの両方。

詳細については、Reginald Braithwaiteの なぜ「関数型プログラミングが重要なのか」が重要 と、SOに関する別の興味深い投稿 なぜ関数型言語なのか? もご覧ください。

3
Michael Sorens

それは一般的に絶対に悪い習慣ではありません。関数呼び出しは値を受け入れ、値を生成する1つの方法は別の関数を呼び出すことです。

変数が定義されているのを見ると、次のようになります:

parsed_value = cashParser(input)

...私はそれを考慮しなければなりませんparsed_valueは複数回使用される可能性があり、これが正しいかどうかを確認する必要があります(変更が他の場所で壊れた場合はどうなりますか?)。さらに変数を追加し始めると、読者がそれらのすべてとそれらがどのように関連しているかを追跡することがより複雑になる可能性があります。だから私が見ると安心します:

receipt = uglyReceipt(cashParser(input))

...中間値のスコープ/ライフタイムが明らかであるため。さて、いつものように、特に変数名で値の目的をより正確に示すことができる場合は、長い式を別々のステートメントに分割すると役立つ場合があります。

user_name = input()
2
coredump