特定の機能を関数に抽出する必要がありますか?


29

3つのタスクを実行する大きなメソッドがあり、それぞれを個別の関数に抽出できます。そのタスクごとに追加の関数を作成する場合、コードが良くなるか悪くなるか、そしてその理由は何ですか?

明らかに、メイン関数のコード行は少なくなりますが、追加の関数宣言があるので、クラスには追加のメソッドがありますが、クラスがより複雑になるため、これは良くないと思います。

すべてのコードを書く前にそれを行うべきですか、それともすべてが完了するまでそのままにしてから関数を抽出する必要がありますか?


19
「すべてが完了するまでそのままにします」は、通常「決して行われない」と同義です。
陶酔

2
それは一般に真実ですが、YAGNIの反対の原則も覚えています(すでに必要なので、この場合は適用されません)。
12


強調したかったのは、コードの行を減らすことにあまり集中しないことです。代わりに、抽象化の観点から考えてみてください。各機能には1つのジョブのみが必要です。関数が複数のジョブを実行していることがわかった場合、通常はメソッドをリファクタリングする必要があります。これらのガイドラインに従えば、過度に長い機能を持つことはほぼ不可能です。
エイドリアン

回答:


35

これは私がよくリンクする本ですが、ここでもう一度行きます。ロバートC.マーティンのクリーンコード、第3章、「関数」。

明らかに、メイン関数のコード行は少なくなりますが、追加の関数宣言があるので、クラスには追加のメソッドがありますが、クラスがより複雑になるため、これは良くないと思います。

+150行の関数、または3つの+50行関数を呼び出す関数を読みたいですか?私は2番目のオプションを好むと思います。

はい、それはより「読みやすい」という意味であなたのコードを改善します。ただ1つのことを実行する関数を作成します。それらは保守が容易で、テストケースを作成しやすくなります。

また、前述の本で私が学んだ非常に重要なこと:あなたの機能に適切で正確な名前を選んでください。関数がより重要であるほど、名前は最も正確でなければなりません。名前の長さを気にする必要はありませんFunctionThatDoesThisOneParticularThingOnly。名前を呼び出す必要がある場合は、そのように名前を付けます。

リファクタリングを実行する前に、1つ以上のテストケースを作成します。動作することを確認してください。リファクタリングが完了したら、これらのテストケースを起動して、新しいコードが正しく機能することを確認できます。追加の「より小さい」テストを作成して、新しい関数が分離可能に実行されることを確認できます。

最後に、これは私が今書いた内容に反していません。本当にこのリファクタリングを行う必要があるかどうかを自問し、「いつリファクタリングするか?」の答えを確認してください (また、「リファクタリング」に関するSOの質問を検索してください、もっとあり、答えは読むのが面白いです)

すべてのコードを書く前にそれを行う必要がありますか、それともすべてが完了するまでそのままにしてから関数を抽出する必要がありますか?

コードがすでに存在し、機能していて、次のリリースに間に合わない場合は、触れないでください。そうでなければ、可能な場合はいつでも小さな関数を作成し、すべてが以前と同じように動作することを確認しながら、いつでもリファクタリングする必要があると思います(テストケース)


10
実際、ボブ・マーティンは、15行の1つの関数よりも2〜3行の7つの関数を好むことを何度も示しています(こちらのsite.google.com/site/unclebobconsultingllc/…を参照)。そして、それは多くの経験豊富な開発者でさえ抵抗するところです。個人的には、これらの「経験豊富な開発者」の多くは、10年以上のコーディングの後、関数を使用して抽象化を構築するような基本的なことを改善できると受け入れるのに苦労しているだけだと思います。
ドックブラウン

私のささやかな意見のために、どんなソフトウェア会社の棚にもあるべき本を参照するためだけに+1。
ファビオマルコリーニ14年

3
私はここで言い換えているかもしれませんが、ほぼ毎日私の頭に響く本からのフレーズは、「各機能は1つのことだけを行い、それをうまくやる」ことです。OPは「私の主な機能は三つありません」と述べたので、ここでは特に関連と思われる
wakjah

あなたは絶対に正しいです!
ジャレイン

3つの別個の関数がどの程度絡み合っているかによります。相互に繰り返し依存する3つのコードブロックよりも、すべて1か所にあるコードブロックに従う方が簡単な場合があります。
user253751

13

はい、明らかに。単一の機能の異なる「タスク」を簡単に確認して分離できる場合。

  1. 読みやすさ-良い名前の関数は、コードを読む必要なくコードが何をするかを明確にします。
  2. 再利用性-必要のないことを行う関数を使用するよりも、複数の場所で1つのことを行う関数を使用する方が簡単です。
  3. テスト容易性-1つの定義済み「関数」を持つ関数、またはそれらの多くを持つ関数をテストする方が簡単です

ただし、これには問題がある可能性があります。

  • 関数を分離する方法を見るのは簡単ではありません。これには、分離に進む前に、最初に関数内部のリファクタリングが必要になる場合があります。
  • 関数には巨大な内部状態があり、渡されます。これには通常、何らかのOOPソリューションが必要です。
  • どの機能を実行すべきかを判断するのは困難です。ユニットテストを行い、あなたが知るまでリファクタリングしてください。

5

あなたが提起している問題は、コーディング、慣習、またはコーディング慣行の問題ではなく、読みやすさの問題であり、テキストエディターがあなたが書いたコードを表示する方法です。これと同じ問題が投稿でも見られます:

長い関数やメソッドを他のものから呼び出されなくても小さなものに分割しても大丈夫ですか?

関数をサブ関数に分割することは、構成するさまざまな機能をカプセル化する目的で大きなシステムを実装する場合に意味があります。それでも、遅かれ早かれ、あなたは多くの大きな機能を自分自身で見つけるでしょう。それらのいくつかは、単一の長い関数として保持するか、より小さい関数として分割するかどうかを維持するのが難しく、維持するのが困難です。これは、システムの他の場所で操作を行う必要がない機能に特に当てはまります。このような長い関数の1つをピックアップして、より広い視野で考えてみましょう。

プロ:

  • それを読むと、関数が行うすべての操作について完全なアイデアが得られます(本として読むことができます)。
  • デバッグする場合は、他のファイル/ファイルの一部にジャンプすることなく、ステップごとに実行できます。
  • 関数の任意の段階で宣言された変数にアクセス/使用する自由があります。
  • 関数が実装するアルゴリズム(関数に完全に含まれる(カプセル化された))。

コントラ:

  • 画面の多くのページが必要です。
  • 読むのに時間がかかります。
  • すべての異なるステップを記憶するのは簡単ではありません。

ここで、長い関数をいくつかのサブ関数に分割し、それらをより広い視野で見てみましょう。

プロ:

  • leave関数を除き、各関数は、実行されたさまざまなステップを単語(サブ関数の名前)で記述します。
  • それぞれの単一の機能/サブ機能を読み取るのに非常に短い時間がかかります。
  • 各サブ機能で影響を受けるパラメーターと変数は明らかです(懸念の分離)。

コントラ:

  • "sin()"のような関数が何をするかは簡単に想像できますが、サブ関数が何をするかは簡単に想像できません。
  • アルゴリズムは消滅し、mayサブ機能に分散されました(概要はありません)。
  • 段階的にデバッグするとき、あなたが来ている深さレベルの関数呼び出しを忘れるのは簡単です(プロジェクトファイルのあちこちにジャンプします);
  • さまざまなサブ機能を読み取るときに、コンテキストを簡単に失うことができます。

どちらのソリューションにも賛否両論があります。実際の最良の解決策は、各関数がコンテンツを呼び出すために、インラインで完全な深さまで拡張できるエディターを持つことです。これにより、サブ関数の関数を分割することが唯一の最良のソリューションになります。


2

私にとっては、コードブロックを関数に抽出する4つの理由があります。

  • あなたはそれを再利用しています:あなたはただコードのブロックをクリップボードにコピーしました。単に貼り付けるのではなく、関数に入れて、ブロックを両側の関数呼び出しで置き換えます。そのため、そのコードブロックを変更する必要があるときはいつでも、コードを複数の場所で変更するのではなく、その単一の関数を変更するだけで済みます。したがって、コードブロックをコピーするたびに、関数を作成する必要があります。

  • これはコールバックです。イベントハンドラー、またはライブラリまたはフレームワークが呼び出すユーザーコードです。(関数を作成せずにこれを想像することはほとんどできません。)

  • 現在のプロジェクトまたは他の場所で再利用されると思われます。2つの配列の最も長い共通サブシーケンスを計算するブロックを作成しただけです。あなたのプログラムがこの関数を一度しか呼び出さないとしても、他のプロジェクトでも最終的にこの関数が必要になると思います。

  • 自己文書化コードが必要です。そのため、コードのブロック上にコメントの行を記述してコードの機能を要約するのではなく、全体を関数に抽出し、コメントに記述する名前を付けます。私はこれのファンではありませんが、使用されているアルゴリズムの名前、そのアルゴリズムを選択した理由などを書き出すのが好きなので、関数名は長すぎます...


1

変数のスコープはできるだけ厳しくするべきだというアドバイスを聞いたことがあると思いますが、それに同意していただければ幸いです。さて、関数はスコープのコンテナであり、小さな関数ではローカル変数のスコープは小さくなります。それらがどのようにいつ使用されるかがより明確になり、間違った順序で、または初期化される前に使用するのが難しくなります。

また、関数は論理フローのコンテナです。唯一の方法があり、その方法は明確に示されており、機能が十分に短い場合、内部フローは明らかです。これには、循環の複雑さを減らす効果があり、欠陥の発生率を減らす信頼できる方法です。


0

余談:dallinの質問(現在は閉じられています)に応じてこれを書きましたが、まだ誰かに役立つと思うので、ここに行きます


関数をアトマイズする理由は2倍だと思います。@ jozefgが述べているように、使用する言語に依存しています。

関心事の分離

これを行う主な理由は、コードの異なる部分を分離しておくためです。そのため、関数の望ましい結果/意図に直接寄与しないコードブロックは、個別の懸念事項であり、抽出できます。

プログレスバーも更新するバックグラウンドタスクがあるとします。プログレスバーの更新は、実行中のタスクに直接関係しないため、プログレスバーを使用するコードの唯一の部分であっても抽出する必要があります。

JavaScriptで関数getMyData()があるとします。1)パラメーターからSOAPメッセージを作成し、2)サービス参照を初期化し、3)SOAPメッセージでサービスを呼び出し、4)結果を解析し、5)結果を返します。私はこの正確な関数を何度も書いていますが、実際に 3と5のコードのみを含む3つのプライベート関数に分割することができます(その場合) 。

改善されたデバッグエクスペリエンス

完全にアトミックな機能がある場合、スタックトレースはタスクリストになり、正常に実行されたすべてのコードがリストされます。

  • 私のデータを取得
    • 石鹸メッセージの作成
    • サービス参照の初期化
    • 解析されたサービス応答-エラー

データの取得中にエラーが発生したことがわかると、より興味深いものになります。ただし、詳細なコールツリーのデバッグには、Microsoft Debugger Canvasなどのツールがさらに便利です。

また、このように書かれたコードを追跡するのは難しいかもしれないという懸念を理解しています。ただし、関数の名前が適切であり(インテリセンスを使用すると、速度を落とすことなく任意の関数で3〜4個のカマルケースワードを使用できます)、ファイルの先頭にパブリックインターフェイスが構築されている場合、コードは擬似コードのようになりますコードベースの高レベルな理解を得る最も簡単な方法です。

参考までに、これは「私が言うとおりにしない」ことの1つで、コードをアトミ​​ックに保つことは、冷酷に私見と矛盾しない限り無意味です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.