9

多くの が関数のサイズについて議論しています。彼らは、一般的に機能はかなり短くすべきだと言っています。意見は、15 行程度のものから「1 画面程度」までさまざまで、現在ではおそらく 40 ~ 80 行程度です。
また、関数は常に 1 つのタスクのみを実行する必要があります。

ただし、コードの両方の基準で頻繁に失敗する関数の種類が 1 つあります。初期化関数です。

たとえば、オーディオ アプリケーションでは、オーディオ ハードウェア/API をセットアップし、オーディオ データを適切な形式に変換し、オブジェクトの状態を適切に初期化する必要があります。これらは明らかに 3 つの異なるタスクであり、API によっては、簡単に 50 行を超えることもあります。

init-functions の特徴は、一般に 1 回だけ呼び出されるため、コンポーネントを再利用する必要がないことです。それでもそれらをいくつかの小さな関数に分割しますか?大きな初期化関数は問題ないと思いますか?

4

9 に答える 9

12

それでもタスクごとに関数を分割し、公開されている初期化関数内から各下位レベルの関数を呼び出します。

void _init_hardware() { }
void _convert_format() { }
void _setup_state() { }

void initialize_audio() {
    _init_hardware();
    _convert_format();
    _setup_state();
}

簡潔な関数を書くことは、物事を読みやすくすることと同じくらい、障害と変化を分離することです。障害が発生していることがわかっている場合は_convert_format()、バグの原因となっている最大40行をかなり速く追跡できます。1つの関数にのみ触れる変更をコミットする場合も同じことが当てはまります。

最後に、私はassert()非常に頻繁に使用するので、「頻繁に失敗し、早期に失敗する」ことができます。関数の開始は、いくつかの健全性チェックのアサートに最適な場所です。関数を短くすることで、より狭い範囲の職務に基づいて関数をより徹底的にテストできます。10の異なることを行う400行の関数を単体テストするのは非常に困難です。

于 2010-04-12T13:24:52.690 に答える
5

小さな部分に分割すると、コードの構造が改善されたり、読みやすくなったりする場合は、関数の機能に関係なく実行してください。行数ではなく、コードの品質です。

于 2010-04-12T13:22:32.823 に答える
3

それでも、関数を論理ユニットに分割しようとします。それらは、意味のある長さまたは短さである必要があります。例えば:

SetupAudioHardware();
ConvertAudioData();
SetupState();

それらに明確な名前を割り当てると、すべてがより直感的で読みやすくなります。また、それらを分解すると、将来の変更や他のプログラムがそれらを再利用しやすくなります。

于 2010-04-12T13:25:31.060 に答える
2

このような状況では、個人的な好みの問題になると思います。私は関数に1つのことだけを実行させることを好みます。そのため、一度だけ呼び出された場合でも、初期化を別々の関数に分割します。ただし、誰かが1つの関数ですべてを実行したい場合は、(コードが明確である限り)あまり心配する必要はありません。議論すべきもっと重要なことがあります(中括弧が独自の別の行に属しているかどうかなど)。

于 2010-04-12T13:24:30.433 に答える
1

相互に接続する必要のあるコンポーネントが多数ある場合は、可能であれば各コンポーネントの作成を個別のメソッドにリファクタリングしたとしても、大規模なメソッドを使用するのは当然のことです。

これに代わる方法の1つは、依存性注入フレームワーク(Spring、Castle Windsor、Guiceなど)を使用することです。それには明確な長所と短所があります...1つの大きな方法を実行するのは非常に苦痛ですが、少なくともすべてが初期化される場所については良い考えがあり、「魔法」が何をしているのかを心配する必要はありません。 。この場合も、展開後に初期化を変更することはできません(たとえば、SpringのXMLファイルの場合のように)。

コードの本体を注入できるように設計することは理にかなっていると思いますが、その注入がフレームワークを介して行われるのか、ハードコードされた(そして潜在的に長い)初期化呼び出しのリストを介して行われるのかは、変更される可能性のある選択です。さまざまなプロジェクトのために。どちらの場合も、アプリケーションを実行する以外に結果をテストするのは困難です。

于 2010-04-12T13:24:39.240 に答える
1

まだ言及されていないので、これを捨てようと思っただけです.Facadeパターンは、複雑なサブシステムへのインターフェースとして引用されることがあります。私自身はあまり使ったことはありませんが、メタファーは通常、コンピューターの電源を入れる (いくつかの手順が必要です) か、ホーム シアター システムの電源を入れる (テレビの電源を入れる、レシーバーの電源を入れる、照明を消すなど) ようなものです。 .)

コード構造によっては、大規模な初期化関数を抽象化することを検討する価値があるかもしれません。関数を などに分解すること_init_X(), _init_Y()は良い方法ですが、私はまだ megar の主張に同意します。このコードでコメントを再利用しない場合でも、次のプロジェクトで「その X コンポーネントをどのように初期化したか?」と自問自答するときに、戻ってそれを選択する方がはるかに簡単です。_init_X()特にX初期化が全体に散らばっている場合は、大きな関数からそれを選択するよりも小さな関数の。

于 2010-04-12T13:53:23.027 に答える
1

まず、初期化関数の代わりにファクトリを使用する必要があります。つまり、 have ではなく、initialize_audio()a を持っていますnew AudioObjectFactory(ここでより適切な名前を考えることができます)。これにより、関心の分離が維持されます。

ただし、抽象化が早すぎないようにも注意してください。明らかに、すでに 2 つの懸念事項があります。1) オーディオの初期化と 2) そのオーディオの使用です。たとえば、初期化するオーディオ デバイスを抽象化するか、初期化中に特定のデバイスを構成する方法を抽象化するまで、ファクトリ メソッド (audioObjectFactory.Create()またはその他のもの) は実際には 1 つの大きなメソッドにとどめておく必要があります。初期の抽象化は、設計を難読化するだけです。

audioObjectFactory.Create()単体テストできるものではないことに注意してください。テストは統合テストであり、抽象化できる部分ができるまで統合テストのままです。後で、さまざまな構成の複数の異なるファクトリがあることに気付く場合があります。その時点で、ハードウェアの呼び出しをインターフェイスに抽象化することが有益な場合があります。これにより、ユニット テストを作成して、さまざまなファクトリが適切な方法でハードウェアを構成していることを確認できます。

于 2010-04-12T13:36:32.363 に答える
1

行数を数えてそれに基づいて関数を決定しようとするのは間違ったアプローチだと思います。初期化コードのようなものについては、別の関数を用意することがよくありますが、ほとんどの場合、Load 関数、Init 関数、または New 関数が雑然として混乱しないようにするためです。他の人が提案したように、いくつかのタスクに分けることができれば、役に立つ名前を付けて整理することができます。一度だけ呼び出す場合でも、それは悪い習慣ではありません。また、再初期化してその関数を再度使用したい場合が他にもあることに気付くことがよくあります。

于 2010-04-12T13:47:50.730 に答える
1

あなたがタグ付けしたように、機能の長さは非常に主観的な問題です。ただし、標準的なベスト プラクティスは、頻繁に繰り返されるコードや、独自のエンティティとして機能する可能性のあるコードを分離することです。たとえば、初期化関数が特定のライブラリで使用されるライブラリ ファイルまたはオブジェクトをロードしている場合、そのコード ブロックをモジュール化する必要があります。

そうは言っても、多くの繰り返しコードや抽象化できるその他のスニペットのために長くない限り、長い初期化メソッドを持つことは悪くありません。

お役に立てば幸いです,
カルロス・ヌニェス

于 2010-04-12T14:22:27.337 に答える