オブジェクトのすべての作業をコンストラクターで行う理由はありますか?


49

これは私のコードでも同僚のコードでもないということで、これを序文にしましょう。数年前、私たちの会社が小さかったとき、私たちには必要なプロジェクトがいくつかありましたが、その能力はありませんでしたので、それらは外部委託されました。今、私は一般的にアウトソーシングや請負業者に対して何もありませんが、彼らが生産したコードベースは大量のWTFです。とはいえ、(ほとんど)動作するので、私が見た外部委託プロジェクトの上位10%にあると思います。

会社の成長に伴い、社内での開発をさらに進めようとしました。この特定のプロジェクトは私の膝に着地したので、私はそれを調べ、掃除し、テストを追加するなどしてきました。

繰り返し見られるパターンが1つありますが、非常に驚​​くほどひどいので、理由があるのではないかと思って、見ないだけです。パターンは、パブリックメソッドやメンバーを持たないオブジェクトであり、オブジェクトのすべての作業を行うパブリックコンストラクターにすぎません。

たとえば、(コードがJavaである場合、それが重要ですが、これがより一般的な質問であることを願っています):

public class Foo {
  private int bar;
  private String baz;

  public Foo(File f) {
    execute(f);
  }

  private void execute(File f) {
     // FTP the file to some hardcoded location, 
     // or parse the file and commit to the database, or whatever
  }
}

疑問に思っているなら、このタイプのコードはしばしば次のように呼ばれます:

for(File f : someListOfFiles) {
   new Foo(f);
}

さて、私はずっと前に、ループ内のインスタンス化されたオブジェクトは一般に悪い考えであり、コンストラクターは最小限の作業を行うべきだと教えられました。このコードを見ると、コンストラクタを削除しexecute、パブリックな静的メソッドを作成する方が良いようです。

私は請負業者になぜそれがこのように行われたのかを尋ねましたが、私が得た応答は「必要に応じて変更できます」でした。これは本当に役に立ちませんでした。

とにかく、プログラミング言語でこのようなことをする理由はありますか、またはこれは単にデイリーWTFへの別の投稿ですか?


18
public static void main(string[] args)オブジェクトのことを知っていて、聞いたことがある開発者によって書かれたようで、それらを一緒にマッシュしようとしました。
アンディハント

再度呼び出されない場合は、そこで呼び出す必要があります。依存性注入などの多くの最新のフレームワークでは、Beanを作成し、プロパティを設定してからTHENを呼び出す必要があり、これらの重いワーカーを使用することはできません。

4
関連質問:コンストラクターでクラスの主要な作業を行う上で問題はありますか?。ただし、作成されたインスタンスは後で使用されるため、これは少し異なります。
-sleske


私はこれを完全な答えに拡張したくはありませんが、ただ言わせてください。これは間違いなく受け入れられるシナリオがあります。これは非常に遠いです。ここでは、実際にオブジェクトを必要とせずにオブジェクトがインスタンス化されます。それとは対照的に、XMLファイルから何らかの種類のデータ構造を作成している場合、コンストラクターから解析を開始することは完全にひどいわけではありません。実際、コンストラクタのオーバーロードを許可する言語では、メンテナーとしてあなたを殺すようなことは何もありません;)
back2dos

回答:


60

わかりました、リストの下に行きます:

ループ内のインスタンス化されたオブジェクトは一般に悪い考えだとずっと前に教えられました

私が使用した言語ではありません。

Cでは、変数を事前に宣言することをお勧めしますが、それはあなたが言ったこととは異なります。ループの上でオブジェクトを宣言して再利用すると少し速くなるかもしれませんが、この速度の向上が無意味になる言語がたくさんあります(そしておそらく、あなたのために最適化を行うコンパイラがあります:))。

一般に、ループ内にオブジェクトが必要な場合は、作成します。

コンストラクタは最小限の作業を行う必要があります

コンストラクタは、オブジェクトのフィールドをインスタンス化し、オブジェクトを使用する準備を整えるために必要な他の初期化を行う必要があります。これは一般に、コンストラクターが小さいことを意味しますが、これがかなりの量の作業になるシナリオがあります。

プログラミング言語でこのようなことをする理由はありますか、それともDaily WTFへの単なる別の提出ですか?

これは、デイリーWTFへの提出です。確かに、コードでできることはもっと悪いことがあります。問題は、作成者がクラスとは何か、それらをどのように使用するかについて大きな誤解を持っていることです。具体的には、このコードで間違っていることがわかります:

  • クラスの誤用:クラスは基本的に関数のように機能しています。既に述べたように、静的関数に置き換えるか、関数を呼び出しているクラスに実装する必要があります。実行内容と使用場所によって異なります。
  • パフォーマンスのオーバーヘッド:言語によっては、オブジェクトの作成が関数の呼び出しよりも遅くなる場合があります。
  • 一般的な混乱:一般に、このコードの使用方法はプログラマーにとって混乱を招きます。それが使われているのを見ないと、誰も問題のコードをどのように使うつもりなのか誰にもわかりません。

御返答いただき有難うございます。「ループ内のオブジェクトのインスタンス化」に関して、これは多くの静的解析ツールが警告するものであり、大学の教授からも聞いたことがある。確かに、パフォーマンスヒットが大きかった90年代からの持ち越しかもしれません。もちろん、必要な場合はそれを行いますが、反対の視点を聞いて驚いています。視野を広げてくれてありがとう。
ケイン

8
オブジェクトをループでインスタンス化することは、ちょっとしたコード臭です。あなたはそれを見て、「私は本当にこのオブジェクトを複数回インスタンス化する必要があるのか​​?」と尋ねるべきです。同じデータで同じオブジェクトをインスタンス化し、同じことを行うように指示している場合、オブジェクトを1回インスタンス化してから、オブジェクトの状態に必要な増分変更を加えて命令を繰り返すだけで、サイクルを節約します(メモリのスラッシングを回避します) 。ただし、たとえば、DBまたはその他の入力からデータストリームを読み取り、そのデータを保持する一連のオブジェクトを作成する場合は、必ずインスタンス化してください。
キース

3
短いバージョン:オブジェクトのインスタンス化は関数のように使用しないでください。
エリックReppen

27

私にとって、コンストラクタで多くの作業を行うオブジェクトを取得するのは少し驚きです。そのため、そのことをお勧めします(最小限の驚きの原則)。

良い例の試み

この使用法がアンチパターンであるかどうかはわかりませんが、C#では次のような行に沿ったコードを見てきました(多少構成された例):

using (ImpersonationContext administrativeContext = new ImpersonationContext(ADMIN_USER))
{
    // Perform actions here under the administrator's security context
}
// We're back to "normal" privileges here

この場合、ImpersonationContextクラスはコンストラクターとDisposeメソッドですべての作業を行います。これは、C#のを利用してusingかなりよくC#の開発者が理解されている文、したがって、我々はしている外いったんコンストラクタ内で発生したセットアップは背中を丸めていることを保証using文は、例外が発生した場合でも。これはおそらくこれで見た中で最も良い使用法です(つまり、コンストラクターでの "作業")が、 "良い"と考えるかどうかはまだわかりません。この場合、それはかなり自明で、機能的で簡潔です。

よく似たパターンの例は、コマンドpatternです。コンストラクターでロジックを実行することは絶対にありませんが、クラス全体がメソッド呼び出し(メソッドの呼び出しに必要なパラメーターを含む)と同等であるという意味では似ています。このようにして、メソッド呼び出し情報をシリアル化するか、後で使用するために渡すことができます。これは非常に便利です。おそらくあなたの請負業者は似たようなことを試みていましたが、私はそれを非常に疑っています。

あなたの例に対するコメント:

非常に明確なアンチパターンだと思います。それは何も追加せず、予想に反し、コンストラクターで過度に長い処理を実行します(コンストラクターでFTP?実際、WTFは、この方法でWebサービスを呼び出すことが知られていると思いますが)。

引用を見つけるのに苦労していますが、Grady Boochの本「Object Solutions」には、C開発者のチームがC ++に移行するという逸話があります。どうやら彼らは訓練を受けていて、経営者から「オブジェクト指向」のことを絶対にやらなければならないと言われたようです。何が間違っているのかを理解するために、著者はコードメトリックツールを使用し、クラスごとの平均メソッド数が正確に1であり、「Do It」というフレーズのバリエーションであることを発見しました。この特定の問題に遭遇したことは一度もありませんが、どうやったらその理由を本当に理解せずに、人々がオブジェクト指向のコードを作成せざるを得ないことの兆候であるようです。


1
あなたの良い例は、Resource Allocation is Initializationのインスタンスです。これは、C ++で推奨されるパターンです。これにより、例外に対して安全なコードを記述しやすくなります。ただし、それがC#に引き継がれるかどうかはわかりません。(この場合、割り当てられる「リソース」は管理者特権です。)
zwol

@Zack C ++でRAIIを知っていたとしても、これらの用語で考えたことはありません。C#の世界では、これは使い捨てパターンと呼ばれ、(通常は管理されていない)リソースが存在する場合は、その存続期間後に解放することを非常に推奨します。この例との主な違いは、通常、コンストラクタで高価な演算子を使用しないことです。たとえば、コンストラクターの後にSqlConnectionのOpen()メソッドを呼び出す必要があります。
ダニエルB

14

番号。

すべての作業がコンストラクターで行われる場合、それはオブジェクトがそもそも必要とされなかったことを意味します。ほとんどの場合、請負業者は単にモジュール化のためにオブジェクトを使用していました。その場合、静的メソッドを持つインスタンス化されていないクラスは、余分なオブジェクトインスタンスを作成せずに同じメリットを得ることができます。

オブジェクトコンストラクターですべての作業を行うことが許容されるケースは1つだけです。これは、オブジェクトの目的が、作業を実行するのではなく、長期的に一意のIDを明確に表すことである場合です。このような場合、オブジェクトは意味のある作業を実行する以外の理由で保持されます。たとえば、シングルトンインスタンス。


7

私は確かに、コンストラクターで何かを計算するために多くの作業を行う多くのクラスを作成しましたが、オブジェクトは不変であるため、その計算の結果をプロパティを介して利用可能にし、他には何もしません。これは通常の不変性です。

ここでの奇妙なことは、副作用を持つコードをコンストラクターに入れることだと思います。オブジェクトを構築し、プロパティやメソッドにアクセスせずにオブジェクトを破棄するのは奇妙に見えます(しかし、少なくともこの場合、他に何かが起こっていることは明らかです)。

これは、関数がパブリックメソッドに移動され、クラスのインスタンスがインジェクトされ、forループがメソッドを繰り返し呼び出す場合に優れています。


4

オブジェクトのすべての作業をコンストラクターで行う理由はありますか?

番号。

  • コンストラクターには副作用がありません。
    • プライベートフィールドの初期化以外のものは、副作用として見なされる必要があります。
    • 副作用を持つコンストラクタは、単一責任原則(SRP)を破り、オブジェクト指向プログラミング(OOP)の精神に反して実行されます。
  • コンストラクターは軽量であり、失敗することはありません。
    • たとえば、コンストラクター内にtry-catchブロックが表示されると、私は常に身震いします。コンストラクターは、例外をスローしたり、エラーを記録したりしないでください。

これらのガイドラインに合理的に疑問を呈し、「しかし、私はこれらの規則に従わず、私のコードは正常に動作します!」と言うことができます。それに対して、「そうでないまで、それは本当かもしれません。」と答えます。

  • コンストラクター内の例外とエラーは非常に予期しないものです。そうするように言われない限り、将来のプログラマーは、これらのコンストラクター呼び出しを防御的なコードで囲む傾向がなくなります。
  • 本番環境で何かが失敗すると、生成されたスタックトレースの解析が困難になる場合があります。スタックトレースの最上部はコンストラクター呼び出しを指している場合がありますが、コンストラクターで多くのことが発生し、失敗した実際のLOCを指し示していない場合があります。
    • これが当てはまる場合、多くの.NETスタックトレースを解析しました。

1
「そうするように言われない限り、将来のプログラマーは、これらのコンストラクター呼び出しを防御的なコードで囲むことを望みません。」- いい視点ね。
グラハム14

2

これをしている人は、関数をファーストクラスの市民として渡すことを許可しないJavaの制限をハッキングしようとしたようです。関数を渡す必要があるときはいつでも、メソッドのようなapplyまたは同様のメソッドでクラス内にラップする必要があります。

だから彼は単にショートカットを使用し、関数の代替としてクラスを直接使用したと思います。関数を実行するたびに、クラスをインスタンス化します。

少なくとも私たちがここでそれを解明しようとしているため、それは間違いなく良いパターンではありませんが、Javaの関数型プログラミングに似た何かをする最も冗長な方法かもしれないと思います。


3
この特定のプログラマーが「関数型プログラミング」という言葉を聞いたことはないと確信しています。
ケイン

プログラマーがクロージャーの抽象化を作成しようとしていたとは思わない。Javaに相当するものには、抽象メソッド(可能な場合の計画)ポリモーフィズムが必要です。ここではその証拠ではありません。
ケビンA.ナウデ

1

私にとっての経験則は、メンバー変数の初期化を除いて、コンストラクターで何もしないことです。その最初の理由は、SRPの原則に従うことです。通常、長い初期化プロセスは、クラスが行うべき以上のことを行うことを示し、初期化はクラス外のどこかで行う必要があるためです。2番目の理由は、この方法では必要なパラメーターのみが渡されるため、結合コードが少なくなるためです。複雑な初期化を伴うコンストラクターには、通常、別のオブジェクトを構築するためだけに使用されるが、元のクラスでは使用されないパラメーターが必要です。


1

私はここで流れに逆らうつもりです-すべてが何らかのクラスにある必要があり、静的クラスを持つことができない言語では、必要な機能の孤立したビットがある状況に陥る可能性がありますどこかに置い、他のものと合わない。選択できるのは、関連性のないさまざまな機能をカバーするユーティリティクラス、または基本的にこれだけを行うクラスです。

このようなビットが1つしかない場合、静的クラスは特定のクラスです。したがって、すべての作業を行うコンストラクター、またはすべての作業を行う単一の関数のいずれかがあります。コンストラクターですべてを行う利点は、一度だけ発生することです。これにより、再利用やスレッド化を心配することなく、プライベートフィールド、プロパティ、メソッドを使用できます。コンストラクター/メソッドがある場合、パラメーターを単一のメソッドに渡そうとしますが、潜在的なスレッドの問題を引き起こすプライベートフィールドまたはプロパティを使用する場合があります。

ユーティリティクラスを使用しても、ほとんどの人は静的クラスをネストしているとは思わないでしょう(言語によっては不可能な場合もあります)。

基本的に、これは言語で許可されている範囲内で、システムの残りの部分から動作を分離する比較的理解しやすい方法ですが、それでも可能な限り多くの言語を利用できます。

率直に言って私はあなたの請負業者がしたように多く答えたでしょう、それを2つのコールに変えるための小さな調整です。おそらく、実際よりも仮説的なものがありますか?それは重要ではなく、おそらくデフォルトのアクションほど決定されていないのに、なぜ決定を正当化しようとするのですか?


1

Pythonのように、関数とクラスがはるかに同じものである言語に共通のパターンがあります。Pythonでは、基本的にオブジェクトを使用して、副作用が多すぎたり、複数の名前付きオブジェクトが返されます。

この場合、オブジェクト自体は単一の作業バンドルの単なるラッパーであり、それを別の関数に詰め込む正当な理由はないため、すべての作業がコンストラクターで行われるわけではないにしても、バルクです。何かのようなもの

var parser = XMLParser()
var x = parser.parse(string)
string a = x.LookupTag(s)

本当に良くない

 var x = ParsedXML(string)
 string a = x.LookupTag(s)

副作用を直接はっきりさせる代わりに、オブジェクトを呼び出してキューに副作用を適用することができます。これにより、視認性が向上します。オブジェクトに悪い副作用がある場合、すべての作業を行うことができ、その後、オブジェクトに渡され、ひどく影響し、オブジェクトにダメージを与えます。この方法でオブジェクトを識別して呼び出しを分離することは、複数のそのようなオブジェクトを一度に関数に渡すよりも明確です。

x.UpdateView(v)

オブジェクトのアクセサーを介して複数の戻り値を作成できます。すべての作業は完了しましたが、すべてのものをコンテキストに含めたいので、複数の参照を関数に渡したくありません。

var x = new ComputeStatistics(inputFile)
int max = x.MaxOptions;
int mean = x.AverageOption;
int sd = x.StandardDeviation;
int k = x.Kurtosis;
...

したがって、Python / C#の共同プログラマーとしては、これはそれほど奇妙ではないようです。


与えられた例が依然として誤解を招くものであることを明確にする必要があります。アクセサーと戻り値がない例では、これはおそらく痕跡です。おそらく、元の提供されたデバッグが何かを返します。
ジョンジェイオーバーマーク14

1

コンストラクタ/デストラクタがすべての作業を行う1つのケースが思い浮かびます。

ロック。通常、その存続期間中はロックと対話することはありません。C#のアーキテクチャを使用すると、デストラクタを明示的に参照せずにこのようなケースを処理するのに適しています。ただし、すべてのロックがこの方法で実装されるわけではありません。

また、ランタイムライブラリのバグを修正するための、コンストラクターのみのコードに相当するものを作成しました。(実際にコードを修正すると、他の問題が発生します。)パッチを元に戻す必要がないため、デストラクタはありませんでした。


-1

AndyBurshに同意します。知識不足の問題があるようです。

ただし、NHibernateなどのフレームワークに言及することは重要です。NHibernateでは、コンストラクターでコーディングするだけで済みます(マップクラスを作成する場合)。ただし、これはフレームワークの制限ではなく、必要なものです。リフレクション、つまりコンストラクターは、常に存在する唯一のメソッドです(ただし、プライベートな場合もあります)。これは、クラスのメソッドを動的に呼び出す必要がある場合に役立ちます。


-4

「ループ内でインスタンス化されたオブジェクトは一般に悪い考えだとずっと前に教えられました」

はい、StringBuilderが必要な理由を思い出せますか。

Javaには2つの学校があります。

ビルダーは最初のビルダーによって昇格されました。これは、再利用することを教えてくれます(なぜなら、共有=効率とIDのため)。しかし、2000年の初めに、Javaでオブジェクトを作成することは(C ++とは対照的に)非常に安価であり、GCは非常に効率的であるため、再利用は価値がなく、重要なことはプログラマの生産性だけであると読み始めました。生産性を高めるには、管理可能なコードを記述する必要があります。これは、モジュール化(範囲を狭めること)を意味します。そう、

これは悪いです

byte[] array = new byte[kilos or megas]
for_loop:
  use(array)

これはいい。

for_loop:
  byte[] array = new byte[kilos or megas]
  use(array)

forum.java.sunが存在するときにこの質問をしましたが、人々はできるだけ狭い配列を宣言することを好むことに同意しました。

現代のJavaプログラマーは、新しいクラスの宣言やオブジェクトの作成をためらうことはありません。彼はそうするよう奨励されています。Javaは、「すべてがオブジェクトである」という考えと安価な作成で、そうするすべての理由を与えます。

さらに、現代のエンタープライズプログラミングスタイルでは、アクションを実行する必要がある場合、その単純なアクションを実行する特別なクラス(または、さらに優れたフレームワーク)を作成します。ここで役立つ優れたJava IDE。彼らは呼吸として、大量のがらくたを自動的に生成します。

そのため、オブジェクトにはBuilderまたはFactoryパターンがないと思います。コンストラクタによってインスタンスを直接作成するのは適切ではありません。オブジェクトごとに特別なファクトリクラスを使用することをお勧めします。

今、関数型プログラミングが登場すると、オブジェクトの作成がさらに簡単になります。これに気付くこともなく、呼吸としてすべてのステップでオブジェクトを作成します。ですから、あなたのコードは新しい時代にさらに「効率的」になると思います

「コンストラクタで何もしないでください。初期化専用です」

個人的には、ガイドラインには理由がありません。私はそれを別の方法と考えています。コードのファクタリングは、共有できる場合にのみ必要です。


3
説明がうまく構成されておらず、説明が難しい。コンテキストへのリンクを持たない競合可能なアサーションを使用して、あちこちに行きます。
新しいアレクサンドリア

本当に議論の余地のあるステートメントは、「コンストラクターはオブジェクトのフィールドをインスタンス化し、オブジェクトを使用する準備を整えるために必要な他の初期化を行うべきです」です。最も人気のある貢献者に教えてください。
ヴァル
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.