Trừu tượng: Cuộc chiến giữa giải quyết vấn đề và giải pháp chung [khép kín]


33

Là một lập trình viên, tôi thấy mình rơi vào tình huống khó xử khi tôi muốn làm cho chương trình của mình trừu tượng và chung chung nhất có thể.

Làm như vậy thường sẽ cho phép tôi sử dụng lại mã của mình và có giải pháp tổng quát hơn cho một vấn đề có thể (hoặc có thể không) xảy ra lần nữa.

Sau đó, giọng nói này trong đầu tôi nói, chỉ cần giải quyết vấn đề giả thật dễ dàng! Tại sao phải dành nhiều thời gian hơn bạn phải?

Tất cả chúng ta đã thực sự phải đối mặt với câu hỏi này, nơi Trừu tượng nằm trên vai phải của bạn và Solve-it-ngu ngốc ngồi bên trái.

Mà nghe và thường xuyên như thế nào? Chiến lược của bạn cho việc này là gì? Bạn có nên trừu tượng mọi thứ?


1
"Trừu tượng và chung chung" thường được gọi là sự kiêu ngạo của lập trình viên :) Tuy nhiên, vì luật của Murphy, một mức độ thích ứng mà bạn cần khi thực hiện nhiệm vụ tiếp theo sẽ là một điều bạn chưa cung cấp.
Matthieu M.

1
Một trong những câu hỏi hay nhất!
thám hiểm

1
Bạn luôn đoán sai nên thấm nó đơn giản. Khi bạn đã mở rộng các trường hợp đơn giản "đủ số lần", bạn bắt đầu thấy một mẫu. Cho đến lúc đó, không.

Câu trả lời:


27

Mà nghe và thường xuyên như thế nào?

Không bao giờ trừu tượng cho đến khi bạn phải.

Trong Java, ví dụ, bạn phải sử dụng các giao diện. Chúng là một sự trừu tượng.

Trong Python bạn không có giao diện, bạn có Duck Typing và bạn không cần mức độ trừu tượng cao chót vót. Vì vậy, bạn chỉ không.

Chiến lược của bạn cho việc này là gì?

Đừng trừu tượng cho đến khi bạn viết nó ba lần.

Một lần là - tốt - một lần. Chỉ cần giải quyết nó và di chuyển trên.

Hai lần là dấu hiệu cho thấy có thể có một mô hình ở đây. Hoặc có thể không. Nó chỉ có thể là sự trùng hợp.

Ba lần là sự khởi đầu của một mô hình. Bây giờ nó vượt qua sự trùng hợp. Bây giờ bạn có thể trừu tượng thành công.

Bạn có nên trừu tượng mọi thứ?

Không.

Thật vậy, bạn không bao giờ nên trừu tượng bất cứ điều gì cho đến khi bạn có bằng chứng tuyệt đối rằng bạn đang thực hiện đúng loại trừu tượng. Nếu không có "Quy tắc ba lần lặp lại", bạn sẽ viết những thứ vô dụng được trừu tượng hóa theo cách không có ích.

Làm như vậy thường sẽ cho phép tôi sử dụng lại mã của mình

Đây là một giả định thường sai. Trừu tượng có thể không giúp đỡ gì cả. Nó có thể được thực hiện xấu. Do đó, đừng làm điều đó cho đến khi bạn phải.


1
"Không bao giờ trừu tượng cho đến khi bạn phải." - Tôi mang nó bạn tránh sử dụng các lớp, phương thức, vòng lặp, hoặc nếu câu lệnh? Những điều này là tất cả trừu tượng. Nếu bạn nghĩ về nó, mã được viết bằng các ngôn ngữ cấp cao rất trừu tượng cho dù bạn có thích hay không. Câu hỏi không phải là trừu tượng. Đó là trừu tượng để sử dụng.
Jason Baker

9
@Jason Baker: "Tôi hiểu rằng bạn tránh sử dụng các lớp, phương thức, vòng lặp hoặc câu lệnh if". Đó dường như không phải là những gì câu hỏi về. Tại sao đưa ra tuyên bố vô lý rằng tất cả các chương trình là không thể nếu chúng ta tránh quá trừu tượng hóa các thiết kế của chúng tôi?
S.Lott

1
Tôi nhớ lại câu chuyện cũ mà một người đàn ông hỏi một người phụ nữ rằng cô ấy sẽ ngủ với anh ta một triệu đô la. Khi cô nói đồng ý, anh hỏi cô có ngủ với anh năm đô không. Khi cô ấy nói "Bạn lấy tôi làm kiểu phụ nữ nào?" câu trả lời là "Chà, chúng tôi đã xác nhận rằng bạn sẽ ngủ với ai đó để quan hệ tình dục. Bây giờ chúng tôi chỉ mặc cả về giá cả." Quan điểm của tôi là không bao giờ quyết định trừu tượng hay không. Đó là về mức độ trừu tượng và những gì trừu tượng để lựa chọn. Bạn không thể chọn giữ sự trừu tượng trừ khi bạn đang viết lắp ráp thuần túy.
Jason Baker

9
@Jason Baker: "Bạn không thể chọn giữ sự trừu tượng trừ khi bạn viết lắp ráp thuần túy" Điều đó không đúng. Hội là một sự trừu tượng về ngôn ngữ máy. Đó là một sự trừu tượng trên các mạch phần cứng. Xin vui lòng ngừng đọc quá nhiều vào câu trả lời không có trong câu hỏi ở nơi đầu tiên. Câu hỏi không phải là "Trừu tượng" như một khái niệm hay một công cụ trí tuệ. Đó là về sự trừu tượng như một kỹ thuật thiết kế OO - áp dụng sai - quá thường xuyên. Câu trả lời của tôi không phải là trừu tượng là không thể. Nhưng "quá trừu tượng" là xấu.
S.Lott

1
@JasonBaker nó không phải là một lý do chưa từng thấy để ngủ với ai đó. Có lẽ bạn đã nghĩ đến "tiền"?

19

À, YAGNI. Khái niệm lạm dụng nhất của lập trình.

Có một sự khác biệt giữa làm cho mã của bạn chung chung và làm thêm. Bạn có nên dành thêm thời gian để làm cho mã của bạn được ghép lỏng lẻo và dễ dàng thích nghi để làm những việc khác? Chắc chắn rồi. Bạn có nên dành thời gian thực hiện chức năng không cần thiết? Không. Bạn có nên dành thời gian để làm cho mã của bạn hoạt động với mã khác chưa được cấy chưa? Không.

Như câu nói "Hãy làm điều đơn giản nhất có thể làm việc". Có điều là mọi người luôn nhầm lẫn đơn giản với dễ dàng . Đơn giản có công việc. Nhưng nó là giá trị nỗ lực.

Bạn có thể quá nhiệt tình không? Tất nhiên. Nhưng kinh nghiệm của tôi là ít công ty cần nhiều thái độ "hoàn thành công việc ngay bây giờ" hơn họ đã có. Hầu hết các công ty cần nhiều người hơn, những người sẽ nghĩ mọi thứ trước thời hạn. Mặt khác, họ luôn luôn có một vấn đề tự duy trì trong đó không ai có thời gian cho bất cứ điều gì, mọi người luôn vội vàng, và không ai làm được gì.

Hãy suy nghĩ về nó theo cách này: rất có thể mã của bạn sẽ được sử dụng lại bằng cách nào đó. Nhưng bạn sẽ không dự đoán chính xác theo cách đó. Vì vậy, làm cho mã của bạn sạch sẽ, trừu tượng và kết nối lỏng lẻo. Nhưng đừng cố làm cho mã của bạn hoạt động với bất kỳ phần chức năng cụ thể nào chưa tồn tại. Ngay cả khi bạn biết nó sẽ tồn tại trong tương lai.


Đẹp phân biệt giữa simpleeasy!
Matthieu M.


"Nhưng kinh nghiệm của tôi là rất ít công ty" - thật thú vị, điều ngược lại có thể đúng trong giới học thuật.
Steve Bennett

12

Một tam đoạn luận:

  1. Tổng quát là đắt tiền.

  2. Bạn đang tiêu tiền của người khác.

  3. Do đó, chi phí của tính tổng quát phải được chứng minh cho các bên liên quan.

Hãy tự hỏi mình nếu bạn thực sự giải quyết vấn đề chung chung hơn để tiết kiệm tiền cho các bên liên quan sau này, hoặc nếu bạn chỉ thấy đó là một thách thức trí tuệ để đưa ra một giải pháp chung không cần thiết.

Nếu tính tổng quát được xác định là mong muốn thì nó nên được thiết kế và thử nghiệm như bất kỳ tính năng nào khác . Nếu bạn không thể viết các bài kiểm tra chứng minh tính tổng quát mà bạn đã triển khai giải quyết vấn đề theo yêu cầu kỹ thuật thì đừng bận tâm làm điều đó! Một tính năng không đáp ứng tiêu chí thiết kế và không thể kiểm tra là tính năng không ai có thể tin cậy.

Và cuối cùng, không có thứ gọi là "chung chung nhất có thể". Giả sử bạn viết phần mềm bằng C #, chỉ để tranh luận. Bạn sẽ làm cho mỗi lớp thực hiện một giao diện? Mỗi lớp một lớp cơ sở trừu tượng với mỗi phương thức một phương thức trừu tượng? Điều đó khá chung chung, nhưng nó không ở đâu gần "càng chung càng tốt". Điều đó chỉ cho phép mọi người thay đổi việc thực hiện bất kỳ phương thức nào thông qua phân lớp. Điều gì xảy ra nếu họ muốn thay đổi việc thực hiện một phương thức mà không cần phân lớp? Bạn có thể biến mọi phương thức thực sự thành một thuộc tính của kiểu ủy nhiệm, với một setter để mọi người có thể thay đổi mọi phương thức thành một phương thức khác. Nhưng nếu ai đó muốn thêm nhiều phương thức thì sao? Bây giờ mọi đối tượng phải được mở rộng.

Tại thời điểm này, bạn nên từ bỏ C # và nắm lấy JavaScript. Nhưng bạn vẫn chưa nhận được bất cứ nơi nào gần đủ chung; Điều gì xảy ra nếu ai đó muốn thay đổi cách tra cứu thành viên làm việc cho đối tượng cụ thể này? Có lẽ bạn nên viết mọi thứ bằng Python thay thế.

Tăng tính tổng quát thường có nghĩa là từ bỏ khả năng dự đoán và tăng ồ ạt chi phí kiểm tra để đảm bảo rằng tính tổng quát được thực hiện thực sự thành công trong việc đáp ứng nhu cầu người dùng thực sự . Là những chi phí được chứng minh bằng lợi ích của họ cho các bên liên quan đang trả tiền cho nó? Có lẽ họ là; đó là tùy thuộc vào bạn để thảo luận với các bên liên quan. Các bên liên quan của tôi hoàn toàn không sẵn sàng cho tôi từ bỏ việc gõ tĩnh để theo đuổi một mức độ tổng quát hoàn toàn không cần thiết và cực kỳ tốn kém, nhưng có lẽ là của bạn.


2
+1 cho các nguyên tắc hữu ích, -1 để tạo ra tất cả về tiền bạc.
Mason Wheeler

4
@Mason: Không phải là về tiền , mà là về nỗ lực . Tiền là một thước đo thực tế của nỗ lực . Hiệu quả là lợi ích tích lũy trên mỗi nỗ lực được tạo ra; một lần nữa, lợi nhuận bằng tiền là thước đo thực tế của lợi ích tích lũy . Đơn giản là dễ dàng thảo luận về sự trừu tượng của tiền hơn là đưa ra một số cách để đo lường nỗ lực, chi phí và lợi ích. Bạn có muốn đo lường nỗ lực, chi phí và lợi ích theo một cách khác? Hãy đưa ra một đề nghị của một cách tốt hơn.
Eric Lippert

2
Tôi đồng ý với quan điểm của @Mason Wheeler. Tôi thấy giải pháp ở đây là không liên quan đến các bên liên quan ở cấp độ đó của một dự án. Nó rõ ràng phụ thuộc rất nhiều vào ngành công nghiệp, nhưng khách hàng thường không nhìn thấy mã. Ngoài ra, tất cả những câu trả lời này có vẻ chống lại nỗ lực, có vẻ lười biếng.
Orble

2
@ Tổ chức: Tôi cực lực phản đối những nỗ lực không cần thiết, tốn kém, lãng phí, lấy đi nguồn lực từ các dự án quan trọng và xứng đáng. Nếu bạn đang gợi ý rằng điều này làm cho tôi "lười biếng" thì tôi gửi rằng bạn đang sử dụng từ "lười biếng" một cách bất thường.
Eric Lippert

1
Theo kinh nghiệm của tôi, các dự án khác không có xu hướng biến mất, vì vậy thường đáng để đầu tư nhiều thời gian cần thiết trong dự án hiện tại trước khi tiếp tục.
Orble

5

Nói rõ hơn, làm cho một cái gì đó khái quát và thực hiện một sự trừu tượng là hai điều hoàn toàn khác nhau.

Ví dụ, hãy xem xét một chức năng sao chép bộ nhớ.

Hàm này là một bản tóm tắt ẩn cách 4 byte được sao chép.

int copy4Bytes (char * pSrc, char * pDest)

Một khái quát hóa sẽ làm cho hàm này sao chép bất kỳ số byte nào.

int copyBytes (char * pSrc, char * pDest, int numBytesToCopy)

Sự trừu tượng cho vay để tái sử dụng, trong khi việc khái quát hóa chỉ làm cho sự trừu tượng hóa trở nên hữu ích trong nhiều trường hợp.

Cụ thể hơn liên quan đến câu hỏi của bạn, Trừu tượng không chỉ hữu ích từ góc độ tái sử dụng mã. Thông thường, nếu được thực hiện một cách chính xác, nó sẽ làm cho mã của bạn dễ đọc và dễ bảo trì hơn. Sử dụng ví dụ trên, điều gì dễ đọc và dễ hiểu hơn nếu bạn lướt qua mã copyBytes () hoặc một vòng lặp để lặp qua một mảng di chuyển dữ liệu một chỉ mục tại một thời điểm? Trừu tượng có thể cung cấp một loại tài liệu tự mà theo tôi làm cho mã dễ làm việc hơn.

Theo quy tắc riêng của tôi, nếu tôi có thể đưa ra một tên hàm tốt mô tả chính xác những gì tôi dự định làm một đoạn mã thì tôi sẽ viết một hàm cho nó bất kể tôi có nghĩ mình sẽ sử dụng lại nó hay không.


+1 để tạo sự khác biệt giữa trừu tượng hóa và khái quát hóa.
Joris Meys

4

Một quy tắc chung tốt cho loại điều này là Zero, One, Infinity. Đó là, nếu bạn cần những gì bạn viết nhiều hơn một lần, bạn có thể cho rằng bạn sẽ cần nó nhiều lần hơn nữa và khái quát hóa. Quy tắc này ngụ ý rằng bạn không bận tâm đến sự trừu tượng trong lần đầu tiên bạn viết một cái gì đó.

Một lý do chính đáng khác cho quy tắc này là lần đầu tiên bạn viết mã, bạn sẽ không nhất thiết phải biết những gì cần trừu tượng vì bạn chỉ có một ví dụ. Luật Murphy ngụ ý rằng nếu bạn viết mã trừu tượng lần đầu tiên, ví dụ thứ hai sẽ có những khác biệt mà bạn không lường trước được.


1
Nghe có vẻ giống như phiên bản Quy tắc tái cấu trúc của tôi: tấn công hai! cấu trúc lại!
Frank Shearar

@Frank: Đây có thể là Quy tắc tái cấu trúc của bạn, nhưng với đoạn thứ hai được thêm vào từ kinh nghiệm cá nhân.
Larry Coleman

+1: Tôi tin rằng điều này cũng được ghi nhận khá sớm trong Lập trình viên thực dụng gần như nguyên văn IIRC.
Steven Evers

chắc chắn rồi Không có gì để trừu tượng chỉ với một ví dụ: ngay khi bạn có một ví dụ thứ hai, bạn có thể thấy những gì phổ biến (được chia sẻ) và những gì không, và bạn có thể trừu tượng hóa!
Frank Shearar

Một mẫu của hai là theo tôi quá nhỏ để khái quát cho vô cùng. Nói cách khác, nhiều quá sớm.

2

Eric Lippert chỉ ra ba điều mà tôi nghĩ áp dụng trong bài viết của mình trong tương lai chứng minh một thiết kế . Tôi nghĩ rằng nếu bạn làm theo nó, bạn sẽ có một hình dạng tốt.

Thứ nhất: Tổng quát sớm là đắt tiền.

Thứ hai: Đại diện trong mô hình của bạn chỉ những thứ luôn nằm trong miền vấn đề và mối quan hệ lớp không thay đổi.

Thứ ba: Giữ chính sách của bạn tránh xa các cơ chế của bạn.


1

Nó phụ thuộc vào lý do tại sao bạn viết mã, mục đích của dự án của bạn. Nếu giá trị của mã của bạn là giải quyết được một vấn đề cụ thể, thì bạn muốn hoàn thành nó và chuyển sang vấn đề tiếp theo. Nếu có những điều nhanh chóng và dễ dàng mà bạn có thể làm để làm cho mọi thứ dễ dàng hơn cho những người sử dụng mã trong tương lai (bao gồm cả chính bạn) thì bằng mọi cách, hãy sắp xếp chỗ ở hợp lý.

Mặt khác, có những trường hợp mã bạn đang viết phục vụ mục đích chung hơn. Ví dụ, khi bạn đang viết thư viện cho các lập trình viên khác, những người sẽ sử dụng nó trong rất nhiều ứng dụng. Người dùng tiềm năng chưa biết và bạn không thể hỏi họ chính xác những gì họ muốn. Nhưng bạn muốn làm cho thư viện của bạn rộng rãi hữu ích. Trong những trường hợp như vậy, tôi sẽ dành nhiều thời gian hơn để cố gắng hỗ trợ giải pháp chung.


0

Tôi là một fan hâm mộ lớn của nguyên tắc KISS.

Tôi tập trung vào việc cung cấp những gì tôi được yêu cầu, và không phải giải pháp tốt nhất là gì. Tôi đã phải từ bỏ chủ nghĩa cầu toàn (và OCD), vì nó khiến tôi đau khổ.


Tại sao không thích sự hoàn hảo? (Tôi đã không bỏ phiếu cho bạn bằng cách này)
Orble

Không nhắm đến sự hoàn hảo làm việc cho tôi. Tại sao? Bởi vì tôi biết chương trình của tôi sẽ không bao giờ hoàn hảo. Không có định nghĩa chuẩn về sự hoàn hảo, vì vậy tôi chỉ chọn cung cấp một cái gì đó hoạt động tốt.
Pablo

-1

nơi Trừu tượng nằm trên vai phải của bạn và Solve-it-ngu ngốc ngồi bên trái.

Tôi không đồng ý với "giải quyết nó ngu ngốc" , tôi nghĩ rằng nó có thể "giải quyết nó thông minh hơn" .

Những gì thông minh hơn:

  • Viết một giải pháp tổng quát phức tạp có thể hỗ trợ nhiều trường hợp
  • Viết mã ngắn và hiệu quả để giải quyết vấn đề trong tay và dễ bảo trì, và có thể được mở rộng trong tương lai NẾU cần thiết.

Sự lựa chọn mặc định nên là cái thứ hai. Trừ khi bạn có thể chứng minh một nhu cầu cho thế hệ.

Giải pháp tổng quát chỉ nên là khi bạn biết rằng đó là thứ sẽ được sử dụng trong nhiều dự án / trường hợp khác nhau.

Thông thường tôi thấy các giải pháp tổng quát được phục vụ tốt nhất là "Mã thư viện cốt lõi"


Nếu bạn có thể đưa ra tất cả các giả định ở đây, bạn sẽ không gặp vấn đề gì. Ngắn và dễ bảo trì là loại trừ lẫn nhau. Nếu bạn biết một cái gì đó sẽ được sử dụng lại, giải pháp chung sẽ giải quyết điều đó.
JeffO

@Jeff Tôi không chắc là tôi hiểu bình luận của bạn ..
Darknight

Bạn đã đưa "Giải quyết nó ngu ngốc" ra khỏi bối cảnh.
Bryan Harrington
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.