プライベート関数/メソッドが多すぎるようなものはありますか?


63

よく文書化されたコードの重要性を理解しています。しかし、自己文書化コードの重要性も理解しています。特定の機能を視覚的に読みやすくするほど、ソフトウェアのメンテナンス中にすばやく移動できます。

とはいえ、私は大きな機能を他の小さな機能に分離するのが好きです。しかし、1つのパブリックメソッドを提供するためだけに、クラスが5つまで持つことができるようになります。ここで、5つのプライベートメソッドに5つのパブリックメソッドを掛けると、それらのパブリックメソッドによって1回だけ呼び出される可能性のある、25の隠しメソッドを取得できます。

確かに、これらのパブリックメソッドは読みやすくなりましたが、機能が多すぎるのは悪い習慣だと思わざるを得ません。

[編集]

人々は、なぜ機能が多すぎるのが悪い習慣だと思うのかと私に尋ねてきました。

簡単な答え:それは直感です。

私の信念は、少しの間、ソフトウェアエンジニアリングの経験に支えられていません。「ライターのブロック」を与えたのは、プログラマーにとってだけの不確実性です。

過去には、私は個人的なプロジェクトのみをプログラミングしてきました。チームベースのプロジェクトに移ったのはつい最近のことです。ここで、他の人がコードを読んで理解できるようにします。

何が読みやすさを改善するのか分かりませんでした。一方では、1つの大きな関数を、わかりやすい名前を持つ他の小さな関数に分離することを考えていました。しかし、それは単に冗長であると言っている私の側面がありました。

それで、正しい道を選ぶために自分自身を啓発するように私はこれを求めています。

[編集]

以下は、私がどのように2つのバージョンが含ま可能性が私の問題を解決します。最初のものは、コードの大きな塊を分離しないことでそれを解決します。2番目別のことを行います。

最初のバージョン:

public static int Main()
{
    // Displays the menu.
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

2番目のバージョン:

public static int Main()
{
    DisplayMenu();
}

private static void DisplayMenu()
{
    Console.WriteLine("Pick your option");
    Console.Writeline("[1] Input and display a polynomial");
    Console.WriteLine("[2] Add two polynomials");
    Console.WriteLine("[3] Subtract two polynomials");
    Console.WriteLine("[4] Differentiate two polynomials");
    Console.WriteLine("[0] Quit");
}

上記の例では、後者はプログラムのランタイム全体で一度だけ使用される関数を呼び出します。

注:上記のコードは一般化されていますが、私の問題と同じ性質のものです。

さて、ここに私の質問があります:どれですか?最初のものを選びますか、それとも2番目のものを選びますか?


1
「機能が多すぎるのは悪い習慣です。」どうして?質問を更新して、なぜこれが悪いように見えるか説明してください。
S.Lott

「クラスの凝集性」の概念を読むことをお勧めします。ボブ・マーティンはこれを「クリーンコード」で説明しています。
JᴀʏMᴇᴇ

他の回答、分割、および名前に追加しますが、クラスが成長するにつれて、そのメンバー変数はますますグローバル変数のようになることに留意してください。問題を改善する1つの方法(リファクタリングなどの優れたソリューションは別として;-))可能であれば(多くの状態にアクセスする必要がないという点で)それらの小さなプライベートメソッドをスタンドアロン関数に分離することです。一部の言語ではクラス外の関数を使用できますが、他の言語では静的クラスを使用できます。この方法で、この方法でその機能を単独で理解できます。
パブロH

「この答えが役に立たない」理由を誰かが教えてくれたら、ありがたいです。いつでも学ぶことができます。:-)
パブロH

@PabloHあなたが提供した答えは、OPに良い観察と洞察を与えますが、実際に尋ねられた質問には答えません。コメント欄に移動しました。
maple_shaft

回答:


36

ここで、5つのプライベートメソッドに5つのパブリックメソッドを掛けると、それらのパブリックメソッドによって1回だけ呼び出される可能性のある、25の隠しメソッドを取得できます。

これはEncapsulationとして知られているもので、より高いレベルでControl Abstractionを作成します。これは良いことです。

これは、誰もが彼らが得るとき、あなたのコードを読むことを意味しstartTheEngine()、あなたのコード内のメソッドなど、低レベルの詳細のすべてを無視することができopenIgnitionModule()turnDistributorMotor()sendSparksToSparkPlugs()injectFuelIntoCylinders()activateStarterSolenoid()、とするために実行する必要があり、他の複雑な、小さな、機能のすべてのはるかに大きく、より抽象的な機能を促進しstartTheEngine()ます。

コードで扱っている問題がこれらのコンポーネントのいずれかを直接扱っていない限り、コード管理者は、サンドボックス化されカプセル化された機能を無視して先に進むことができます。

これには、コードをテストしやすくするという利点もあります。。たとえばturnAlternatorMotor(int revolutionRate)、他のシステムから完全に独立したテストケースを作成し、その機能をテストできます。その関数に問題があり、出力が期待したものでない場合は、問題が何であるかがわかります。

コンポーネントに分解されていないコードは、テストがはるかに困難です。突然、あなたのメンテナーは、測定可能なコンポーネントを掘り下げることができるのではなく、抽象化だけを見ているのです。

私のアドバイスは、コードがスケーリングされ、メンテナンスが容易であり、今後数年間使用および更新できるため、あなたがしていることをやり続けることです。


3
だから、一箇所でしか呼び出されないクラス全体でロジックを利用できるようにするのは良い「カプセル化」だと思いますか?
スティーブンジュリス

9
@Steven Jeuris:これらの機能が非公開にされている限り、問題ありません。実際、この回答で述べたように、読みやすいと思います。一連の低レベルのプライベート関数(もちろん適切な名前が付けられている)のみを呼び出す高レベルのパブリック関数を理解する方がはるかに簡単です。もちろん、すべてのコードを高レベル関数自体に入れることもできますが、同じ場所でより多くのコードを調べる必要があるため、コードを理解するのに時間がかかります。
ギャブリン

2
@gablin:クラス全体からプライベート関数にアクセスできます。で、この(明らかに物議を醸す)の記事私はあまりの機能を有するとき、「グローバル可読性」が失われている方法について説明します。実際、関数が行うべきことのほかに共通する原則は、あまり多くのことはすべきではないということです。短いために分割するだけで「ローカルな可読性」が得られますが、これは簡単なコメントと「コード段落」でも実現できます。わかりました、コードは自己文書化されるべきですが、コメントは時代遅れではありません!
スティーブンジュリス

1
@Steven-理解しやすいものは何ですか? myString.replaceAll(pattern, originalData);または、基になる文字列オブジェクトを表すバッキング文字配列を含むコード内のreplaceAllメソッド全体を、文字列の機能を使用する必要があるたびに貼り付けますか?
jmort253

7
@Steven Jeuris:あなたにはポイントがあります。クラスが(パブリックまたはプライベート)あまりにも多くの機能を持っている場合は、おそらく全体のクラスには、あまりにも多くをやろうとしています。このような場合、大きすぎる関数をいくつかの小さな関数に分割するのと同じように、いくつかの小さなクラスに分割する方がよい場合があります。
ガブリン

22

はいといいえ。基本的な原則は次のとおりです。メソッドは1つのことと1つのことだけを行う必要があります

メソッドを小さなメソッドに「分割」するのは正しいことです。その後、再び、これらの方法は、最適にしないように十分一般的であるべき唯一のこと「ビッグ」の方法を提供しますが、他の場所で再利用できます。ただし、InitializePlugins、InitializeInterfaceなどを呼び出すInitializeメソッドのように、メソッドが単純なツリー構造に従う場合もあります。

あなたが本当に大きなメソッド/クラスを持っている場合、それは通常、それが多くのことをしている兆候であり、「ブロブ」を分解するためにリファクタリングを行う必要があります。Perphapsは抽象化の下で別のクラスの複雑さを隠し、依存性注入を使用します


1
誰もが盲目的に「一つのこと」のルールに従うわけではないのを読むのはいいことです!; pいい答えです!
スティーブンジュリス

16
大きなメソッドのみを処理する場合でも、大きなメソッドを小さなメソッドに分割することがよくあります。それを分割する理由は、1つの巨大なコードの塊(自己コメントである場合もそうでない場合もある)を単に持つのではなく、扱いやすく理解しやすくなるためです。
ギャブリン

1
大きな方法を避けられない場合もあります。たとえば、Initializeメソッドがある場合、InitializeSystems、InitializePluginsなどを呼び出すことができます。ただし、リファクタリングは必要ないことを常にチェックし続ける必要があります
-Homde

1
すべてのメソッド(または関数)で重要なことは、明確な概念インターフェイスを持たなければならないということです。これは「契約」です。それを突き止めることができない場合、それは問題となり、ファクタリングが間違っています。(契約を明示的に文書化することは良いことです-または、ツールを使用してEiffelスタイルを強制することはできますが、そもそも契約を結ぶことほど重要ではありません。)
ドナルドフェローズ

3
@gablin:考慮すべきもう1つの利点:物事を小さな機能に分割するとき、「他の1つの機能しか提供しない」とは思わず、「今のところ他の1つの機能しか提供しない」とは考えないでください。小さい関数がより汎用的で再利用可能であるため、将来、他のメソッドを作成するときに、大きなメソッドをリファクタリングすることで時間を大幅に節約できることが何度かありました。
GSto

17

一般的に、少なすぎるよりも多すぎる関数の側でエラーを起こした方が良いと思います。私が実際に見たこのルールの2つの例外は、DRYYAGNIの原則です。ほぼ同一の機能が多数ある場合は、それらを組み合わせてパラメーターを使用し、繰り返しを避けてください。また、「念のため」に作成して使用されていない関数が多すぎる場合もあります。読みやすさと保守性を追加する場合に一度だけ使用される関数を使用しても、まったく問題はありません。


10

jmort253の素晴らしい回答は、「はい、でも」という回答をフォローアップするきっかけになりました...

小さなプライベートメソッドをたくさん持つことは悪いことではありませんが、ここに質問を投稿させているその微妙な気持ちに注意してください:)。プライベートメソッドのみに焦点を当てたテストを作成しているポイントに到達した場合(直接呼び出すか、結果をテストできるように特定の方法で呼び出されるようにシナリオを設定することにより)一歩後退する必要があります。

脱出しようとする、より焦点を絞った独自のパブリックインターフェイスを持つ新しいクラスがあります。その時点で、クラスを抽出して、現在プライベートメソッドに存在している既存のクラス機能の一部をカプセル化することを検討する必要があります。これで、元のクラスが新しいクラスを依存関係として使用できるようになり、そのクラスに対してより焦点を絞ったテストを直接作成できるようになりました。


これは素晴らしい答えであり、コードが確実に進むべき方向だと思います。ベストプラクティスでは、必要な機能を提供する最も制限の厳しいアクセス修飾子を使用します。メソッドがクラスの外部で使用されない場合、privateが最も意味があります。メソッドをパブリックにするか、別のクラスに移動して他の場所で使用できるようにする方が理にかなっている場合は、どうしてもそれを行うことができます。+1
jmort253

8

私が参加したほとんどすべてのオブジェクト指向プロジェクトは、大きすぎるクラスと長すぎるメソッドにひどく苦しんでいました。

コードの次のセクションを説明するコメントが必要だと感じたら、そのセクションを別のメソッドに抽出します。長期的には、これによりコードが短くなります。これらの小さなメソッドは、当初の予想よりも多く再利用されます。それらの束を別のクラスに集める機会を見始めます。すぐに、あなたはより高いレベルであなたの問題について考えています、そして、全体の努力はずっと速く行きます。新しい機能は、これらの小さなメソッドから作成した小さなクラスに基づいて構築できるため、作成が簡単です。


7

5つのパブリックメソッドと25のプライベートメソッドを持つクラスは、私にはそれほど大きくないようです。クラスに明確に定義された責任があることを確認し、メソッドの数についてあまり心配しないでください。

ただし、これらのプライベートメソッドは、「doSomethingPart1」、「doSomethingPart2」の方法ではなく、タスク全体の特定の側面に焦点を当てる必要があります。これらのプライベートメソッドが、任意に分割された長いストーリーの一部であり、それぞれがコンテキスト全体の外では無意味である場合、何も得られません。


+1「これらのプライベートメソッドが任意に分割された長いストーリーの一部であり、それぞれがコンテキスト全体の外では無意味である場合、何も得られません。」
マフディ

4

R. MartinのClean Code(p。176)からの抜粋:

重複の排除、コードの表現力、およびSRPのような基本的な概念でさえ、行き過ぎです。クラスとメソッドを小さくするために、非常に多くの小さなクラスとメソッドを作成する場合があります。したがって、このルール[クラスとメソッドの数を最小限に抑える]は、関数とクラスの数も少なく保つことを示唆しています。高いクラス数とメソッド数は、無意味な教義の結果である場合があります。


3

いくつかのオプション:

  1. それはいいです。プライベートメソッドに名前を付けるだけでなく、パブリックメソッドに名前を付けるだけです。また、パブリックメソッドに適切な名前を付けます。

  2. これらのヘルパーメソッドの一部を、ヘルパークラスまたはヘルパーモジュールに抽象化します。そして、これらのヘルパークラス/モジュールの名前が適切であり、それ自体が確実に抽象化されていることを確認してください。


1

おそらくそれがコードアーキテクチャの貧弱さの症状であると感じるのであれば、「プライベートメソッドが多すぎる」という問題はないと思います。

アーキテクチャがうまく設計されていれば、Xを実行するコードがどこにあるべきかは一般的に明らかです。これにいくつかの例外がある場合でも問題ありませんが、これらを標準としてリファクタリングするために、アーキテクチャをリファクタリングする必要があります。

クラスを開くときに問題が発生する場合、それは大きなチャンクを小さなコードチャンクに分割するプライベートメソッドでいっぱいであり、おそらくこれはアーキテクチャの欠落の症状です。クラスとアーキテクチャの代わりに、このコード領域は1つのクラス内で手続き的な方法で実装されています。

ここでバランスを見つけることは、プロジェクトとその要件によって異なります。これに対する反論は、ジュニアプログラマーが、アーキテクトを50クラス取る50行のコードでソリューションをノックアップできるということです。


0

プライベート関数/メソッドが多すぎるようなものはありますか?

はい。

Pythonでは、「プライベート」(C ++、Java、およびC#で使用される)の概念は実際には存在しません。

「プライベートな」命名規則がありますが、それだけです。

プライベートは、テストが難しいコードにつながる可能性があります。また、コードを単純にクローズすることにより、「オープンクローズドプリンシプル」を破ることができます。

したがって、Pythonを使用したことのある人にとって、プライベート関数には価値がありません。すべてを公開して、それで完了します。


1
どのようにしてオープンクローズド原則を破ることができますか?コードは、パブリックメソッドがより小さなプライベートメソッドに分割されているかどうかに関係なく、拡張のために開かれ、変更のために閉じられます。
-Byxor
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.