web-dev-qa-db-ja.com

最小驚きの原則(POLA)とインターフェース

私がC++を学んでいた四半世紀前、私はインターフェイスは寛容であるべきであり、消費者がソースやドキュメントにアクセスできないので、メソッドが呼び出された順序をできる限り気にしないように教えられましたこの。

しかし、私がジュニアプログラマーやシニアデベロッパーに助言を求めてきたときはいつでも、驚いたことに反応して、これが本当に問題なのか、それとも流行から消えたのかと思いました。

泥と同じくらい明確ですか?

これらのメソッドとのインターフェースを検討してください(データファイルを作成するため):

OpenFile
SetHeaderString
WriteDataLine
SetTrailerString
CloseFile

もちろん、これらを順番に実行することもできますが、ファイル名は気にしていません(a.out)または含まれているヘッダーとトレーラーの文字列は、AddDataLineを呼び出すだけです。

それほど極端ではない例として、ヘッダーとトレーラーの省略があります。

さらに別の方法として、ファイルを開く前にヘッダーとトレーラーの文字列を設定することもできます。

これは、認識されるインターフェース設計の原則ですか、それとも名前が付けられる前のPOLAの方法ですか?

N.B.このインターフェースの特徴にとらわれないでください。これは、この質問のための単なる例です。

17
Robbie Dee

驚きの最小化の原則に固執する1つの方法は、 [〜#〜] isp [〜#〜][〜#〜] srp [のような他の原則を考慮することです。 〜#〜] 、または [〜#〜] dry [〜#〜]

あなたが与えた特定の例では、ファイルを操作するための順序付けには特定の依存関係があるという提案があるようです。ただし、APIはファイルアクセスとデータ形式の両方を制御するため、SRPの違反のような匂いがします。

編集/更新:APIを使用するたびに同じ手順を繰り返す必要があるため、API自体がDRYへの違反をユーザーに要求していることも示唆しています

IOオペレーションがデータオペレーションとは別のものであり、API自体が順序を「所有」している別のAPIを検討してください。

ContentBuilder

_SetHeader( ... )
AddLine( ... )
SetTrailer ( ... )
_

FileWriter

_Open(filename) 
Write(content) throws InvalidContentException
Close()
_

上記の分離により、ContentBuilderは、行/ヘッダー/トレーラー(おそらく、順序を認識しているContentBuilder.Serialize()メソッドも)を格納する以外に、実際に「実行」する必要はありません。他のSOLID=原則に従うことにより、行を追加する前または後にヘッダーまたはトレーラーを設定しても、ContentBuilderの何も実際にファイルに書き込まれないため、_FileWriter.Write_ 。

また、もう少し柔軟になるという利点もあります。たとえば、コンテンツを診断ロガーに書き出すか、ファイルに直接書き込む代わりに、ネットワークを介して渡すと便利な場合があります。

APIを設計するときは、状態、戻り値、例外、コールバックなど、エラーレポートについても考慮する必要があります。 APIのユーザーは、契約の違反、またはファイルI/Oエラーなど、制御できないその他のエラーをプログラムで検出できることを期待しています。

25
Ben Cottrell

これは、POLAだけでなく、バ​​グの発生源としての無効な状態の防止についてもです。

具体的な実装を提供せずに、例にいくつかの制約を提供する方法を見てみましょう。

最初のステップ:ファイルが開かれる前に、何も呼び出されないようにします。

_CreateDataFileInterface
  + OpenFile(filename : string) : DataFileInterface

DataFileInterface
  + SetHeaderString(header : string) : void
  + WriteDataLine(data : string) : void
  + SetTrailerString(trailer : string) : void
  + Close() : void
_

これで、実際のデータを書き込むことができるDataFileInterfaceインスタンスを取得するために_CreateDataFileInterface.OpenFile_を呼び出す必要があることは明らかです。

2番目のステップ:ヘッダーとトレーラーが常に設定されていることを確認します。

_CreateDataFileInterface
  + OpenFile(filename : string, header: string, trailer : string) : DataFileInterface

DataFileInterface
  + WriteDataLine(data : string) : void
  + Close() : void
_

次に、DataFileInterface:ファイル名、ヘッダー、およびトレーラーを取得するために、必要なすべてのパラメーターを事前に提供する必要があります。すべての行が書き込まれるまでトレーラ文字列を利用できない場合は、このパラメータをClose()に移動して(メソッドの名前をWriteTrailerAndClose()に変更することもできます)、少なくともファイルを完了できないようにすることができますトレーラー文字列なし。


コメントに返信するには:

インターフェースの分離が好きです。しかし、私は強制に関するあなたの提案(たとえば、WriteTrailerAndClose())がSRPの違反に近づいていると考えがちです。 (これは私が何度も苦労してきたことですが、あなたの提案は可能な例のようです。)どのように答えますか?

そうだね。必要以上に例に集中したくありませんでしたが、それは良い質問です。この場合、私はそれをFinalize(trailer)と呼び、あまり機能しないと主張します。トレーラーの作成とクローズは、単なる実装の詳細です。しかし、あなたが同意しない場合、またはそれが異なる同様の状況にある場合、考えられる解決策は次のとおりです。

_CreateDataFileInterface
  + OpenFile(filename : string, header : string) : IncompleteDataFileInterface

IncompleteDataFileInterface
  + WriteDataLine(data : string) : void
  + FinalizeWithTrailer(trailer : string) : CompleteDataFileInterface

CompleteDataFileInterface
  + Close()
_

この例では実際には実行しませんが、結果的にこのテクニックを実行する方法を示しています。

ちなみに、私は実際にメソッドをこの順序で呼び出す必要があると想定しました。たとえば、多数の行を連続して書き込む場合などです。これが必要ない場合、私は常にビルダーを好みます Ben Cottrelの提案どおり

12