最近の宿題でuglyReceipt(cashParser(cashInput()))
は、プログラム自体が完璧に機能する醜い方法で関数を呼び出すことになりましたが、それでも何か間違ったことをしているように感じました。
関数の呼び出しはこのような悪い習慣であり、そうであれば、代わりに何をすべきですか?
最近の宿題でuglyReceipt(cashParser(cashInput()))
は、プログラム自体が完璧に機能する醜い方法で関数を呼び出すことになりましたが、それでも何か間違ったことをしているように感じました。
関数の呼び出しはこのような悪い習慣であり、そうであれば、代わりに何をすべきですか?
回答:
これは実際に使用するネストの量に依存します。結局のところ、読みやすくするために、式の中で関数の結果を直接使用することが許可されています。ネストされた式を使用しないコード(アセンブラコードなど)と、ネストされた式を使用しすぎるコードはどちらも読みにくいです。優れたコードは、両極端のバランスをとろうとします。
それでは、いくつかの例を見てみましょう。あなたがあなたの質問で与えたものは私にはかなり合法のようですので、ここで心配することは何もありません。ただし、次のような行
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)
合計6つの異なる演算を1つの式に組み合わせたとしても、理解ははるかに簡単です。
私のアドバイスは、頭の中で簡単に解析できる表現に注意を払うことです。単一の式が何をするかを理解するために再検討する必要がある瞬間、追加の変数を導入する時が来ました。
それが良いか悪いかは状況に大きく依存すると思います。これが悪いと考えられる主な理由は、コードの読み取りとデバッグが(間違いなく)困難になるためです。これは、プログラミングを初めて学ぶときに特に当てはまります。コーディングスキル(およびコード)がより高度になるにつれて、これが受け入れられる場合があります。
たとえば、次のようなエラーメッセージを考えてみます。
line 1492: missing argument "foo"
不足している引数がある場合に、あなたはどのように知っているcashInput
、cashParser
またはuglyReceipt
?言語によっては、エラーメッセージで通知される場合と通知されない場合があります。
これらの関数呼び出しを分解しても、エラーメッセージが1492行を示している場合、問題がどこにあるのかがすぐにわかります。
1491: input = cashInput()
1492: parsed_value = cashParser(input)
1493: receipt = uglyReceipt(parsed_value)
ステップを個別に分割すると、任意のステップでブレークポイントを設定できるため、デバッグがはるかに簡単になり、ローカル変数の値を変更することで値を簡単に挿入できます。
あなたの質問の根底にあるコンセプトは非常に重要なので、コメントだけではなく別の答えが必要だと思います(私が始めたように)。
これまでの他の3つの答えは、「ネストされた関数呼び出し」と呼ばれるものを使用して、特定の状況がメリットがあるかどうかについて、いくつかの有用な考慮点を提供します。しかし、おそらくもっと重要な点が質問の下のコメントに隠されています。それらのエルディテの人々が示唆している微妙なところを見逃した場合のために、実際に関数型プログラミングと呼ばれるトピックを発見しました。この用語を見たことがない場合は、@ HighPerformanceMarkのコメントでそれが本当に「もの」であるとは思わなかったかもしれません。
しかし、確かにそうです!関数型プログラミングは、John Hughesの独創的な論文Why Functional Programming Mattersから何十年もの間書かれてきました 。関数型言語(つまり、関数型プログラミングスタイルでのみ記述できる)、Erlang、Lisp、OCaml、Haskellなどの言語があります。しかし、命令型/関数型のハイブリッド言語である言語は他にもたくさんあります。つまり、これらは伝統的に命令型言語ですが、Perl、C ++、Java、C#など、関数型プログラミングもサポートしています。ウィキペディアの関数型プログラミングに関するエントリーは、多くの言語の関数型スタイルと命令型スタイルの比較を示す素晴らしいセクションを提供します。
命令型スタイルと関数型スタイルの違いについては言うことがたくさんありますが、重要な出発点は、関数型プログラミングでは、関数またはメソッドに副作用がないため、一般にプログラムの理解とデバッグの両方が容易になるということです。
詳細については、Reginald Braithwaiteの「なぜ関数型プログラミングが重要なのか」という問題と、SO に関するもう1つの興味深い投稿、「なぜ関数型言語なのか」もご覧ください。
一般的に、これは決して悪いことではありません。関数呼び出しは値を受け入れ、値を生成する1つの方法は別の関数を呼び出すことです。
変数が定義されているのを見たとき:
parsed_value = cashParser(input)
...私はparsed_value
それが複数回使用される可能性があることを考慮する必要があり、おそらくこれが正しいかどうかを確認する必要があります(私の変更が他の場所で壊れている場合はどうなりますか?)さらに変数を追加し始めると、読者がそれらのすべてとそれらがどのように関連しているかを追跡することがより複雑になる可能性があります。だから私が見ると安心します:
receipt = uglyReceipt(cashParser(input))
...中間値のスコープ/ライフタイムが明らかであるため。これで、いつものように、特に変数名で値の目的をより正確にできる場合は、長い式を別々のステートメントに分割すると役立つ場合があります。
user_name = input()