リーク抽象化の意味は?


88

「リーク抽象化」という用語はどういう意味ですか?(例を挙げて説明してください。私は、単なる理論を理解するのに苦労することがよくあります。)



14
JoelSpolskyの元の記事TheLaw of Leaky Abstractionsを読むことをお勧めします。これは、私が知る限り、この用語の由来です。
ダニエルプライデン2010年

1
提案された複製の答えのほとんどは、流暢なインターフェースに関するものです。
デビッドソーンリー2010年

@David:投票数が2番目に多い投稿は、リークのある抽象化の意味と優れた例に答えています。
missingfaktor

4
4年後にこの質問への道をグーグルで検索すると、どの投稿が2番目に投票された投稿であったかを推測するのは困難です。
ジョンレイノルズ

回答:


103

これがミートスペースの例です:

自動車にはドライバー向けの抽象概念があります。最も純粋な形では、ハンドル、アクセル、ブレーキがあります。この抽象化は、ボンネットの下にあるもの(エンジン、カム、タイミングベルト、スパークプラグ、ラジエーターなど)に関する多くの詳細を隠します。

この抽象化の優れた点は、ユーザーを再トレーニングすることなく、実装の一部を改善された部分に置き換えることができることです。ディストリビューターキャップを電子点火に交換し、固定カムを可変カムに交換するとします。これらの変更によりパフォーマンスは向上しますが、ユーザーは引き続きホイールを操作し、ペダルを使用して開始および停止します。

それは実際には非常に注目に値します... 16歳または80歳は、内部でどのように機能するかをあまり知らなくても、この複雑な機械を操作できます。

しかし、リークがあります。トランスミッションは小さな漏れです。オートマチックトランスミッションでは、ギアを切り替えるときに車のパワーが一瞬失われるのを感じることができますが、CVTでは、ずっとスムーズなトルクを感じることができます。

より大きなリークもあります。エンジンの回転が速すぎると、損傷する可能性があります。エンジンブロックが冷たすぎると、車が始動しないか、性能が低下する可能性があります。また、ラジオ、ヘッドライト、エアコンを同時にクランクすると、燃費が低下します。


7
例をありがとう。他の誰も簡単な説明をすることができないようでした。
セバスチャンパッテン2013年

7
これは、特にソフトウェアバージョンのすべてであるユーザーの視点を示しているため、優れた回答です。
チャド2013

1
ミートスペースとはどういう意味ですか?信徒の説明?
brumScouse 2018年

1
@brumScouse「ミートスペース」とは、物理的なオフラインの世界を意味します。これは、オンラインのサイバースペースの世界と対比するために使用されます。答えを編集して、定義へのリンクを含めます。
マークE.ハース2018年

この投稿が「まだリークがある」と指摘しているのが好きです。それらを最小限に抑えることがすべてです。
alaboudi

49

これは単に、抽象化によって実装の詳細の一部が公開されること、または抽象化を使用するときに実装の詳細に注意する必要があることを意味します。この用語は、2002年頃のJoel Spolskyによるものです。詳細については、ウィキペディアの記事参照してください。

典型的な例は、リモートファイルをローカルとして扱うことができるネットワークライブラリです。この抽象化を使用する開発者は、ネットワークの問題により、ローカルファイルとは異なる方法でこれが失敗する可能性があることに注意する必要があります。次に、ネットワークライブラリが提供する抽象化以外のエラーを具体的に処理するコードを開発する必要があります。


7
@mehaaseあなたの抽象化が意図的に漏れているのか、それとも怠慢であるのかがどのように重要かわかりません。参照された記事の例と詳細情報を使用して回答を拡張し、それが自立できるようにしました。さらに、「漏れのある抽象化」は必ずしも蔑称である必要はないと思います。私にとっては、開発者として、抽象化を扱うときにもっと注意する必要がある状況を説明しているにすぎません。デザインは、「漏れ」とは関係なく、良い場合も悪い場合もあります。
tvanfosson 2011

12

ウィキペディアにはこれについてかなり良い定義があります

リークのある抽象化とは、複雑さを軽減(または非表示)することを目的とした、実装された抽象化を指します。

言い換えれば、ソフトウェアの場合、プログラムの制限や副作用を介して機能の実装の詳細を観察できるときです。

簡単な例は、C#/ VB.Netクロージャとそれらがref / outパラメータをキャプチャできないことです。それらをキャプチャできない理由は、リフティングプロセスがどのように発生するかについての実装の詳細によるものです。これを行うためのより良い方法があると言っているわけではありません。


12

.NET開発者によく知られている例を次に示します。ASP.NETのPageクラスは、HTTP操作の詳細、特にフォームデータの管理を非表示にしようとするため、開発者は投稿された値を処理する必要がありません(フォーム値をサーバーに自動的にマップするため)コントロール)。

しかし、最も基本的な使用シナリオを超えてさまよった場合、Page抽象化がリークし始め、クラスの実装の詳細を理解しない限り、ページを操作するのが難しくなります。

一般的な例の1つは、ページにコントロールを動的に追加することです。動的に追加されたコントロールの値は、適切なタイミングで追加しない限り、マップされません。基になるエンジンが受信フォームの値を適切なコントロールにマップする前です。あなたがそれを学ばなければならないとき、抽象化は漏れました


Webformsのバケツには底がありました。さらに悪いことに、薄く覆い隠された抽象化は、グローブボックスで作業しているようにHttpで作業することになりました。
brumScouse 2018年

8

まあ、ある意味では、それは純粋に理論的なものですが、重要ではありません。

抽象化を使用して、物事を理解しやすくします。個々のアイテムである順序付けられた文字のセットを扱っているという事実を隠すために、ある言語の文字列クラスを操作する場合があります。数字を扱っているという事実を隠すために、順序付けられた文字のセットを扱います。私は1と0を扱っているという事実を隠すために数字を扱っています。

リークのある抽象化とは、隠そうとしている詳細を隠さない抽象化です。Javaまたは.NETで5文字の文字列でstring.Lengthを呼び出すと、実装の詳細により、これらの言語が文字と呼ぶものが実際には1または1またはいずれかを表すことができるUTF-16データポイントであるため、5から10までの任意の回答を得ることができます。文字の.5。抽象化がリークされました。ただし、リークしないということは、長さを見つけるには、(実際の長さを格納するために)より多くのストレージスペースが必要になるか、(実際の長さを計算するために)O(1)からO(n)に変更する必要があることを意味します。私が本当の答えを気にするなら(あなたは本当にそうしないことが多いです)、あなたは実際に何が起こっているのかについての知識に取り組む必要があります。

より議論の余地のあるケースは、メソッドまたはプロパティが内部の仕組みに入ることができる場合、それらが抽象化リークであるか、またはより低いレベルの抽象化に移行するための明確に定義された方法であるかなど、人々が同意しない問題になることがあります。


2
そして、あなたは1と0を使って、あなたが電子工学と物理学を
扱っ

6

RPCを使用して例を挙げていきます。

RPCの理想的な世界では、リモートプロシージャコールはローカルプロシージャコールのように見える必要があります(または、話は続きます)。それは、それらが呼び出すときことをプログラマに完全に透明でなければならないSomeObject.someFunction()場合、彼らは見当がつかないSomeObject(またはちょうどsomeFunctionそのことについては)ローカルに保存され、実行またはリモートに格納され、実行されます。これによりプログラミングが簡単になるという理論があります。

ローカル関数呼び出しを行うこと(世界で最も遅いインタープリター言語を使用している場合でも)と次のことには大きな違いがあるため、現実は異なります。

  • プロキシオブジェクトを介した呼び出し
  • パラメータのシリアル化
  • ネットワーク接続を確立する(まだ確立されていない場合)
  • データをリモートプロキシに送信する
  • リモートプロキシにデータを復元させ、あなたに代わってリモート関数を呼び出します
  • 戻り値のシリアル化
  • 戻り値をローカルプロキシに送信する
  • シリアル化されたデータの再組み立て
  • リモート関数からの応答を返す

時間だけでも、それは約3桁(またはそれ以上!)の大きさの違いです。これらの3桁以上の大きさは、パフォーマンスに大きな違いをもたらし、RPCを実際の関数呼び出しとして誤って初めて扱ったときに、プロシージャコールの抽象化がリークすることは明らかです。さらに、コードに重大な問題がない限り、実際の関数呼び出しでは、実装のバグ以外の障害点はほとんどありません。RPC呼び出しには、通常の市内呼び出しに期待する以上の障害ケースとして発生する可能性のある次のすべての問題があります。

  • ローカルプロキシをインスタンス化できない可能性があります
  • リモートプロキシをインスタンス化できない可能性があります
  • プロキシが接続できない場合があります
  • あなたが送るパラメータはそれを無傷にするか全くしないかもしれません
  • リモートが送信する戻り値は、そのまままたはまったく作成されない場合があります

これで、「ローカル関数呼び出しと同じように」RPC呼び出しに、ローカル関数呼び出しを行うときに対処する必要のない追加の障害状態が大量に発生します。抽象化はさらに困難になりました。

結局、RPCは、成功したときと失敗したときの両方で、すべてのレベルでふるいのようにリークするため、悪い抽象化です。


<pimp>これに対するErlangのアプローチは、関数呼び出しとプロセスへのメッセージの送信の違いを隠そうとせず、2つが非常に異なる構文を使用するという点で優れています。また、リモートプロセスメッセージの送信は、同じ一般的な構文を使用しているにもかかわらず、ローカルプロセスの送信とは明らかに異なります。</ pimp>
ちょうど私の正しい意見2010年

2
まあ、これは実際に良い例(読解、人々)を与える唯一の応答なので、それは私の+1を取得します。
マークE.ハース2011年

3

DjangoのORMの多対多の例

サンプルAPIの使用法で、Publicationオブジェクトを多対多属性に追加する前に、ベースのArticleオブジェクトa1を.save()する必要があることに注意してください。また、多対多属性を更新すると、基になるデータベースにすぐに保存されますが、単一属性の更新は、.save()が呼び出されるまでデータベースに反映されないことに注意してください。

抽象化は、単一値属性と複数値属性が単なる属性であるオブジェクトグラフを使用しているということです。しかし、リレーショナルデータベースに裏打ちされたデータストアとしての実装はリークします... RDBSの整合性システムは、オブジェクトインターフェイスの薄いベニヤを通して表示されます。


1

抽象化とは何ですか?

まず、「抽象化」とは何かを理解するのが最善ですか?

抽象化は、世界を単純化する方法です。それはあなたが実際にボンネットの下/カーテンの後ろで何が起こっているのかを心配する必要がないことを意味します。それは何かがばか証拠であることを意味します。さて、それはどういう意味ですか?これは例によって最もよく示されています。

抽象化の例:737/747型機の飛行の複雑さは「抽象化」されています

ボーイングの旅客機を例にとってみましょう。これらの飛行機は非常に複雑な機械です。あなたはジェットエンジン、酸素システム、電気システム、着陸装置システムなどを持っていますが、パイロットはジェットエンジンの複雑さを心配する必要はありません....すべてが「抽象化」されています。つまり、最後にその日、パイロットは飛行機を制御するためのホイールと操縦桿についてのみ心配します。左に移動して左に移動し、右に移動して右に移動し、引き上げて高度を取得し、押し下げて下降します。それは十分に単純です......実際に私は嘘をつきました:ハンドルを制御することはもう少し複雑です。理想的な世界では、それが彼がすべき唯一のことです心配する。しかし、これは実際には当てはまりません。飛行機がどのように動作するか、または実装の詳細をまったく理解せずにサルのように飛行機を飛ばすと、墜落して搭乗している全員を殺す可能性があります。

漏れのある抽象化

実際には、パイロットは多くの重要なことを心配する必要があります-すべてが抽象化されているわけではありません:パイロットは風速、推力、迎え角、燃料、高度、天候の問題、降下の角度、パイロットは正しい方向に進んでおり、飛行機は現在の方向に進んでいます。コンピューターはこれらのタスクでパイロットを支援することができますが、すべてが自動化/簡素化されているわけではありません。

たとえば、パイロットがコラムを強く引き上げすぎると、飛行機は従いますが、パイロットは飛行機を失速させる危険があります。飛行機を失速させた場合、地面に衝突する前に制御を取り戻すのは非常に困難です。 。

言い換えれば、パイロットが他に何も知らずにハンドルを制御するだけでは十分ではありません......... nooooo .......彼女は飛行機の根本的なリスクと制限について知っている必要があります彼が飛ぶ前に.......彼女は飛行機がどのように機能するか、そして飛行機がどのように飛ぶかを知らなければなりません。彼は実装の詳細を知っている必要があります.....彼女は強く引き上げすぎると失速につながること、またはあまりにも急に着陸すると飛行機が破壊されることを知っている必要があります。

それらのものは抽象化されていません。多くのものが抽象化されていますが、すべてではありません。パイロットは、ステアリングコラムと、おそらく1つか2つの他のことだけを心配する必要があります。抽象化は「リーク」です。

......それはあなたのコードでも同じことです。基礎となる実装の詳細がわからない場合は、多くの場合、隅に追いやられます。

コーディングの例を次に示します。

ORMは、データベースクエリを処理する際の多くの面倒を抽象化しますが、次のようなことをしたことがある場合は次のようになります。

User.all.each do |user|
   puts user.name # let's print each user's name
end

そうすれば、数百万人以上のユーザーがいる場合、それがアプリを強制終了するための優れた方法であることがわかります。すべてが抽象化されるわけではありません。User.all2500万人のユーザーとの通話はメモリ使用量を急増させ、問題を引き起こすことを知っておく必要があります。あなたはいくつかの根本的な詳細を知る必要があります。抽象化は漏れています。


0

スケールと実行によって導かれるある時点で、抽象化フレームワークがそのように動作する理由を理解するために、抽象化フレームワークの実装の詳細に精通する必要があるという事実。

たとえば、次のSQLクエリについて考えてみます。

SELECT id, first_name, last_name, age, subject FROM student_details;

そしてその代替案:

SELECT * FROM student_details;

現在、これらは論理的に同等のソリューションのように見えますが、個々の列名の指定により、最初のソリューションのパフォーマンスが向上しています。

これは些細な例ですが、最終的にはJoelSpolskyの引用に戻ります。

すべての重要な抽象化は、ある程度、漏れがあります。

ある時点で、操作で特定の規模に達すると、DB(SQL)の動作方法を最適化する必要があります。そのためには、リレーショナルデータベースがどのように機能するかを知る必要があります。最初は抽象化されていましたが、リークがあります。あなたはある時点でそれを学ぶ必要があります。


-1

ライブラリに次のコードがあると仮定します。

Object[] fetchDeviceColorAndModel(String serialNumberOfDevice)
{
    //fetch Device Color and Device Model from DB.
    //create new Object[] and set 0th field with color and 1st field with model value. 
}

コンシューマーがAPIを呼び出すと、Object []を取得します。消費者は、オブジェクト配列の最初のフィールドに色の値があり、2番目のフィールドがモデル値であることを理解する必要があります。ここで、抽象化がライブラリからコンシューマーコードにリークされました。

解決策の1つは、デバイスのモデルと色をカプセル化したオブジェクトを返すことです。コンシューマーはそのオブジェクトを呼び出して、モデルと色の値を取得できます。

DeviceColorAndModel fetchDeviceColorAndModel(String serialNumberOfTheDevice)
{
    //fetch Device Color and Device Model from DB.
    return new DeviceColorAndModel(color, model);
}

-3

リークのある抽象化とは、状態をカプセル化することです。リークのある抽象化の非常に単純な例:

$currentTime = new DateTime();

$bankAccount1->setLastRefresh($currentTime);
$bankAccount2->setLastRefresh($currentTime);
$currentTime->setTimestamp($aTimestamp);

class BankAccount {
    // ...

    public function setLastRefresh(DateTimeImmutable $lastRefresh)
    {
        $this->lastRefresh = $lastRefresh;
    } }

そして正しい方法(漏れのない抽象化ではない):

class BankAccount
{
    // ...

    public function setLastRefresh(DateTime $lastRefresh)
    {
        $this->lastRefresh = clone $lastRefresh;
    }
}

詳細はこちら

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