Làm trừu tượng có phải giảm khả năng đọc mã?


19

Một nhà phát triển giỏi mà tôi làm việc cùng đã nói với tôi gần đây về một số khó khăn mà anh ta gặp phải khi triển khai một tính năng trong một số mã mà chúng tôi đã thừa hưởng; Ông nói rằng vấn đề là mã rất khó theo dõi. Từ đó, tôi nhìn sâu hơn vào sản phẩm và nhận ra việc thấy đường dẫn mã khó đến mức nào.

Nó đã sử dụng rất nhiều giao diện và các lớp trừu tượng, đến nỗi cố gắng hiểu nơi mọi thứ bắt đầu và kết thúc là khá khó khăn. Nó khiến tôi suy nghĩ về những lần tôi đã xem xét các dự án trong quá khứ (trước khi tôi nhận thức được các nguyên tắc mã sạch) và thấy rất khó khăn trong việc thực hiện dự án, chủ yếu là vì các công cụ điều hướng mã của tôi sẽ luôn đưa tôi đến một giao diện. Sẽ mất rất nhiều nỗ lực để tìm ra cách triển khai cụ thể hoặc nơi mà một cái gì đó được nối dây trong một số kiến ​​trúc kiểu plugin.

Tôi biết một số nhà phát triển hoàn toàn từ chối các container tiêm phụ thuộc vì lý do này. Nó nhầm lẫn đường dẫn của phần mềm đến mức độ khó của điều hướng mã được tăng theo cấp số nhân.

Câu hỏi của tôi là: khi một khung hoặc mẫu giới thiệu quá nhiều chi phí như thế này, nó có đáng không? Nó có phải là một triệu chứng của một mô hình được thực hiện kém?

Tôi đoán một nhà phát triển nên nhìn vào bức tranh lớn hơn về những gì trừu tượng mang lại cho dự án để giúp họ vượt qua sự thất vọng. Thông thường, thật khó để làm cho họ nhìn thấy bức tranh lớn đó. Tôi biết tôi đã thất bại trong việc bán nhu cầu của IOC và DI với TDD. Đối với những nhà phát triển này, việc sử dụng những công cụ đó chỉ gây khó chịu cho việc đọc mã quá nhiều.

Câu trả lời:


17

Đây thực sự là một nhận xét dài về câu trả lời của @kevin cline.

Mặc dù bản thân các ngôn ngữ không nhất thiết phải gây ra hoặc ngăn chặn điều này, tôi nghĩ có một điều gì đó với ý kiến ​​của anh ấy rằng nó liên quan đến ngôn ngữ (hoặc ít nhất là cộng đồng ngôn ngữ) ở một mức độ nào đó. Cụ thể, mặc dù bạn có thể gặp phải cùng một vấn đề trong các ngôn ngữ khác nhau, nhưng nó thường sẽ có các hình thức khá khác nhau trong các ngôn ngữ khác nhau.

Ví dụ, khi bạn gặp vấn đề này trong C ++, rất có thể đó là kết quả của sự trừu tượng hóa quá ít và hơn nữa là kết quả của sự thông minh quá mức. Ví dụ, lập trình viên đã ẩn sự biến đổi quan trọng đang xảy ra (mà bạn không thể tìm thấy) trong một trình vòng lặp đặc biệt, do đó, có vẻ như nó chỉ sao chép dữ liệu từ nơi này sang nơi khác thực sự có một số tác dụng phụ không có gì để làm với việc sao chép dữ liệu. Để giữ cho mọi thứ thú vị, điều này được xen kẽ với đầu ra được tạo ra như một tác dụng phụ của việc tạo ra một đối tượng tạm thời trong quá trình truyền một loại đối tượng này sang một đối tượng khác.

Ngược lại, khi bạn gặp nó trong Java, rất có thể bạn sẽ thấy một số biến thể của "thế giới xin chào doanh nghiệp" nổi tiếng, trong đó thay vì một lớp tầm thường làm một cái gì đó đơn giản, bạn có một lớp cơ sở trừu tượng và một lớp dẫn xuất cụ thể thực hiện giao diện X và được tạo bởi một lớp nhà máy trong khung DI, v.v ... 10 dòng mã thực hiện công việc thực sự bị chôn vùi dưới 5000 dòng cơ sở hạ tầng.

Một số ngôn ngữ phụ thuộc vào môi trường ít nhất là ngôn ngữ - làm việc trực tiếp với các môi trường cửa sổ như X11 và MS Windows nổi tiếng với việc biến một chương trình "xin chào thế giới" tầm thường thành hơn 300 dòng rác gần như không thể giải mã được. Theo thời gian, chúng tôi cũng đã phát triển nhiều bộ công cụ khác nhau để bảo vệ chúng tôi khỏi điều đó - nhưng 1) những bộ công cụ đó khá không tầm thường và 2) kết quả cuối cùng vẫn không chỉ lớn hơn và phức tạp hơn, mà còn thường kém linh hoạt hơn hơn là một chế độ văn bản tương đương (ví dụ, mặc dù nó chỉ in ra một số văn bản, việc chuyển hướng nó sang một tệp hiếm khi có thể / được hỗ trợ).

Để trả lời (ít nhất là một phần) câu hỏi ban đầu: ít nhất là khi tôi đã nhìn thấy nó, việc thực hiện một mẫu kém hơn là việc áp dụng một mẫu không phù hợp với nhiệm vụ trong tay - hầu hết thường cố gắng áp dụng một số mô hình có thể hữu ích trong một chương trình không thể tránh khỏi rất lớn và phức tạp, nhưng khi áp dụng cho một vấn đề nhỏ hơn cuối cùng cũng khiến nó trở nên to lớn và phức tạp, mặc dù trong trường hợp này kích thước và độ phức tạp thực sự là có thể tránh được .


7

Tôi thấy rằng điều này thường xảy ra do không sử dụng phương pháp YAGNI . Mọi thứ đều thông qua các giao diện, mặc dù chỉ có một triển khai cụ thể và không có kế hoạch hiện tại để giới thiệu các giao diện khác, là một ví dụ điển hình về việc thêm độ phức tạp mà bạn không cần đến. Đó có thể là dị giáo nhưng tôi cảm thấy giống như vậy về việc sử dụng thuốc tiêm phụ thuộc.


+1 để đề cập đến YAGNI và trừu tượng hóa với các điểm tham chiếu duy nhất. Vai trò chính của việc tạo ra một sự trừu tượng là bao gồm điểm chung của nhiều thứ. Nếu một sự trừu tượng chỉ được tham chiếu từ một điểm, chúng ta không thể nói về việc bao thanh toán những thứ phổ biến, một sự trừu tượng như thế này chỉ góp phần vào vấn đề yoyo. Tôi sẽ mở rộng điều này bởi vì điều này đúng với tất cả các loại trừu tượng: hàm,
tổng quát

3

Chà, không đủ trừu tượng và mã của bạn thật khó hiểu vì bạn không thể tách biệt phần nào làm gì.

Quá nhiều trừu tượng và bạn thấy sự trừu tượng nhưng không phải là mã, và sau đó làm cho việc theo dõi chuỗi thực thi thực sự khó khăn.

Để đạt được sự trừu tượng tốt, người ta nên HỎI: xem câu trả lời của tôi cho câu hỏi này để biết phải làm gì để tránh những vấn đề đó .

Tôi nghĩ rằng việc tránh phân cấp sâu và đặt tên là điểm quan trọng nhất để xem xét trường hợp bạn mô tả. Nếu các tóm tắt được đặt tên tốt, bạn sẽ không phải đi quá sâu, chỉ đến mức trừu tượng mà bạn cần hiểu điều gì xảy ra. Đặt tên cho phép bạn xác định mức độ trừu tượng này ở đâu.

Vấn đề phát sinh trong mã cấp thấp, khi bạn thực sự cần tất cả quá trình để được hiểu. Sau đó, đóng gói thông qua các mô-đun bị cô lập rõ ràng là sự giúp đỡ duy nhất.


3
Chà, không đủ trừu tượng và mã của bạn thật khó hiểu vì bạn không thể tách biệt phần nào làm gì. Đó là sự gói gọn, không trừu tượng. Bạn có thể cô lập các bộ phận trong các lớp cụ thể mà không có nhiều trừu tượng.
Tuyên bố

Các lớp không phải là trừu tượng duy nhất mà chúng ta đang sử dụng: hàm, mô-đun / thư viện, dịch vụ, v.v.
Klaim

1
@Statement: Đóng gói dữ liệu tất nhiên là một sự trừu tượng.
Ed S.

Hệ thống phân cấp không gian tên là thực sự tốt đẹp, mặc dù.
JAB

2

Đối với tôi đó là một vấn đề khớp nối và liên quan đến độ chi tiết của thiết kế. Ngay cả hình thức khớp nối lỏng lẻo nhất cũng giới thiệu sự phụ thuộc từ thứ này sang thứ khác. Nếu điều đó được thực hiện cho hàng trăm đến hàng ngàn đối tượng, ngay cả khi chúng tương đối đơn giản, tuân thủ SRP và ngay cả khi tất cả các phụ thuộc chảy vào trừu tượng ổn định, điều đó tạo ra một cơ sở mã hóa rất khó để suy luận về một tổng thể có liên quan.

Có những điều thiết thực giúp bạn đánh giá mức độ phức tạp của một cơ sở mã, không được thảo luận thường xuyên trong SE lý thuyết, như mức độ sâu của ngăn xếp cuộc gọi bạn có thể nhận được trước khi bạn kết thúc và bạn cần đi sâu đến đâu trước khi bạn có thể, với rất tự tin, hiểu tất cả các tác dụng phụ có thể xảy ra ở cấp độ đó của ngăn xếp cuộc gọi, kể cả trong trường hợp ngoại lệ.

Và tôi đã tìm thấy, theo kinh nghiệm của tôi, các hệ thống phẳng hơn với các ngăn xếp cuộc gọi nông hơn có xu hướng dễ dàng hơn nhiều để lý do. Một ví dụ cực đoan sẽ là một hệ thống thành phần thực thể trong đó các thành phần chỉ là dữ liệu thô. Chỉ có các hệ thống mới có chức năng, và trong quá trình triển khai và sử dụng ECS, tôi đã thấy nó là hệ thống dễ nhất từ ​​trước đến nay, để lý giải về việc khi nào các cơ sở mã phức tạp trải qua hàng trăm ngàn dòng mã về cơ bản đun sôi vài chục hệ thống chứa tất cả các chức năng.

Quá nhiều thứ cung cấp chức năng

Giải pháp thay thế trước đây khi tôi làm việc trong các cơ sở mã trước đó là một hệ thống có hàng trăm đến hàng ngàn vật thể nhỏ bé, trái ngược với vài chục hệ thống cồng kềnh với một số đối tượng được sử dụng chỉ để truyền thông điệp từ đối tượng này sang đối tượng khác ( Messageví dụ, có giao diện công cộng riêng). Về cơ bản, đó là những gì bạn nhận được tương tự khi bạn hoàn nguyên ECS trở lại điểm mà các thành phần có chức năng và mỗi tổ hợp thành phần duy nhất trong một thực thể mang lại loại đối tượng riêng. Và điều đó sẽ có xu hướng mang lại các chức năng nhỏ hơn, đơn giản hơn được kế thừa và cung cấp bởi sự kết hợp vô tận của các đối tượng mô hình hóa các ý tưởng tuổi teen ( Particleđối tượng vs.Physics System, ví dụ). Tuy nhiên, nó cũng có xu hướng tạo ra một biểu đồ phức tạp của các phụ thuộc lẫn nhau gây khó khăn cho việc suy luận về những gì xảy ra từ cấp độ rộng, đơn giản là vì có rất nhiều điều trong codebase thực sự có thể làm điều gì đó và do đó có thể làm điều gì đó sai - - các loại không phải là loại "dữ liệu", mà là loại "đối tượng" có chức năng liên quan. Các loại phục vụ dưới dạng dữ liệu thuần túy không có chức năng liên quan có thể có thể bị lỗi do chúng không thể tự làm bất cứ điều gì.

Các giao diện thuần túy không giúp ích gì cho vấn đề dễ hiểu này bởi vì ngay cả khi điều đó làm cho "phụ thuộc thời gian biên dịch" ít phức tạp hơn và cung cấp nhiều phòng thở hơn để thay đổi và mở rộng, thì nó cũng không làm cho "phụ thuộc thời gian chạy" và tương tác trở nên ít phức tạp hơn. Đối tượng khách hàng vẫn kết thúc việc gọi các chức năng trên một đối tượng tài khoản cụ thể ngay cả khi chúng được gọi thông qua IAccount. Đa hình và giao diện trừu tượng có công dụng của chúng nhưng chúng không tách rời mọi thứ theo cách thực sự giúp bạn suy luận về tất cả các tác dụng phụ có thể xảy ra tại bất kỳ điểm nào. Để đạt được kiểu tách rời hiệu quả này, bạn cần một cơ sở mã có ít thứ hơn có chứa chức năng.

Nhiều dữ liệu hơn, ít chức năng hơn

Vì vậy, tôi đã tìm thấy phương pháp ECS, ngay cả khi bạn không áp dụng hoàn toàn, cực kỳ hữu ích, vì nó biến hàng trăm đối tượng thành dữ liệu thô với hệ thống cồng kềnh, được thiết kế thô hơn, cung cấp tất cả chức năng. Nó tối đa hóa số loại "dữ liệu" và giảm thiểu số lượng loại "đối tượng", và do đó giảm thiểu tối đa số lượng địa điểm trong hệ thống của bạn thực sự có thể bị lỗi. Kết quả cuối cùng là một hệ thống rất "phẳng", không có biểu đồ phụ thuộc phức tạp, chỉ là hệ thống cho các thành phần, không bao giờ ngược lại và không bao giờ thành phần với các thành phần khác. Về cơ bản, đó là dữ liệu thô nhiều hơn và ít trừu tượng hơn, có tác dụng tập trung và làm phẳng chức năng của cơ sở mã cho các khu vực chính, trừu tượng hóa chính.

30 điều đơn giản hơn không nhất thiết đơn giản hơn để lý giải về hơn 1 điều phức tạp hơn, nếu 30 điều đơn giản đó có liên quan đến nhau trong khi điều phức tạp lại tự đứng vững. Vì vậy, đề nghị của tôi thực sự là chuyển sự phức tạp ra khỏi sự tương tác giữa các vật thể và nhiều hơn đối với các vật thể cồng kềnh không phải tương tác với bất kỳ thứ gì khác để đạt được sự tách rời hàng loạt, sang toàn bộ "hệ thống" (không phải là nguyên khối và vật thể thần, làm phiền bạn, và không phải các lớp với 200 phương thức, nhưng một mức độ cao hơn đáng kể so với một Messagehoặc Particlemặc dù có giao diện tối giản). Và ủng hộ các loại dữ liệu cũ đơn giản hơn. Bạn càng phụ thuộc vào những thứ đó, bạn sẽ nhận được càng ít khớp nối. Ngay cả khi điều đó mâu thuẫn với một số ý tưởng SE, tôi đã thấy nó thực sự giúp ích rất nhiều.


0

Câu hỏi của tôi là, khi một khung hoặc mẫu giới thiệu quá nhiều chi phí như thế này, nó có đáng không? Nó có phải là một triệu chứng của một mô hình được thực hiện kém?

Có lẽ đó là một triệu chứng của việc chọn ngôn ngữ lập trình sai.


1
Tôi không thấy điều này có liên quan gì đến ngôn ngữ của sự lựa chọn. Trừu tượng là một khái niệm độc lập ngôn ngữ cấp cao.
Ed S.

@Ed: Một số trừu tượng đơn giản hơn có thể thực hiện được trong một số ngôn ngữ so với các ngôn ngữ khác.
kevin cline

Đúng, nhưng điều đó không có nghĩa là bạn không thể viết một sự trừu tượng hoàn toàn có thể duy trì và dễ hiểu trong các ngôn ngữ đó. Quan điểm của tôi là câu trả lời của bạn không trả lời câu hỏi hoặc giúp OP bằng mọi cách.
Ed S.

0

Hiểu biết kém về các mẫu thiết kế có xu hướng là một nguyên nhân chính của vấn đề này. Một trong những điều tồi tệ nhất tôi từng thấy đối với việc này và nảy từ giao diện này sang giao diện khác mà không có nhiều dữ liệu cụ thể ở giữa là một phần mở rộng cho Kiểm soát lưới của Oracle.
Nó thực sự trông giống như ai đó đã có một phương pháp nhà máy trừu tượng và cực khoái mô hình trang trí trên toàn bộ mã Java của tôi. Và nó để lại cho tôi cảm giác vừa rỗng vừa cô đơn.


-1

Tôi cũng sẽ thận trọng không sử dụng các tính năng IDE giúp dễ dàng trừu tượng hóa.

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.