Ý nghĩa của Leaky Abstraction?


88

Thuật ngữ "Leaky Abstraction" nghĩa là gì? (Vui lòng giải thích bằng các ví dụ. Tôi thường gặp khó khăn khi tìm hiểu lý thuyết đơn thuần.)



14
Bạn có thể muốn đọc bài báo gốc của Joel Spolsky Luật trừu tượng rò rỉ mà theo tôi biết là nguồn gốc của thuật ngữ này.
Daniel Pryden 7/10/10

1
Hầu hết các câu trả lời của bản dupe được đề xuất là về giao diện thông thạo.
David Thornley

@David: Bài đăng được bình chọn cao thứ hai trả lời câu trả lời trừu tượng rò rỉ nghĩa là gì và với một ví dụ tuyệt vời.
missfaktor

4
Khi bạn google theo cách của bạn đến câu hỏi này 4 năm sau, thật khó để đoán bài đăng nào từng là bài được bình chọn cao thứ 2.
John Reynolds

Câu trả lời:


103

Đây là một ví dụ về không gian thịt :

Ô tô có những điều trừu tượng cho người lái xe. Ở dạng thuần túy nhất, có vô lăng, chân ga và phanh. Sự trừu tượng này ẩn rất nhiều chi tiết về những gì bên dưới mui xe: động cơ, cam, dây đai thời gian, bugi, bộ tản nhiệt, v.v.

Điều thú vị về sự trừu tượng này là chúng ta có thể thay thế các phần của quá trình triển khai bằng các phần được cải tiến mà không cần đào tạo lại người dùng. Giả sử chúng tôi thay thế nắp phân phối bằng đánh lửa điện tử và chúng tôi thay thế cam cố định bằng cam biến thiên. Những thay đổi này cải thiện hiệu suất nhưng người dùng vẫn lái bằng bánh xe và sử dụng bàn đạp để bắt đầu và dừng lại.

Nó thực sự khá đáng chú ý ... một người 16 tuổi hoặc 80 tuổi có thể vận hành bộ máy phức tạp này mà không thực sự biết nhiều về cách nó hoạt động bên trong!

Nhưng có rò rỉ. Đường truyền là một rò rỉ nhỏ. Trong hộp số tự động, bạn có thể cảm thấy chiếc xe bị mất điện trong giây lát khi chuyển số, trong khi ở hộp số CVT, bạn cảm thấy mô-men xoắn mượt mà khi tăng tốc.

Có những rò rỉ lớn hơn, quá. Nếu bạn quay động cơ quá nhanh, bạn có thể làm hỏng nó. Nếu lốc máy quá lạnh, xe có thể không nổ máy hoặc hoạt động kém. Và nếu bạn chỉnh cả radio, đèn pha và AC cùng một lúc, bạn sẽ thấy mức tiết kiệm xăng của mình giảm xuống.


7
Cảm ơn vì ví dụ. Không ai khác dường như có thể đưa ra một lời giải thích đơn giản.
Sebastian Patten

7
Đây là một câu trả lời tuyệt vời, đặc biệt vì nó thể hiện quan điểm của người dùng, đó là tất cả những gì phiên bản phần mềm.
chad

1
Không gian thịt có nghĩa là gì? Cư sĩ giải thích?
brumScouse

1
@brumScouse "không gian thịt" có nghĩa là thế giới thực, ngoại tuyến. Nó được sử dụng để đối lập với thế giới trực tuyến, không gian mạng. Tôi sẽ chỉnh sửa câu trả lời của mình để bao gồm một liên kết đến định nghĩa.
Mark E. Haase

Tôi thích cách bài đăng này chỉ ra rằng "vẫn còn rò rỉ". Đó là tất cả về việc giảm thiểu chúng.
alaboudi

49

Nó chỉ đơn giản có nghĩa là phần trừu tượng của bạn tiết lộ một số chi tiết triển khai hoặc bạn cần biết chi tiết triển khai khi sử dụng phần trừu tượng. Thuật ngữ này được gán cho Joel Spolsky , khoảng năm 2002. Xem bài viết trên wikipedia để biết thêm thông tin.

Một ví dụ cổ điển là các thư viện mạng cho phép bạn coi các tệp từ xa là cục bộ. Nhà phát triển sử dụng phần trừu tượng này phải biết rằng các sự cố mạng có thể khiến việc này không thành công theo cách mà các tệp cục bộ không làm được. Sau đó, bạn cần phát triển mã để xử lý các lỗi cụ thể nằm ngoài phần trừu tượng mà thư viện mạng cung cấp.


7
@mehaase Tôi không thấy vấn đề như thế nào cho dù phần tóm tắt của bạn bị rò rỉ do thiết kế hay do sơ ý. Tôi đã mở rộng câu trả lời bằng một ví dụ và thêm thông tin từ bài viết được tham khảo để nó có thể tự đứng vững. Hơn nữa, tôi không nghĩ rằng "sự trừu tượng bị rò rỉ" nhất thiết phải là một điều đáng kinh ngạc. Đối với tôi, nó chỉ mô tả một tình huống mà bạn, với tư cách là một nhà phát triển, cần phải cẩn thận hơn khi làm việc với phần trừu tượng. Thiết kế có thể tốt, xấu, hoặc không phụ thuộc vào "độ rò rỉ".
tvanfosson

12

Wikipedia có một định nghĩa khá tốt cho điều này

Một phần trừu tượng bị rò rỉ đề cập đến bất kỳ phần trừu tượng nào được triển khai, nhằm mục đích giảm (hoặc ẩn) sự phức tạp, trong đó các chi tiết cơ bản không bị ẩn hoàn toàn

Hay nói cách khác đối với phần mềm, đó là khi bạn có thể quan sát chi tiết triển khai của một tính năng thông qua các hạn chế hoặc tác dụng phụ trong chương trình.

Một ví dụ nhanh sẽ là các lần đóng C # / VB.Net và chúng không có khả năng nắm bắt các tham số ref / out. Lý do chúng không thể được nắm bắt là do một chi tiết thực hiện của quá trình nâng xảy ra như thế nào. Điều này không có nghĩa là mặc dù có một cách tốt hơn để làm điều này.


12

Đây là một ví dụ quen thuộc với các nhà phát triển .NET: PageLớp của ASP.NET cố gắng ẩn các chi tiết của các hoạt động HTTP, đặc biệt là việc quản lý dữ liệu biểu mẫu, để các nhà phát triển không phải xử lý các giá trị đã đăng (vì nó tự động ánh xạ các giá trị biểu mẫu tới máy chủ điều khiển).

Nhưng nếu bạn vượt ra ngoài các tình huống sử dụng cơ bản nhất, phần Pagetrừu tượng bắt đầu bị rò rỉ và nó trở nên khó hoạt động với các trang trừ khi bạn hiểu chi tiết triển khai của lớp.

Một ví dụ phổ biến là thêm động các điều khiển vào trang - giá trị của các điều khiển được thêm động sẽ không được ánh xạ cho bạn trừ khi bạn thêm chúng vào đúng thời điểm : trước khi công cụ bên dưới ánh xạ các giá trị biểu mẫu đến các điều khiển thích hợp. Khi bạn phải học điều đó, sự trừu tượng đã bị rò rỉ .


Biểu mẫu web có đáy bo trong nhóm của nó. Điều tồi tệ hơn là những phần trừu tượng được che đậy mỏng manh lại làm việc với Http giống như bạn đang làm việc trong một chiếc hộp đựng găng tay.
brumScouse

8

Vâng, theo một cách nào đó, nó là một điều hoàn toàn lý thuyết, mặc dù không phải là không quan trọng.

Chúng tôi sử dụng những điều trừu tượng để làm cho mọi thứ dễ hiểu hơn. Tôi có thể thao tác trên một lớp chuỗi bằng một số ngôn ngữ để che giấu sự thật rằng tôi đang xử lý một tập hợp ký tự có thứ tự là các mục riêng lẻ. Tôi xử lý một tập hợp các ký tự có thứ tự để che giấu sự thật rằng tôi đang xử lý các con số. Tôi xử lý các con số để che giấu sự thật rằng tôi đang xử lý các số 1 và số 0.

Một phần trừu tượng bị rò rỉ là phần không che giấu các chi tiết mà nó có nghĩa là để ẩn. Nếu lệnh gọi string.Length trên một chuỗi 5 ký tự trong Java hoặc .NET, tôi có thể nhận được bất kỳ câu trả lời nào từ 5 đến 10, vì chi tiết triển khai mà những gì các ngôn ngữ đó gọi ký tự thực sự là điểm dữ liệu UTF-16 có thể đại diện cho 1 hoặc .5 của một ký tự. Sự trừu tượng đã bị rò rỉ. Mặc dù vậy, không rò rỉ nó có nghĩa là việc tìm độ dài sẽ yêu cầu nhiều không gian lưu trữ hơn (để lưu trữ độ dài thực) hoặc thay đổi từ O (1) thành O (n) (để tính độ dài thực là bao nhiêu). Nếu tôi quan tâm đến câu trả lời thực sự (thường là bạn không thực sự thực sự), bạn cần phải tìm hiểu về những gì đang thực sự diễn ra.

Nhiều trường hợp tranh luận hơn xảy ra với các trường hợp như trong đó một phương thức hoặc thuộc tính cho phép bạn tham gia vào hoạt động bên trong, cho dù chúng là rò rỉ trừu tượng hay những cách được xác định rõ ràng để chuyển sang mức trừu tượng thấp hơn, đôi khi có thể là một vấn đề mà mọi người không đồng ý.


2
Và bạn làm việc với số 1 và số 0 để che giấu sự thật rằng bạn đang làm việc với điện tử và vật lý (nhận xét rất muộn, tôi biết)
Davy8

6

Tôi sẽ tiếp tục đưa ra các ví dụ bằng cách sử dụng RPC.

Trong thế giới lý tưởng của RPC, một cuộc gọi thủ tục từ xa sẽ giống như một cuộc gọi thủ tục cục bộ (hoặc câu chuyện diễn ra như vậy). Nó phải hoàn toàn minh bạch với lập trình viên để khi họ gọi, SomeObject.someFunction()họ không biết liệu SomeObject(hoặc chỉ someFunctionvì vấn đề đó) được lưu trữ và thực thi cục bộ hay được lưu trữ và thực thi từ xa. Lý thuyết cho rằng điều này làm cho việc lập trình trở nên đơn giản hơn.

Thực tế là khác bởi vì có sự khác biệt LỚN giữa việc thực hiện một cuộc gọi hàm cục bộ (ngay cả khi bạn đang sử dụng ngôn ngữ được thông dịch chậm nhất thế giới) và:

  • gọi thông qua một đối tượng proxy
  • tuần tự hóa các thông số của bạn
  • tạo kết nối mạng (nếu chưa được thiết lập)
  • truyền dữ liệu tới proxy từ xa
  • yêu cầu proxy từ xa khôi phục dữ liệu và thay mặt bạn gọi chức năng từ xa
  • tuần tự hóa (các) giá trị trả về
  • truyền các giá trị trả về tới proxy cục bộ
  • tập hợp lại dữ liệu được tuần tự hóa
  • trả lại phản hồi từ chức năng từ xa

Chỉ riêng trong thời gian, đó là khoảng ba đơn đặt hàng (hoặc nhiều hơn!) Chênh lệch về độ lớn. Ba + bậc lớn đó sẽ tạo ra sự khác biệt rất lớn về hiệu suất, điều này sẽ làm cho sự tóm tắt của bạn về một lệnh gọi thủ tục bị rò rỉ khá rõ ràng lần đầu tiên bạn coi RPC là một lệnh gọi hàm thực. Hơn nữa, một cuộc gọi hàm thực, ngăn chặn các vấn đề nghiêm trọng trong mã của bạn, sẽ có rất ít điểm lỗi ngoài lỗi triển khai. Một cuộc gọi RPC có tất cả các vấn đề có thể xảy ra sau đây sẽ được giải quyết dưới dạng các trường hợp thất bại hơn và cao hơn những gì bạn mong đợi từ một cuộc gọi cục bộ thông thường:

  • bạn không thể khởi tạo proxy cục bộ của mình
  • bạn không thể khởi tạo proxy từ xa của mình
  • các proxy có thể không kết nối được
  • các thông số bạn gửi có thể không làm cho nó nguyên vẹn hoặc hoàn toàn
  • giá trị trả về mà điều khiển từ xa gửi có thể không làm cho nó nguyên vẹn hoặc hoàn toàn

Vì vậy, bây giờ lệnh gọi RPC của bạn "giống như một cuộc gọi hàm cục bộ" có một loạt các điều kiện lỗi bổ sung mà bạn không phải đối mặt khi thực hiện các lệnh gọi hàm cục bộ. Sự trừu tượng đã bị rò rỉ một lần nữa, thậm chí còn khó hơn.

Cuối cùng thì RPC là một sự trừu tượng tồi tệ vì nó bị rò rỉ như một cái sàng ở mọi cấp độ - khi thành công và khi thất bại cả hai.


<pimp> Tôi thích cách tiếp cận này của Erlang hơn ở chỗ nó không cố gắng che giấu sự khác biệt giữa một lệnh gọi hàm và gửi tin nhắn đến một quy trình đến mức cả hai sử dụng cú pháp rất khác nhau. Và việc gửi tin nhắn theo quy trình từ xa rất khác so với gửi theo quy trình cục bộ, mặc dù sử dụng cùng một cú pháp chung. </pimp>
JUST MY OPINION đúng

2
Chà, đây là phản hồi duy nhất thực sự đưa ra một ví dụ điển hình (đọc hiểu, mọi người), vì vậy nó nhận được +1 của tôi.
Mark E. Haase

3

Một ví dụ trong ví dụ nhiều đến nhiều django ORM :

Lưu ý trong Cách sử dụng API mẫu rằng bạn cần .save () đối tượng Article cơ sở a1 trước khi bạn có thể thêm các đối tượng Publication vào thuộc tính many-to-many. Và lưu ý rằng việc cập nhật thuộc tính many-to-many sẽ lưu vào cơ sở dữ liệu bên dưới ngay lập tức, trong khi cập nhật một thuộc tính số ít không được phản ánh trong db cho đến khi .save () được gọi.

Sự trừu tượng là chúng ta đang làm việc với một đồ thị đối tượng, trong đó các thuộc tính giá trị đơn và thuộc tính giá trị mult chỉ là các thuộc tính. Nhưng việc triển khai như một kho lưu trữ dữ liệu được hỗ trợ bởi cơ sở dữ liệu quan hệ bị rò rỉ ... khi hệ thống toàn vẹn của RDBS xuất hiện thông qua lớp mỏng của một giao diện đối tượng.


1

Trừu tượng là gì?

Đầu tiên tốt nhất bạn nên hiểu "trừu tượng" là gì?

Trừu tượng là một cách đơn giản hóa thế giới. Nó có nghĩa là bạn không phải lo lắng về những gì đang thực sự xảy ra dưới mui xe / đằng sau bức màn. Nó có nghĩa là một cái gì đó là bằng chứng ngu ngốc. Ok, vậy điều đó có nghĩa là gì? Điều này được minh họa tốt nhất bằng ví dụ.

Ví dụ về sự trừu tượng: Sự phức tạp của việc bay một chiếc 737/747 được "trừu tượng hóa" đi

Chúng ta hãy lấy ví dụ về một chiếc máy bay chở khách của Boeing. Những chiếc máy bay này là những phần máy móc rất phức tạp. Bạn có động cơ phản lực, hệ thống oxy, hệ thống điện, hệ thống thiết bị hạ cánh, v.v. nhưng phi công không phải lo lắng về sự phức tạp của động cơ phản lực .... tất cả những thứ đó được "trừu tượng hóa", có nghĩa là: ở cuối trong ngày, một phi công chỉ lo cho bánh xe và một cột điều khiển để điều khiển máy bay. Trái để đi sang trái, và phải để đi sang phải, kéo lên để tăng độ cao và ấn xuống để hạ xuống. Nó đủ đơn giản ...... thực ra tôi đã nói dối: điều khiển tay lái phức tạp hơn một chút. Trong một thế giới lý tưởng, đó là điều duy nhất anh ấy nênđược lo lắng về. Nhưng đây không phải là trường hợp trong đời thực: nếu bạn lái máy bay như một con khỉ, mà không có bất kỳ hiểu biết thực sự nào về cách máy bay vận hành hoặc bất kỳ chi tiết triển khai nào, thì bạn có thể sẽ rơi và giết tất cả mọi người trên máy bay.

Tóm tắt rò rỉ

Trong thực tế, một phi công phải lo lắng về RẤT NHIỀU điều quan trọng - không phải tất cả mọi thứ đã được trừu tượng hóa: phi công phải lo lắng về tốc độ gió, lực đẩy, góc tấn công, nhiên liệu, độ cao, các vấn đề thời tiết, góc hạ, liệu phi công đang đi đúng hướng, nơi máy bay đang ở ngay bây giờ và v.v. Máy tính có thể giúp phi công trong những công việc này, nhưng không phải mọi thứ đều được tự động hóa / đơn giản hóa.

Ví dụ: Nếu phi công kéo quá mạnh vào cột - máy bay sẽ tuân theo, nhưng sau đó phi công sẽ có nguy cơ làm máy bay bị đình trệ, và nếu bạn dừng máy bay, rất khó để lấy lại kiểm soát trước khi nó lao xuống đất. .

Nói cách khác, phi công chỉ cần điều khiển vô lăng mà không biết gì khác là chưa đủ ......... không ....... cô ấy phải biết về những rủi ro và hạn chế tiềm ẩn của máy bay trước khi anh ta bay một chiếc ....... cô ấy phải biết máy bay hoạt động như thế nào, và máy bay bay như thế nào; anh ta phải biết chi tiết thực hiện ..... cô ấy phải biết rằng kéo lên quá mạnh sẽ dẫn đến chết máy, hoặc hạ cánh quá dốc sẽ phá hủy máy bay.

Những điều đó không bị trừu tượng hóa đi. Rất nhiều thứ được trừu tượng hóa đi, nhưng không phải tất cả mọi thứ. Phi công chỉ cần lo lắng về cột lái, và có lẽ một hoặc hai điều khác. Sự trừu tượng là "rò rỉ".

...... nó giống như vậy trong mã của bạn. Nếu bạn không biết các chi tiết triển khai cơ bản, thì thường xuyên hơn không, bạn sẽ tự làm việc cho mình.

Đây là một ví dụ trong mã hóa:

ORMs giải thích rất nhiều rắc rối trong việc xử lý các truy vấn cơ sở dữ liệu, nhưng nếu bạn đã từng làm điều gì đó như:

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

Sau đó, bạn sẽ nhận ra đó là một cách hay để giết ứng dụng của mình nếu bạn có hơn vài triệu người dùng. Không phải mọi thứ đều bị trừu tượng hóa. Bạn cần biết rằng việc gọi điện User.allvới 25 triệu người dùng sẽ làm tăng mức sử dụng bộ nhớ của bạn và gây ra nhiều vấn đề. Bạn cần biết một số chi tiết cơ bản. Sự trừu tượng bị rò rỉ.


0

Thực tế là tại một số thời điểm , sẽ được hướng dẫn bởi quy mô và cách thực thi của bạn, bạn sẽ cần phải làm quen với các chi tiết triển khai của khung trừu tượng để hiểu tại sao nó hoạt động theo cách mà nó hoạt động.

Ví dụ: hãy xem xét SQLtruy vấn này :

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

Và thay thế của nó:

SELECT * FROM student_details;

Bây giờ, chúng trông giống như một giải pháp tương đương về mặt logic, nhưng hiệu suất của giải pháp đầu tiên tốt hơn do đặc điểm kỹ thuật tên cột riêng lẻ.

Đó là một ví dụ tầm thường nhưng cuối cùng nó trở lại câu nói của Joel Spolsky:

Tất cả những điều trừu tượng không tầm thường, ở một mức độ nào đó, đều bị rò rỉ.

Tại một số điểm, khi bạn đạt đến một quy mô nhất định trong hoạt động của mình, bạn sẽ muốn tối ưu hóa cách hoạt động của DB (SQL). Để làm điều đó, bạn sẽ cần biết cách hoạt động của cơ sở dữ liệu quan hệ. Nó đã được tóm tắt đối với bạn ngay từ đầu, nhưng nó đã bị rò rỉ. Bạn cần phải học nó tại một số điểm.


-1

Giả sử, chúng ta có mã sau trong thư viện:

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. 
}

Khi người tiêu dùng gọi API, họ sẽ nhận được Đối tượng []. Người tiêu dùng phải hiểu rằng trường đầu tiên của mảng đối tượng có giá trị màu và trường thứ hai là giá trị mô hình. Ở đây phần tóm tắt đã bị rò rỉ từ thư viện sang mã người tiêu dùng.

Một trong những giải pháp là trả về một đối tượng bao gồm Mô hình và Màu sắc của Thiết bị. Người tiêu dùng có thể gọi đối tượng đó để lấy giá trị mô hình và màu sắc.

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

-3

Tóm tắt rò rỉ là tất cả về trạng thái đóng gói. ví dụ rất đơn giản về trừu tượng bị rò rỉ:

$currentTime = new DateTime();

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

class BankAccount {
    // ...

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

và đúng cách (không trừu tượng bị rò rỉ):

class BankAccount
{
    // ...

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

thêm mô tả ở đây .

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.