小さな機能と同じ機能で依存する機能を維持する


15

ノードの配列を設定し、それらをグラフのような構造で互いに接続するクラスがあります。次のことが最善ですか:

  1. 1つの機能でノードを初期化および接続する機能を保持します
  2. 2つの異なる関数で初期化および接続機能を使用します(これらの関数はプライベートであることに注意してください)。

方法1:(1つの関数が2つのことを行っている点が悪いが、依存する機能をグループ化したままにしている-最初に初期化されない限り、ノードを接続しないでください。)

init() {
    setupNodes()
}

private func setupNodes() {
    // 1. Create array of nodes
    // 2. Go through array, connecting each node to its neighbors 
    //    according to some predefined constants
}

方法2:(自己文書化という意味では、ただし、connectNodes()をsetupNodes()の前に呼び出さないでください。したがって、クラス内部で作業する人はこの順序を知る必要があります。)

init() {
    setupNodes()
}

private func setupNodes() {
    createNodes()
    connectNodes()
}

private func createNodes() {
    // 1. Create array of nodes
}

private func connectNodes() {
    // 2. Go through array, connecting each node to its neighbors 
    //    according to some predefined constants
}

考えを聞いて興奮しました。



これを解決する1つの方法は、最終オブジェクトの作成にのみ使用できる中間オブジェクトを定義することです。これは常に正しい解決策ではありませんが、インターフェイスユーザーが何らかの方法で中間状態を操作する必要がある場合に役立ちます。
ジョエルコーネット

回答:


23

あなたが扱っている問題は、時間的結合と呼ばれます

このコードがいかに理解しやすいか心配するのはあなたです。

private func setupNodes() {
    createNodes();
    connectNodes();
}

私はそこに何が起こっているか推測できますが、これが他に何が起こっているかをもう少し明確にするかどうか教えてください:

private func setupNodes() {
    self.nodes = connectNodes( createNodes() );
}

これには、インスタンス変数の変更との結合が少ないという追加の利点がありますが、私にとっては読みやすいことが一番です。

これによりconnectNodes()、ノードへの依存関係が明示的になります。


1
リンクをありがとう。私の関数はプライベートであり、Swiftのinit()コンストラクターから呼び出されるため、コードがリンクした例ほど悪いとは思いません(外部クライアントがインスタンスをインスタンス化することは不可能です) nullインスタンス変数)、しかし、私は同様の匂いがあります。
mcfroob 16

1
追加したコードは読みやすいので、そのスタイルでリファクタリングします。
mcfroob 16

10

次の 2つの理由により、機能を分離します

1.プライベート機能は、まさにこの状況に対してプライベートです。

あなたのinit機能は公開され、そしてそれのインタフェース、行動、および戻り値は、あなたが保護し、変更を心配する必要があるものです。そのメソッドから期待する結果は、使用する実装に関係なく同じになります。

残りの機能はプライベートキーワードの背後に隠されているため、好きなように実装できます。そのため、あるビットが最初に呼び出される他のビットに依存している場合でも、適切にモジュール化できます。

2.ノードを相互に接続することは、プライベート機能ではない場合があります

ある時点で、他のノードをアレイに追加したい場合はどうしますか?現在持っているセットアップを破棄し、完全に再初期化しますか?または、既存のアレイにノードを追加してからconnectNodes再度実行しますか?

connectNodesノードの配列がまだ作成されていない場合、おそらく正気な応答を返すことができます(例外をスローしますか?空のセットを返しますか?状況に合ったものを決定する必要があります)。


私は1と同じように考えていたので、ノードが初期化されていない場合は例外または何かを投げることができましたが、特に直感的ではありません。回答ありがとうございます。
mcfroob 16

4

また、(これらの各タスクの複雑さに応じて)これが別のクラスを分割するのに適した継ぎ目であることがわかります。

(Swiftがこの方法で動作するかどうかはわかりませんが、擬似コード:)

class YourClass {
    init(generator: NodesGenerator) {
        self.nodes = connectNodes(generator.make())
    }
    private func connectNodes() {

    }
}

class NodesGenerator {
    public func make() {
        // Return some nodes from storage or make new ones
    }
}

これにより、ノードの作成と変更を別々のクラスに分割します。NodeGeneratorノードの作成/取得YourClassのみを考慮し、ノードの接続のみを指定します。


2

これがプライベートメソッドの正確な目的であることに加えて、Swiftは内部関数を使用する機能を提供します。

内部メソッドは、呼び出しサイトが1つしかない関数に最適ですが、個別のプライベート関数であることを正当化しないように感じます。

たとえば、前提条件をチェックし、いくつかのパラメーターを設定し、作業を行うプライベート再帰関数に委任するパブリック再帰「エントリ」関数を使用することは非常に一般的です。

この場合の表示例を次に示します。

init() {
    self.nodes = setupNodes()

    func setupNodes() {
        var nodes = createNodes()
        connect(Nodes: nodes)
    }

    private func createNodes() -> [Node]{
        // 1. Create array of nodes
    }

    func connect(Nodes: [Node]) {
        // 2. Go through array, connecting each node to its neighbors 
        //    according to some predefined constants
    }
}

共有状態を変更するのではなく、戻り値とパラメーターを使用してデータをやり取りする方法に注意してください。これにより、実装にジャンプする必要なく、データフローが一見してより明確になります。


0

宣言するすべての関数には、ドキュメントを追加し、プログラムの他の部分で使用できるように一般化するという負担が伴います。また、ファイル内の他の関数がコードを読んでいる人にどのように使用しているのかを理解するという負担も伴います。

ただし、プログラムの他の部分で使用されていない場合は、別の関数として公開しません。

あなたの言語がそれをサポートしている場合、ネストされた関数を使用することにより、1つの関数で1つの関数を実行できます。

function setupNodes ()  {
  function createNodes ()  {...} 
  function connectNodes ()  {...}
  createNodes() 
  connectNodes() 
} 

宣言の場所は非常に重要であり、上記の例では、内部関数が外部関数の本体内でのみ使用されることを意図しているというさらなる手がかりを必要としないことは明らかです。

それらをプライベート関数として宣言している場合でも、それらはまだファイル全体から見えると仮定します。したがって、メイン関数の宣言の近くでそれらを宣言し、それらが外部関数によってのみ使用されることを明確にするドキュメントを追加する必要があります。

どちらか一方だけを厳密に行う必要はないと思います。最善の方法は、ケースごとに異なります。

複数の関数に分割すると、3つの関数が存在する理由とそれらが相互にどのように機能するかを理解するためのオーバーヘッドが確実に追加されますが、ロジックが複雑な場合、この追加のオーバーヘッドは、複雑なロジックを分割することによって導入される単純さよりもはるかに少ない可能性がありますより単純な部分に。


興味深いオプション。おっしゃるように、なぜこのように関数が宣言されたのか少し戸惑うかもしれませんが、関数の依存関係は十分に含まれています。
mcfroob 16

この質問のいくつかの不確実性に答えるために、1)はい、Swiftは内部関数をサポートします。2)2つのレベルの「プライベート」があります。private囲む型(構造体/クラス/列挙体)内でのみfileprivateアクセスを許可しますが、ファイル全体でアクセスを許可します
アレクサンダー-モニカー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.