init()メソッドはコード臭いですか?


20

init()型のメソッドを宣言する目的はありますか?

私はコンストラクタよりも優先init()すべきかどうかまたは宣言を避ける方法をinit()尋ねていません。

メソッドを宣言する背後に何らかの理由があるのinit()(それがどれほど一般的かを見て)、それがコードのにおいであり、避けるべきかどうかを尋ねています。


init()イディオムは非常に一般的ですが、私は、任意の真のメリットを見ていません。

メソッドによる初期化を促進する型について話している:

class Demo {
    public void init() {
        //...
    }
}

これはいつプロダクションコードで使用されますか?


コンストラクターがオブジェクトを完全に初期化せず、部分的に作成されたオブジェクトを生成することを示唆しているため、コードの匂いがするかもしれません。状態が設定されていない場合、オブジェクトは存在しないはずです。

これは、エンタープライズアプリケーションの意味で、生産をスピードアップするために使用されるある種の手法の一部である可能性があると信じさせてくれます。それは私がそのようなイディオムを持つことを考えることができる唯一の論理的な理由です。


1
「...それがどれほど一般的であるかを見る...」:それは一般的ですか?例を挙げていただけますか?おそらく、初期化と構築を分離する必要があるフレームワークを扱っているのでしょう。
から来る

メソッドは、基本クラスまたは派生クラス、あるいはその両方で見つかりましたか?(または:継承階層に属するクラスでメソッドが見つかりましたか?ベースクラスはinit()派生クラスでを呼び出しますか、またはその逆ですか?)その場合、ベースクラスに「ポストコンストラクターを実行させる例です。 」は、最も派生したクラスの構築が完了した後にのみ実行できます。これは、マルチフェーズ初期化の例です。
rwong

インスタンス化の時点で初期化したくない場合は、2つを分離するのが理にかなっています。
JᴀʏMᴇᴇ

興味があるかもしれません。is-a-start-run-or-execute-method-a-good-practice
Laiv

回答:


39

はい、それはコード臭です。コードのにおいは、必ずしも常に削除する必要があるものではありません。それはあなたが再確認するものです。

ここには、根本的に異なる2つの状態のオブジェクトがあります:pre-initとpost-init。これらの状態には、異なる責任、呼び出せるメソッド、および異なる動作があります。事実上、2つの異なるクラスです。

それらを物理的に2つの別々のクラスにすると、潜在的なバグのクラス全体を静的に削除しますが、その代償として、モデルを「現実世界のモデル」とかなり厳密に一致させません。通常、最初の名前ConfigまたはSetupそのような名前を付けます。

そのため、次回は、construct-initイディオムを2クラスモデルにリファクタリングして、それがどのようになるかを確認してください。


6
2クラスモデルを試すことをお勧めします。コード臭に対処する具体的な手順を提案することは有用です。
イヴァン

1
これがこれまでのベストアンサーです。しかし、「これらの状態には異なる責任、呼び出せるメソッド、および異なる動作があります」-これらの責任を分離しないとSRPに違反します。ソフトウェアの目的があらゆる面で現実世界のシナリオを再現することである場合、これは理にかなっています。しかし、実稼働環境では、開発者は主に容易な管理性を促進するコードを作成し、必要に応じてソフトウェアベースの環境に適合するようにモデルを修正します(次のコメントに続く)
Vince Emigh

1
現実の世界は異なる環境です。それを複製しようとするプログラムは、ほとんどのプロジェクトでそれを考慮しない/すべきではないように、ドメイン固有のようです。ドメイン固有のプロジェクトに関しては、眉をひそめられるものの多くが受け入れられるので、これを可能な限り一般的なものにしようとしています(thisコンストラクターでの呼び出しがコード臭であり、エラーが発生しやすいコードになる可能性があるなど、プロジェクトがどのドメインに属するかに関係なく、回避することをお勧めします)。
ビンスエミー

14

場合によります。

init方法は、コンストラクタから分離オブジェクトの初期化が必要ない場合にコードのにおいです。これらの手順を分離することが理にかなっている場合があります。

簡単なGoogle検索でこの例を見つけました。オブジェクトの割り当て中に実行されるコード(コンストラクター)が初期化自体からより適切に分離される可能性のあるケースを簡単に想像できます。多分あなたはレベル化されたシステムを持っており、割り当て/構築はレベルXで行われますが、初期化はレベルYでのみです。Yだけが必要なパラメータを提供できるからです。「init」はコストがかかり、割り当てられたオブジェクトのサブセットに対してのみ実行する必要があり、そのサブセットの決定はレベルYでのみ実行できます。または、派生オブジェクトの(仮想)「init」メソッドをオーバーライドする場合コンストラクターでは実行できないクラス。レベルXは、継承ツリーから割り当てられたオブジェクトを提供するかもしれませんが、レベルYは、具体的な派生を認識せず、共通のインターフェース(ここで、init 多分定義されます)。

もちろん、私の経験では、これらのケースは、コンストラクターですべての初期化を直接行うことができる標準ケースのごく一部であり、別のinitメソッドを見るときはいつでも、その必要性を疑問視するのは良い考えです。


2
そのリンクの答えは、コンストラクターの作業量を減らすのに役立つと言っていますが、部分的に作成されたオブジェクトを奨励します。より小さいコンストラクターは分解によって達成できるため、答えは新しい問題(すべての必要な初期化メソッドを呼び出すのを忘れる可能性があり、オブジェクトにエラーが発生しやすい)が発生し、コードにおいのカテゴリーに分類されるようです
Vince Emigh

@VinceEmigh:わかりました。これは、SEプラットフォームで最初に見つけた例で、おそらく最良のものではありませんが、別の方法の正当なユースケースがありますinit。ただし、このような方法を見たときはいつでも、その必要性について気軽に質問してください。
Doc Brown

私はそれが必要になる状況はないと感じているので、私はそれのすべてのユースケースを質問/挑戦しています。私にとっては、オブジェクト作成のタイミングが悪いため、適切な設計によって回避できるエラーの候補であるため、回避する必要があります。init()メソッドの適切な使用があった場合、その目的について学ぶことから利益を得られると確信しています。自分の無知を言い訳、私はちょうどそれを避けるべきである何かを考えてから私を防止いかに難しいか、私はそれのための使用を見つける抱えている時の、で驚いています
ヴィンスEmigh

1
@VinceEmigh:そのような状況を考えることができないとき、あなたはあなたの想像力に取り組む必要があります;-)。または、もう一度答えを読んでください。「割り当て」だけにしないでください。または、さまざまなベンダーのより多くのフレームワークを使用します。
ドックブラウン

1
@DocBrown実証済みのプラクティスではなく想像力を使用すると、二重括弧の初期化などのファンキーなコードが発生します。賢いですが、非効率的であり、避けるべきです。ベンダーがそれを使用していることは知っていますが、それは使用が正当化されるという意味ではありません。もしそうだと感じたら、理由を教えてください。それが私が理解しようとしていることです。いくつかの有益な目的を持っていることについては行き詰まっているように見えますが、良いデザインを奨励する例を与えるのに苦労しているように聞こえます。どのような状況で使用できるかは知っています、使用すべきですか?
ヴィンスEmigh

5

私の経験は2つのグループに分けられます。

  1. init()が実際に必要なコード。これは、スーパークラスまたはフレームワークが、クラスのコンストラクターが構築中にすべての依存関係を取得することを妨げる場合に発生する可能性があります。
  2. init()が使用されているが、回避できたコード。

私の個人的な経験では、(1)のいくつかのインスタンスだけを見ましたが、(2)の多くのインスタンスを見ました。その結果、私は通常init()がコード臭であると仮定しますが、これは常にそうではありません。時々あなたはそれを回避することができないだけです。

多くの場合、Builderパターンを使用すると、init()の必要性/欲求を取り除くことができます。


1
スーパークラスまたはフレームワークが、型がコンストラクターを介して必要な依存関係を取得することを許可しない場合、init()メソッドを追加するとどのように解決されますか?init()この方法は、いずれかの依存関係を受け入れるようにパラメータを必要とするか、依存関係をインスタンス化する必要があるだろう内のinit()あなたも、コンストラクタで行うことができます方法を、。例を挙げていただけますか?
ビンスエミー

1
@VinceEmigh:init()を使用して、外部ソースから設定ファイルをロードしたり、データベース接続を開いたり、そのような何かをすることができます。DoFn.initialize()メソッド(Apache Crunchフレームワークから)は、この方法で使用されます。また、シリアル化できない内部フィールドをロードするために使用することもできます(DoFnsはシリアル化可能でなければなりません)。ここでの2つの問題は、(1)初期化メソッドが呼び出されることを保証する必要があること、および(2)オブジェクトがそれらのリソースを取得する場所(または構築する方法)を知る必要があることです。
イヴァン

1

Initメソッドが役立つ典型的なシナリオは、変更する構成ファイルがあり、アプリケーションを再起動せずに変更を反映させる場合です。もちろん、これはInitメソッドをコンストラクターとは別に呼び出さなければならないという意味ではありません。コンストラクターからInitメソッドを呼び出してから、構成パラメーターが変更されたときに/後で呼び出します。

要約すると、世の中のほとんどのジレンマについては、これがコードのにおいであるかどうかは、状況と状況に依存します。


構成の更新場合は、これは設定に基づいて、それの状態を変更/リセットするためにオブジェクトを必要とし、あなたはそれのようにオブジェクトの行為を持っている方が良いだろうとは思わない観測者に向けてConfig
ビンスエミー

@Vince Emigh必要ありません。構成が変更される正確な瞬間を知っていれば、オブザーバーは機能します。ただし、構成データがアプリケーションの外部で変更可能なファイルに保持されている場合、実際に洗練されたアプローチはありません。たとえば、いくつかのファイルを解析し、データをいくつかの内部モデルに変換するプログラムがあり、別の構成ファイルに欠落データのデフォルト値が含まれている場合、デフォルト値を変更すると、次に実行するときにそれらを再度読み取ります解析。この場合、アプリケーションにInitメソッドがあると非常に便利です。
ウラジミールストキッチ16年

設定ファイルを外部から実行時に変更されている場合は、なしでこれらの変数をリロードする方法はありませんいくつかのそれは(INITを呼び出す必要があることを、あなたのアプリケーションを知らせる通知の種類update/ reload実際にそれらの変更を登録するには、おそらくこのような動作のために、より説明的になりますが) 。その場合、その通知により、アプリケーション内で構成の値が内部で変更されます。これは、構成を監視可能にすることで監視でき、構成の値の1つを変更するように構成が指示されたときにオブザーバーに通知できると信じています。または、私はあなたの例を誤解していますか?
ビンスエミー

アプリケーションは、いくつかの外部ファイル(一部のGISまたは他のシステムからエクスポートされた)を取得し、それらのファイルを解析して、アプリケーションが使用するシステムが使用する内部モデルに変換します。そのプロセス中に、いくつかのデータギャップが見つかり、それらのデータギャップは、値をデフォルトにすることで埋めることができます。これらのデフォルト値は、変換プロセスの開始時に読み取ることができる構成ファイルに保存できます。このプロセスは、変換する新しいファイルがあるたびに呼び出されます。ここで、Initメソッドが役立ちます。
ウラジミールストキッチ16年

1

それらの使用方法に依存します。

ヒープ上のオブジェクトの再割り当てを続けたくない場合(ビデオゲームを作成してパフォーマンスを高く保つ必要がある場合、ガベージコレクターがパフォーマンスを低下させる場合など)、Java / C#などのガベージコレクション言語でそのパターンを使用します。コンストラクタを使用して、必要な他のヒープ割り当てinitを作成し、再利用するたびに基本的な有用な状態を作成します。これは、オブジェクトプールの概念に関連しています。

また、初期化命令の共通サブセットを共有する複数のコンストラクターがある場合にも役立ちますが、その場合initはプライベートになります。そうすれば、各コンストラクターを可能な限り最小化できるため、各コンストラクターには固有の命令とinit残りを実行するための単一の呼び出しのみが含まれます。

ただし一般的には、コードのにおいです。


reset()メソッドはあなたの最初のステートメントをより説明的にしないでしょうか?2つ目(多くのコンストラクター)については、多くのコンストラクターを持つことはコードのにおいです。オブジェクトには複数の目的/責任があり、SRP違反を示唆しています。オブジェクトには1つの責任があり、コンストラクターはその1つの責任に必要な依存関係を定義する必要があります。オプションの値のために複数のコンストラクターがある場合、それらは望遠鏡にする必要があります(これはコードの匂いでもあり、代わりにビルダーを使用する必要があります)。
ビンスエミー

@VinceEmighはinitまたはresetを使用できますが、結局は単なる名前です。Initは、初めて使用するときに設定されなかったオブジェクトをリセットすることはほとんど意味がないため、使用する傾向があるコンテキストでより理にかなっています。コンストラクターの問題に関しては、多くのコンストラクターを持たないようにしていますが、役立つ場合もあります。任意の言語のstringコンストラクターリスト、オプションのトンを見てください。私にとって通常は最大3つのコンストラクターですが、初期化としての命令の一般的なサブセットは、コードを共有しているが何らかの形で異なっている場合に意味があります。
コーディ

名前は非常に重要です。クライアントに契約を強制的に読み取らせずに実行される動作を記述します。不適切な命名は、誤った仮定につながる可能性があります。の複数のコンストラクタについてはString、文字列の作成を分離することで解決できます。最終的に、a StringはでありString、コンストラクタは、必要に応じて実行するために必要なもののみを受け入れます。これらのコンストラクターのほとんどは変換の目的で公開されていますが、これはコンストラクターの誤用です。コンストラクターはロジックを実行しないでください。そうしないと、初期化に失敗する危険があり、役に立たないオブジェクトが残ります。
ビンスエミー

JDKには恐ろしいデザインがたくさんあります。頭の中で10個ほどリストアップできます。ソフトウェア設計は、多くの言語のコアアスペクトが一般に公開されてから進化してきました。現代に向けて再設計された場合、コードが破損する可能性があるため、それらは残ります。
ビンスエミー

1

init()メソッドは、他のオブジェクトが同時に使用する外部リソース(ネットワーク接続など)を必要とするオブジェクトがある場合に、かなり意味があります。オブジェクトの存続期間中、リソースを独占する必要はないかもしれません。このような状況では、リソースの割り当てが失敗する可能性が高い場合、コンストラクターでリソースを割り当てたくない場合があります。

特に組み込みプログラミングでは、確定的なメモリフットプリントが必要なので、コンストラクタを早期に(おそらく静的に)呼び出し、特定の条件が満たされた後にのみ初期化するのが一般的です(良い?)

そのような場合を除いて、私はすべてがコンストラクターに入るべきだと思います。


タイプに接続が必要な場合は、DIを使用する必要があります。接続の作成に問題がある場合は、それを必要とするオブジェクトを作成しないでください。クラス内で接続の作成を押し出すと、オブジェクトが作成され、そのオブジェクトはその依存関係(接続)をインスタンス化します。依存関係のインスタンス化が失敗すると、使用できないオブジェクトになり、リソースが無駄になります。
ビンスエミー

必ずしも。一時的にすべての目的に使用できないオブジェクトになります。そのような場合、オブジェクトは、リソースが使用可能になるまでキューまたはプロキシとして機能するだけです。init方法を完全に非難するのは制限が強すぎる、私見。
トフロ

0

一般に、機能的なインスタンスに必要なすべての引数を受け取るコンストラクタを好みます。これにより、そのオブジェクトのすべての依存関係が明確になります。

一方、パラメーターなしのパブリックコンストラクターと、依存関係と構成値を注入するためのインターフェイスを必要とする単純な構成フレームワークを使用します。それが完了すると、構成フレームワークはinitオブジェクトのメソッドを呼び出します。これで、私があなたのために持っているすべてのものを受け取ったので、作業の準備をするために最後のステップを実行します。ただし、注意してください:initメソッドを自動的に呼び出すのは構成フレームワークです。そのため、忘れずに呼び出すことができます。


0

init()メソッドがオブジェクトの状態ライフサイクルに意味的に埋め込まれている場合、コードの匂いはありません。

オブジェクトを一貫した状態にするためにinit()を呼び出す必要がある場合、それはコード臭です。

このような構造が存在する技術的な理由はいくつかあります。

  1. フレームワークフック
  2. オブジェクトを初期状態にリセットする(冗長性を回避する)
  3. テスト中にオーバーライドする可能性

1
しかし、なぜソフトウェアエンジニアがライフサイクルに組み込むのでしょうか?正当であり(部分的に構築されたオブジェクトを奨励することを考慮して)、より効率的な代替手段で撃shotすることができない目的はありますか?ライフサイクルに埋め込むとコードの匂いがし、オブジェクト作成のより良いタイミングで置き換える必要があると感じます(その時点で使用する予定がないのにオブジェクトにメモリを割り当てる理由オブジェクトを作成する前に実際にオブジェクトが必要になるまで待つことができるときに部分的に構築されたオブジェクト?)
ヴィンスエミー

ポイントは、initメソッドを呼び出す前にオブジェクトが使用可能でなければならないということです。たぶんそれが呼ばれた後とは別の方法で。状態パターンを参照してください。部分的なオブジェクトの構築という意味では、コードのにおいです。
-oopexpert

-4

initという名前は不透明な場合があります。車とエンジンを取りましょう。車を起動するには(電源を入れるだけで、ラジオを聞く)、すべてのシステムが準備ができていることを確認します。

したがって、エンジン、ドア、ホイールなどを構築します。画面にはengine = offと表示されます。

これらはすべて高価なので、エンジンなどの監視を開始する必要はありません。次に、キーを回して点火すると、engine-> startを呼び出します。すべての高価なプロセスの実行を開始します。

エンジンがオンになっています。そして点火のプロセスが始まります。

エンジンが利用可能でなければ、車はパワーアップしません。

エンジンを複雑な計算に置き換えることができます。Excelセルのよう。すべてのセルが常にすべてのイベントハンドラーでアクティブである必要はありません。セルに注目すると、セルを開始できます。そのようにしてパフォーマンスを向上させます。

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