Thảo luận về sự đơn giản


8

Gần đây tại công ty của tôi, chúng tôi đã có một cuộc tranh luận về sự trừu tượng so với sự đơn giản. Một trường phái suy nghĩ mà tôi mô tả là "KHÔ và trừu tượng không thể làm hại", và dẫn đến mã như thế này:

def make_foo_binary(binaryName, objFiles, fooLibsToLinkAgainst)
    make_exe_task(binaryName, objFiles.ext('.o'), fooLibsToLinkAgainst)
end

và điều này:

class String
    def escape_space
        return self.gsub(' ', '\ ')
    end
end

Quan điểm của tôi là việc tạo ra một sự trừu tượng như thế này, chỉ được sử dụng ở một nơi, làm cho mã ít đọc hơn, vì bạn đang thay thế một lệnh gọi hàm mà người đọc quen thuộc với (gsub) bằng một lệnh khác mà họ chưa từng thấy trước (esc_space), mà họ sẽ cần đọc nếu họ muốn hiểu cách mã thực sự hoạt động. Sự thay thế về cơ bản được mô tả bằng tiếng Anh ("không gian thoát") và tiếng Anh nổi tiếng là mơ hồ. Ví dụ, không cần nhìn vào định nghĩa, bạn không biết liệu nó có thoát khỏi tất cả các khoảng trắng hay chỉ là ký tự khoảng trắng.

Có rất nhiều bài viết hát những lời ca ngợi về DRY và sự trừu tượng. Có ai biết các nguồn mô tả giới hạn trừu tượng không? Điều đó hát những lời khen ngợi và thảo luận về tính thực dụng của việc giữ mã đơn giản?

Chỉnh sửa: Tôi có thể tìm thấy các văn bản khuyến khích sự đơn giản trong cuộc sống hoặc bằng văn bản (tiếng Anh), ví dụ: "Đơn giản hóa, đơn giản hóa!" Của Thoreau hoặc "Bài viết mạnh mẽ của Strunk và White là súc tích." Trường hợp tương đương với lập trình?


1
Tôi khuyên bạn nên đọc "Clean Code" của Robert C. Martin, nếu bạn chưa. Nếu bạn có, hãy đọc một vài chương đầu tiên cẩn thận hơn.
Bruno Schäpper

Tôi khuyên bạn nên xem Simple Made Easy để thảo luận chuyên sâu về cách viết mã đơn giản.
dan_waterworth

3
Không phải là một fan hâm mộ của "Mã sạch". Quá giáo điều.
Giàn khoan

Câu trả lời:


9

Chắc chắn: Joel Spolsky (bạn có thể đã nghe nói về anh ta) nói :

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

Trừu tượng thất bại. Đôi khi một chút, đôi khi rất nhiều. Có rò rỉ. Mọi thứ trở nên tệ đi. Nó xảy ra ở mọi nơi khi bạn có sự trừu tượng.

Ngoài ra, hãy xem KISS và YAGNI mà Jeff Atwood thảo luận :

Là nhà phát triển, tôi nghĩ chúng ta cũng có xu hướng quá lạc quan trong việc đánh giá tính tổng quát của các giải pháp của chính mình, và do đó chúng ta kết thúc việc xây dựng [giải pháp] công phu xung quanh những thứ có thể không chứng minh mức độ phức tạp đó. Để chống lại sự thôi thúc này, tôi khuyên bạn nên tuân theo học thuyết YAGNI (You Ar't Gonna Need It). Xây dựng những gì bạn cần khi bạn cần, tích cực tái cấu trúc khi bạn đi cùng; đừng dành nhiều thời gian lên kế hoạch cho những tình huống hoành tráng, chưa biết trong tương lai. Phần mềm tốt có thể phát triển thành những gì cuối cùng nó sẽ trở thành.

(xem liên kết để báo giá chính xác)

Giải thích của tôi về những trích dẫn này:

  1. Thật sự rất khó để tạo ra sự trừu tượng tốt - việc tạo ra những thứ nhảm nhí sẽ dễ dàng hơn rất nhiều.

  2. Nếu bạn thêm một sự trừu tượng mà không có nhu cầu hoặc sai, thì bây giờ bạn có thêm mã cần được kiểm tra, gỡ lỗi và duy trì. Và nếu bạn phải quay lại và tái cấu trúc, bây giờ bạn có thêm trọng lượng chết kéo bạn xuống.


1
Nguồn tốt. +1
KChaloux

7

Tôi không quen thuộc với ngôn ngữ hoặc thời gian chạy của các ví dụ của bạn, nhưng gsubkhông có nghĩa gì với tôi. Tuy nhiên, escape_spacecó ý nghĩa hơn rất nhiều. Tôi hoàn toàn không đồng ý với lập luận của bạn rằng các cuộc gọi chức năng quen thuộc không nên được thay thế bằng các cuộc gọi đến các chức năng mà họ chưa từng thấy trước đây . Nếu tôi đưa đối số này đến cực trị, thì người ta không bao giờ nên thêm chức năng mới vào chương trình: nó luôn trừu tượng hóa các cuộc gọi chức năng quen thuộc và luôn thay thế chúng bằng cuộc gọi do người dùng xác định mới này.

Một nhà phát triển không bao giờ phải đọc mã để hiểu cách thức hoạt động của nó. Trước hết, anh ấy hoặc cô ấy không cần phải quan tâm đến cách thức hoạt động của nó, nhưng có thể hiểu cách sử dụng nó và những tác dụng của nó. Nếu đây không phải là trường hợp, thì có vấn đề với tên và chữ ký của chức năng, hoặc tài liệu của nó.

Đối với tôi, mục tiêu của trừu tượng hóa là loại bỏ các chi tiết bên trong và cung cấp giao diện sạch hơn cho một số chức năng. Trong tương lai, hoạt động bên trong của escape_spacechức năng có thể được thay đổi hoặc ghi đè. Tôi không quan tâm rằng một số chức năng gsubđược gọi với hai đối số. Tôi quan tâm rằng không gian của tôi được thoát ra. Vì vậy, làm cho sự trừu tượng hữu ích.

Trong ngôn ngữ yêu thích của tôi, C #, tôi luôn thêm tài liệu cho tất cả các chức năng của mình (thậm chí là riêng tư) mô tả những thứ như chức năng, sử dụng, đơn vị đã sử dụng, ngoại lệ, hợp đồng đầu vào và loại. Sau đó, với IntelliSense của Visual Studio, bất kỳ nhà phát triển nào cũng có thể thấy rõ hơn chức năng này làm gì mà không cần đọc mã . Nếu không có một công cụ như IntelliSense, các nhà phát triển sẽ phải cụ thể hơn trong tên hàm của họ hoặc giữ tài liệu bổ sung ở đâu đó dễ dàng truy cập.

Tôi không nghĩ sự trừu tượng bao giờ cần phải được giới hạn và tôi không biết về một nguồn như vậy.


2
Ngôn ngữ anh ta đang sử dụng là Ruby và gsub là một hàm được sử dụng rất phổ biến của lớp String. Bạn sẽ khó tìm thấy bất cứ ai viết nhiều hơn một chút Ruby mà không biết ngay rằng gsub được sử dụng như "tìm và thay thế tất cả".
KChaloux

@KChaloux Cũng có thể đoán được với những người không phải là người dùng Ruby: Bên cạnh bối cảnh (các đối số được truyền vào), có vẻ như 'g' + "thay thế" - và trong các lệnh thay thế (ví dụ: trong sedvà vim's :s), 'g' là được sử dụng như "tất cả"
Izkata

Vâng, dự đoán của tôi thực sự là nó đã thay thế một cái gì đó. Nhưng tôi không thích đoán chức năng làm gì. Nó phải là một tạo tác từ thời trước, nhưng tên chức năng như gsub, atoiwcstombsđược đặt tên tồi tệ hơn nhiều escape_space.
Daniel AA Pelsmaeker

2
@Virtlink Mặt khác, gsub là một lệnh phổ biến. Khi bạn thấy "something".gsub(' ', '\ ')bạn biết chính xác sản lượng bạn nhận được. Thoát khỏi không gian không phải là điều tôi thấy rất thường xuyên. Có phải nó đang sử dụng ký tự `\` hoặc một số ký tự thoát khác không? Liệu nó có thoát khỏi dòng mới? Chưa kể đó là một con khỉ của lớp String mặc định, được coi là thực tiễn xấu của một số nhà phát triển.
KChaloux

5

Khi nói về trừu tượng, tôi nghĩ điều quan trọng là xác định hai thuật ngữ: Sự phức tạp cố hữu và ngẫu nhiên. Sự phức tạp cố hữu là sự phức tạp của vấn đề mà sự trừu tượng đang giải quyết, sự phức tạp ngẫu nhiên là sự phức tạp mà sự trừu tượng đang che giấu.

Trừu tượng tốt là những thứ ẩn giấu rất nhiều sự phức tạp ngẫu nhiên và cố gắng giải quyết các vấn đề đúng đắn, làm giảm sự phức tạp vốn có của chúng. Tôi nghĩ những gì bạn đang nản lòng ở đây là sự trừu tượng quá nông cạn; họ không che giấu sự phức tạp ngẫu nhiên và rất khó (nếu không muốn nói là như vậy) để hiểu được sự trừu tượng cũng như hiểu được việc triển khai của họ.


2

Câu trả lời của tôi sẽ đi sâu hơn vào trải nghiệm cá nhân, nhưng một tìm kiếm nhanh đã tìm thấy blog này , có vẻ như nó đi sâu vào chi tiết khá tuyệt vời khi sự trừu tượng đi quá xa (không chỉ mục được liên kết, mà có một số mục liên quan).

Về cơ bản, trừu tượng có thể là một điều tốt. Tuy nhiên, giống như bất cứ điều gì khác, người ta có thể được mang đi với nó. Ví dụ của bạn là những cái tốt mà sự trừu tượng đã đi quá xa. Bạn đang tạo "các giao dịch" về cơ bản không làm gì ngoài việc đổi tên hàm được đề cập.

Bây giờ, trong trường hợp các chức năng tùy chỉnh, điều này có thể là cần thiết, nếu bạn đang muốn loại bỏ sơ đồ đặt tên cũ theo hướng mới (chẳng hạn như thay đổi esc_space thành escSpace hoặc escWhitespace, hoặc bất cứ điều gì). Đối với chức năng thư viện, mặc dù? Quá mức cần thiết. Chưa kể phản tác dụng. Bạn đã đạt được gì khi thực hiện sự trừu tượng đó? Nếu đó là tất cả những gì nó làm, thì tất cả những gì bạn có được là đau đầu khi phải nhớ chức năng đó và truyền kiến ​​thức đó cho người khác (trong khi đó, với các chức năng của thư viện, các nhà phát triển đã biết về chúng hoặc có thể nhập ruby gsubvào Google và tìm hiểu ý nghĩa của nó).

Tôi khuyên bạn nên thông minh hơn về việc xác định khi nào nên trừu tượng hóa một cái gì đó. Ví dụ: những thứ có nhiều hơn 2-3 dòng và được sử dụng ít nhất hai lần (ngay cả khi có sự khác biệt nhỏ, rất nhiều lần, những khác biệt đó có thể được tham số hóa), nói chung là những ứng cử viên trừu tượng tốt. Đôi khi, tôi sẽ xem xét các khối mã lớn làm một việc cụ thể trong một hàm lớn hơn, vì mục đích dễ đọc và nguyên tắc "một công việc" và trừu tượng hóa chúng thành các hàm được bảo vệ trong cùng một lớp (thường là ngay lập tức vùng lân cận của hàm sử dụng nó).

Ngoài ra, đừng quên YAGNI (bạn sẽ không cần nó) và tối ưu hóa sớm. Đây có phải là hiện đang được sử dụng nhiều hơn một lần? Không? Sau đó, đừng lo lắng về việc trừu tượng hóa nó ngay bây giờ (trừ khi làm như vậy sẽ tạo ra mức tăng khả năng đọc đáng kể). "Nhưng chúng ta có thể cần nó sau!" Sau đó trừu tượng hóa nó khi bạn thực sự cần nó ở một nơi khác. Cho đến lúc đó, bạn chỉ kết thúc với con trỏ đến con trỏ đến con trỏ không có lợi ích thực sự.

Mặt khác của đồng tiền này, khi bạn thực hiện một cái gì đó trừu tượng, hãy làm điều đó ngay khi nhu cầu trở nên rõ ràng. Việc di chuyển và điều chỉnh hai hoặc ba khối mã dễ dàng hơn 15. Nói chung, ngay khi tôi thấy mình sao chép các khối mã từ nơi khác hoặc viết một cái gì đó trông rất quen thuộc, tôi bắt đầu nghĩ về cách trừu tượng hóa nó.


2
Vài năm trước tôi đã hoàn toàn đồng ý với bạn và làm việc theo cách bạn mô tả nó. Ở giữa, tôi đọc một số điều của Robert Martin và bắt đầu suy nghĩ lại về cách lập trình của tôi. Xây dựng các hàm rất nhỏ với tên hay, ngay cả khi hàm được sử dụng một lần, có một lợi thế thực sự: bạn cần ít bình luận hơn. Và nó giúp bạn giữ cho người gọi của chức năng đó đơn giản và dễ đọc hơn. Đó chắc chắn không phải là "tối ưu hóa sớm" (Knuth đã nói về hiệu suất, không phải chất lượng mã). YAGNI nên được xem xét, tất nhiên, nhưng các công cụ tái cấu trúc tốt đã thực hiện những thay đổi như vậy rất dễ dàng ngày nay.
Doc Brown

1
@DocBrown - Tôi nghĩ chúng tôi có thể đồng ý nhiều hơn bạn nghĩ. Chúng ta có thể nói chuyện hàng giờ hoặc hàng ngày về những ưu và nhược điểm của sự trừu tượng. Tôi đã cố gắng ngắn gọn hơn thế, nhưng tôi đã chạm vào mối quan tâm của bạn khi tôi đề cập đến tính dễ đọc và "một công việc" / nguyên tắc trách nhiệm duy nhất. Trường hợp tôi thường vẽ đường là khi một hàm chỉ đơn giản là chuyển qua cho một hàm khác (mà trong tôi, trong hầu hết các trường hợp, là trừu tượng vì lợi ích trừu tượng).
Shauna

2

Đầu tiên, hãy xem ví dụ này của Robert C. Martin:

http://blog.objectmentor.com/articles/2009/09/11/one-thing-extract-till-you-drop

Lý do cho kiểu tái cấu trúc này là do xây dựng các khái niệm trừu tượng để tạo ra các hàm rất nhỏ, để mọi hàm chỉ làm một việc . Điều đó dẫn đến các chức năng như những người bạn đã chỉ cho chúng tôi ở trên. Tôi tin rằng đây là một điều tốt - nó giúp bạn tạo mã trong đó mỗi hàm được gọi trong một hàm khác có cùng mức độ trừu tượng.

Nếu bạn nghĩ rằng việc sử dụng tên như make_foo_binaryhoặc escape_spacelàm cho mã ít đọc hơn, do đó, bạn nên thử chọn tên tốt hơn (ví dụ escape_space_chars_with_backslash:) thay vì từ bỏ tính trừu tượng.

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.