it-swarm-vi.com

Tôi nên cân nhắc điều gì khi các nguyên tắc DRY và KISS không tương thích?

nguyên tắc DRY đôi khi buộc các lập trình viên phải viết các hàm/lớp phức tạp, khó bảo trì. Mã như thế này có xu hướng trở nên phức tạp hơn và khó duy trì hơn theo thời gian. Vi phạm nguyên tắc KISS .

Ví dụ, khi nhiều chức năng cần phải làm một cái gì đó tương tự. Giải pháp DRY thông thường là viết một hàm lấy các tham số khác nhau để cho phép các biến thể nhỏ trong cách sử dụng.

Ưu điểm là hiển nhiên, DRY = một nơi để thực hiện thay đổi, v.v.

Nhược điểm và lý do nó vi phạm KISS là vì các hàm như thế này có xu hướng ngày càng phức tạp hơn với nhiều tham số hơn theo thời gian. Cuối cùng, các lập trình viên sẽ rất sợ thực hiện bất kỳ thay đổi nào đối với các chức năng đó hoặc chúng sẽ gây ra lỗi trong các trường hợp sử dụng khác của chức năng.

Cá nhân tôi nghĩ rằng nó có ý nghĩa để vi phạm DRY nguyên tắc để làm cho nó tuân theo KISS.

Tôi thà có 10 hàm siêu đơn giản tương tự như có một hàm siêu phức tạp.

Tôi thà làm một cái gì đó tẻ nhạt, nhưng dễ dàng (thực hiện cùng một thay đổi hoặc thay đổi tương tự ở 10 nơi), hơn là thực hiện một thay đổi rất đáng sợ/khó khăn ở một nơi.

Rõ ràng cách lý tưởng là biến nó thành KISS càng tốt mà không vi phạm DRY. Nhưng đôi khi có vẻ như không thể.

Một câu hỏi được đặt ra là "mức độ thường xuyên của mã này thay đổi?" ngụ ý rằng nếu nó thay đổi thường xuyên, thì nó phù hợp hơn để làm cho nó KHÔ. Tôi không đồng ý, vì việc thay đổi hàm phức tạp này DRY thường sẽ làm cho nó phát triển phức tạp và thậm chí còn tệ hơn theo thời gian.

Về cơ bản, tôi nghĩ, nói chung, KISS> DRY.

Bạn nghĩ sao? Trong trường hợp nào bạn nghĩ DRY phải luôn chiến thắng KISS và ngược lại? Bạn cân nhắc điều gì khi đưa ra quyết định? Làm thế nào để bạn tránh được tình huống này?

71
user158443

KISS là chủ quan. DRY rất dễ áp ​​dụng. Cả hai đều có những ý tưởng hay đằng sau chúng nhưng cả hai đều dễ lạm dụng. Chìa khóa là sự cân bằng.

KISS thực sự là trong mắt của nhóm của bạn. Bạn không biết KISS là gì. Nhóm của bạn làm. Hiển thị công việc của bạn cho họ và xem họ có nghĩ đơn giản không. Bạn là một thẩm phán kém về việc này vì bạn đã biết cách hoạt động của nó. Tìm hiểu mức độ khó của mã của bạn cho người khác đọc.

DRY không phải là về cách mã của bạn trông như thế nào. Bạn không thể phát hiện ra các vấn đề thực DRY bằng cách tìm kiếm mã giống hệt nhau. Vấn đề thực DRY có thể là bạn đang giải quyết cùng một vấn đề với giao diện hoàn toàn khác mã ở một nơi khác. Bạn không vi phạm DRY khi bạn sử dụng mã giống hệt nhau để giải quyết vấn đề khác ở một nơi khác. Tại sao? Vì các vấn đề khác nhau có thể thay đổi độc lập. và cái khác thì không.

Đưa ra quyết định thiết kế ở một nơi. Đừng lan truyền quyết định xung quanh. Nhưng đừng gấp mọi quyết định có vẻ giống nhau ngay bây giờ vào cùng một nơi. Bạn có thể có cả x và y ngay cả khi cả hai được đặt thành 1.

Với viễn cảnh này, tôi chưa bao giờ đặt KISS hoặc DRY so với bên kia. Tôi không thấy gần như căng thẳng giữa họ. Tôi bảo vệ chống lại lạm dụng Cả hai đều là những nguyên tắc quan trọng nhưng cũng không phải là viên đạn bạc.

144
candied_orange

Tôi đã viết về điều này đã có trong một nhận xét đến câu trả lời khác bởi candied_orange cho một câu hỏi tương tự và cũng có phần chạm vào nó trong một câu trả lời khác nha , nhưng nó lặp đi lặp lại:

DRY là một từ viết tắt ba chữ cái dễ thương cho một từ "Đừng lặp lại chính mình", được đặt trong cuốn sách Lập trình viên thực dụng , trong đó nó là một - toàn bộ phần 8,5 trang . Nó cũng có một giải thích và thảo luận nhiều trang trên wiki .

Định nghĩa trong cuốn sách như sau:

Mỗi phần kiến ​​thức phải có một đại diện duy nhất, rõ ràng, có thẩm quyền trong một hệ thống.

Lưu ý rằng đó là rõ ràng không về việc loại bỏ trùng lặp. Đó là về xác định trong số các bản sao đó là bản sao chính tắc. Ví dụ: nếu bạn có bộ đệm, thì bộ đệm sẽ chứa các giá trị trùng lặp với thứ khác. Tuy nhiên, phải làm rõ rằng bộ đệm là không nguồn chính tắc.

Nguyên tắc là không ba chữ DRY. Đó là khoảng 20 trang trong cuốn sách và wiki.

Nguyên tắc này cũng liên quan chặt chẽ với OAOO, đó là một từ viết tắt bốn chữ cái không dễ thương cho "Một lần và chỉ một lần", đến lượt nó là một nguyên tắc trong Lập trình eXtreme có giải thích và thảo luận nhiều trang trên wiki .

Trang wiki OAOO có một trích dẫn rất thú vị từ Ron Jeffries:

Tôi đã từng thấy Beck tuyên bố hai bản vá mã gần như hoàn toàn khác nhau là "sao chép", thay đổi chúng sao cho chúng trùng lặp và sau đó loại bỏ bản sao mới được chèn để đưa ra một cái gì đó rõ ràng tốt hơn.

Mà ông xây dựng trên:

Tôi nhớ lại một lần khi thấy Beck nhìn vào hai vòng lặp khá giống nhau: chúng có cấu trúc khác nhau và nội dung khác nhau, không có gì trùng lặp ngoại trừ từ "for", và thực tế là chúng lặp đi lặp lại - khác nhau - giống nhau bộ sưu tập.

Anh ta thay đổi vòng lặp thứ hai để lặp lại giống như cách vòng thứ nhất đã làm. Điều này đòi hỏi phải thay đổi phần thân của vòng lặp để bỏ qua các mục vào cuối bộ sưu tập, vì phiên bản trước chỉ làm phần trước của bộ sưu tập. Bây giờ các báo cáo là như nhau. "Chà, phải loại bỏ sự trùng lặp đó, anh ta nói, và di chuyển phần thứ hai vào vòng lặp thứ nhất và xóa hoàn toàn vòng lặp thứ hai.

Bây giờ anh ta có hai loại xử lý tương tự đang diễn ra trong một vòng lặp. Anh ta tìm thấy một số loại trùng lặp trong đó, trích xuất một phương pháp, làm một vài điều khác và voila! mã tốt hơn nhiều.

Bước đầu tiên đó - tạo sự trùng lặp - đã gây sửng sốt.

Điều này cho thấy: bạn có thể có bản sao mà không cần mã trùng lặp!

Và cuốn sách cho thấy mặt trái của đồng tiền:

Là một phần trong ứng dụng đặt hàng rượu trực tuyến của bạn, bạn đã nắm bắt và xác thực tuổi người dùng của bạn, cùng với số lượng họ đặt hàng. Theo chủ sở hữu trang web, cả hai nên là số và cả hai đều lớn hơn 0. Vì vậy, bạn mã lên các xác nhận:

def validate_age(value):
 validate_type(value, :integer)
 validate_min_integer(value, 0)

def validate_quantity(value):
 validate_type(value, :integer)
 validate_min_integer(value, 0)

Trong quá trình xem xét mã, cư dân biết tất cả đã trả lại mã này, cho rằng nó CÓ MỘT DRY vi phạm: cả hai cơ quan chức năng đều giống nhau.

Họ sai. Mã là như nhau, nhưng kiến ​​thức họ đại diện là khác nhau. Hai hàm xác nhận hai điều riêng biệt xảy ra có cùng quy tắc. Đó là một sự trùng hợp ngẫu nhiên, không phải là một bản sao.

Đây là mã trùng lặp không phải là trùng lặp kiến ​​thức.

Có một giai thoại tuyệt vời về sự trùng lặp dẫn đến cái nhìn sâu sắc về bản chất của ngôn ngữ lập trình: nhiều lập trình viên biết ngôn ngữ lập trình Scheme và đó là ngôn ngữ thủ tục trong họ LISP trước tiên- các thủ tục lớp và bậc cao hơn, phạm vi từ vựng, đóng từ vựng và tập trung vào các cấu trúc dữ liệu và mã minh bạch tham chiếu chức năng thuần túy. Tuy nhiên, điều mà không nhiều người biết, đó là nó được tạo ra để nghiên cứu Hệ thống diễn viên và lập trình hướng đối tượng (mà các tác giả của Đề án coi là có liên quan chặt chẽ nếu không phải là điều tương tự).

Hai trong số các thủ tục cơ bản trong Lược đồ là lambda, tạo ra một thủ tục và apply, thực thi một thủ tục. Những người tạo Scheme đã thêm hai mục nữa: alpha, tạo ra một a ctor (hoặc đối tượng) và send , sẽ gửi tin nhắn đến một diễn viên (hoặc đối tượng).

Một hậu quả khó chịu của việc có cả applysend là cú pháp thanh lịch cho các cuộc gọi thủ tục không còn hoạt động. Trong Lược đồ như chúng ta biết ngày nay (và gần như bất kỳ LISP nào), một danh sách đơn giản thường được hiểu là "diễn giải phần tử đầu tiên của danh sách thành một thủ tục và apply cho phần còn lại của danh sách, được giải thích như là đối số ". Vì vậy, bạn có thể viết

(+ 2 3)

và điều đó tương đương với

(apply '+ '(2 3))

(Hoặc một cái gì đó gần gũi, Scheme của tôi khá gỉ.)

Tuy nhiên, điều này không còn hoạt động nữa, vì bạn không biết nên apply hay đến send (giả sử rằng bạn không muốn ưu tiên một trong hai điều mà những người tạo ra Scheme đã làm 't, họ muốn cả hai mô hình đều bằng nhau). Hay, bạn có khỏe không? Những người tạo ra Scheme nhận ra rằng thực ra, họ chỉ cần kiểm tra loại đối tượng được tham chiếu bởi ký hiệu: if + là một thủ tục, bạn apply nó, nếu + là một diễn viên, bạn send một tin nhắn cho nó. Bạn thực sự không cần riêng biệt applysend, bạn có thể có một cái gì đó như apply-or-send.

Và đó là những gì họ đã làm: họ lấy mã của hai thủ tục applysend và đưa chúng vào cùng một thủ tục, như hai nhánh của một điều kiện.

Không lâu sau, họ cũng viết lại trình thông dịch Scheme, cho đến thời điểm đó đã được viết bằng ngôn ngữ hội chuyển đổi đăng ký cấp độ rất thấp cho một máy đăng ký, trong Đề án cấp cao. Và họ nhận thấy một điều đáng kinh ngạc: mã trong hai nhánh của điều kiện trở nên giống hệt nhau. Họ đã không nhận thấy điều này trước đây: hai thủ tục được viết vào những thời điểm khác nhau (họ bắt đầu với một "LISP tối thiểu" và sau đó thêm OO cho nó), và độ dài và mức độ thấp của hội có nghĩa là chúng thực sự được viết khá khác nhau, nhưng sau khi viết lại chúng bằng ngôn ngữ cấp cao, rõ ràng là chúng đã làm điều tương tự.

Điều này dẫn đến sự hiểu biết sâu sắc về Diễn viên và OO: thực hiện chương trình hướng đối tượng và thực hiện chương trình bằng ngôn ngữ thủ tục với các lần đóng từ vựng và gọi đuôi thích hợp, là điều tương tự. Sự khác biệt duy nhất là liệu nguyên thủy của ngôn ngữ của bạn là đối tượng/diễn viên hay thủ tục. Nhưng hoạt động, nó giống nhau.

Điều này cũng dẫn đến một nhận thức quan trọng khác mà không may được hiểu rõ ngay cả ngày nay: bạn không thể duy trì sự trừu tượng hướng đối tượng mà không có các cuộc gọi đuôi thích hợp, hoặc đặt mạnh hơn: một ngôn ngữ tuyên bố là hướng đối tượng nhưng không có các cuộc gọi đuôi thích hợp , không phải hướng đối tượng. (Thật không may, điều đó áp dụng cho tất cả các ngôn ngữ yêu thích của tôi và nó không mang tính học thuật: I have gặp phải vấn đề này, rằng tôi phải phá vỡ đóng gói để tránh tràn ngăn xếp.)

Đây là một ví dụ trong đó thực sự trùng lặp ẩn rất tốt bị che khuất một phần kiến ​​thức quan trọng và khám phá Sự trùng lặp này cũng tiết lộ kiến ​​thức.

39
Jörg W Mittag

Khi nghi ngờ, luôn luôn chọn giải pháp đơn giản nhất có thể giải quyết vấn đề.

Nếu hóa ra giải pháp đơn giản là quá đơn giản, nó có thể dễ dàng thay đổi. Mặt khác, một giải pháp quá phức tạp cũng khó thay đổi và rủi ro hơn.

KISS thực sự là quan trọng nhất trong tất cả các nguyên tắc thiết kế, nhưng nó thường bị bỏ qua, bởi vì văn hóa nhà phát triển của chúng tôi đặt nhiều giá trị vào việc thông minh và sử dụng các kỹ thuật ưa thích. Nhưng đôi khi một if thực sự tốt hơn một mẫu chiến lược .

Nguyên tắc DRY đôi khi buộc các lập trình viên phải viết các hàm/lớp phức tạp, khó bảo trì.

Dừng lại ngay đó! Mục đích của nguyên tắc DRY là để có được mã duy trì nhiều hơn. Nếu áp dụng nguyên tắc trong trường hợp cụ thể sẽ dẫn đến để ít hơn mã có thể duy trì, thì nguyên tắc không nên được áp dụng.

Hãy nhớ rằng không có bất kỳ nguyên tắc nào trong số này là mục tiêu trong chính họ. Mục tiêu là tạo ra phần mềm đáp ứng mục đích của nó và có thể được sửa đổi điều chỉnh và mở rộng khi cần thiết. Cả KISS, DRY, RẮN và tất cả các nguyên tắc khác là có nghĩa là để đạt được mục tiêu này. Nhưng tất cả đều có những hạn chế của chúng và có thể được áp dụng theo cách mà chúng hoạt động trái với mục tiêu cuối cùng, đó là viết phần mềm hoạt động và bảo trì.

8
JacquesB

IMHO: nếu bạn ngừng tập trung vào mã là KISS/DRY và bắt đầu tập trung vào các yêu cầu điều khiển mã, bạn sẽ tìm thấy câu trả lời tốt hơn mà bạn đang tìm kiếm.

Tôi tin:

  1. Chúng ta cần khuyến khích nhau duy trì tính thực dụng (như bạn đang làm)

  2. Chúng ta không bao giờ ngừng thúc đẩy tầm quan trọng của thử nghiệm

  3. Tập trung vào các yêu cầu nhiều hơn sẽ giải quyết các câu hỏi của bạn.

TLD

Nếu yêu cầu của bạn là có các bộ phận thay đổi độc lập, thì hãy giữ các chức năng độc lập bằng cách không có chức năng trợ giúp. Nếu yêu cầu của bạn (và bất kỳ thay đổi nào trong tương lai đối với nó) là giống nhau cho tất cả các chức năng, hãy chuyển logic đó thành chức năng trợ giúp.

Tôi nghĩ rằng tất cả các câu trả lời của chúng tôi cho đến nay tạo ra một sơ đồ Venn: tất cả chúng ta đều nói giống nhau, nhưng chúng tôi cung cấp chi tiết cho các phần khác nhau.

Ngoài ra, không ai khác đề cập đến thử nghiệm, đó là một phần lý do tại sao tôi viết câu trả lời này. Tôi nghĩ rằng nếu ai đó đề cập đến các lập trình viên sợ thực hiện thay đổi, thì sẽ rất không khôn ngoan khi không nói về thử nghiệm! Ngay cả khi chúng tôi "nghĩ" vấn đề là về mã, thì đó có thể là vấn đề thực sự là thiếu kiểm tra. Các quyết định khách quan vượt trội trở nên thực tế hơn khi mọi người đã đầu tư vào thử nghiệm tự động trước tiên.

Đầu tiên, tránh sợ hãi là sự khôn ngoan - Tốt lắm!

Đây là một câu bạn đã nói: các lập trình viên sẽ rất ngại thực hiện bất kỳ thay đổi nào đối với các hàm [trợ giúp] đó hoặc họ sẽ gây ra lỗi trong các trường hợp sử dụng khác của hàm

Tôi đồng ý rằng nỗi sợ này là kẻ thù và bạn phải không bao giờ bám vào các nguyên tắc nếu chúng chỉ gây ra sợ xếp tầng lỗi/công việc/thay đổi. Nếu sao chép/dán giữa nhiều chức năng là chỉ cách để loại bỏ nỗi sợ này (mà tôi không tin đó là - xem bên dưới), thì đó là điều bạn nên làm.

Thực tế là bạn cảm thấy sợ thay đổi này và rằng bạn đang cố gắng làm điều gì đó về nó, khiến bạn trở nên chuyên nghiệp hơn so với nhiều người khác không quan tâm đủ đến việc cải thiện mã - họ chỉ làm những gì họ nói và thực hiện các thay đổi tối thiểu để đóng vé của họ.

Ngoài ra (và tôi có thể nói rằng tôi đang lặp lại những gì bạn đã biết): kỹ năng của mọi người át chủ bài kỹ năng thiết kế. Nếu những người thực tế trong công ty của bạn hoàn toàn xấu, thì việc "lý thuyết" của bạn tốt hơn không thành vấn đề. Bạn có thể phải đưa ra quyết định tồi tệ hơn về mặt khách quan, nhưng bạn biết rằng những người sẽ duy trì nó có khả năng hiểu và làm việc cùng. Ngoài ra, nhiều người trong chúng ta cũng hiểu quản lý (IMO) quản lý vi mô cho chúng ta và tìm cách để luôn từ chối tái cấu trúc cần thiết.

Là một người bán hàng viết mã cho khách hàng, tôi phải nghĩ về điều này mọi lúc. Tôi có thể muốn sử dụng currying và lập trình meta vì có một lập luận rằng nó khách quan hơn, nhưng trong thực tế, tôi thấy mọi người bị nhầm lẫn bởi mã đó bởi vì nó không hiển nhiên rõ ràng chuyện gì đang xảy ra.

Thứ hai, kiểm tra tốt hơn giải quyết nhiều vấn đề cùng một lúc

Nếu (và chỉ khi) bạn có các bài kiểm tra tự động hiệu quả, ổn định, được chứng minh theo thời gian (đơn vị và/hoặc tích hợp), thì tôi cá là bạn sẽ thấy nỗi sợ hãi biến mất. Đối với những người mới tham gia các bài kiểm tra tự động, có thể cảm thấy rất đáng sợ khi tin tưởng vào các bài kiểm tra tự động; những người mới đến có thể thấy tất cả những chấm xanh đó và rất ít tin tưởng những chấm xanh đó phản ánh hoạt động sản xuất thực tế. Tuy nhiên, nếu bạn, cá nhân, tự tin vào các bài kiểm tra tự động, thì bạn có thể bắt đầu khuyến khích về mặt cảm xúc/quan hệ với người khác.

Đối với bạn, (nếu bạn chưa có), bước đầu tiên là nghiên cứu thực hành kiểm tra nếu bạn chưa có. Tôi thành thật cho rằng bạn đã biết những thứ này, nhưng vì tôi không thấy điều này được đề cập trong bài viết gốc của bạn, tôi phải nói về nó. Bởi vì các kiểm tra tự động điều này quan trọng và phù hợp với tình huống của bạn.

Tôi sẽ không cố gắng đơn phương thực hiện tất cả các thực hành kiểm tra trong một bài đăng ở đây, nhưng tôi sẽ thách thức bạn tập trung vào ý tưởng về các bài kiểm tra "bằng chứng tái cấu trúc". Trước khi bạn cam kết kiểm tra đơn vị/tích hợp thành mã, hãy tự hỏi liệu có cách nào hợp lệ để cấu trúc lại CUT (mã đang kiểm tra) sẽ phá vỡ bài kiểm tra bạn vừa viết không. Nếu đó là sự thật, thì (IMO) xóa bài kiểm tra đó. Tốt hơn là nên có ít bài kiểm tra tự động hơn mà không cần phải phá vỡ khi bạn tái cấu trúc, hơn là có một điều cho bạn biết rằng bạn có phạm vi kiểm tra cao (chất lượng hơn số lượng). Rốt cuộc, thực hiện tái cấu trúc dễ dàng hơn là (IMO) mục đích chính của kiểm tra tự động.

Khi tôi đã áp dụng triết lý "tái cấu trúc" này theo thời gian, tôi đã đi đến những kết luận sau:

  1. Kiểm tra tích hợp tự động tốt hơn so với kiểm tra đơn vị
  2. Đối với các bài kiểm tra tích hợp, nếu bạn cần, hãy viết "giả lập/giả mạo" bằng "kiểm tra hợp đồng"
  3. Không bao giờ kiểm tra API riêng - là các phương thức lớp riêng hoặc các hàm không được báo cáo từ một tệp.

Người giới thiệu:

Trong khi bạn đang nghiên cứu thực hành kiểm tra, bạn có thể phải dành thêm thời gian để tự viết những bài kiểm tra đó. Đôi khi, cách tiếp cận tốt nhất là không nói cho ai biết bạn đang làm điều đó, bởi vì họ sẽ quản lý bạn. Rõ ràng điều này không phải lúc nào cũng có thể bởi vì nhu cầu kiểm tra có thể lớn hơn nhu cầu cân bằng công việc/cuộc sống tốt. Nhưng, đôi khi có những thứ đủ nhỏ để bạn có thể thoát khỏi việc bí mật trì hoãn một nhiệm vụ trong một hoặc hai ngày để chỉ viết các bài kiểm tra/mã cần thiết. Điều này, tôi biết, có thể là một tuyên bố gây tranh cãi, nhưng tôi nghĩ đó là thực tế.

Ngoài ra, rõ ràng bạn có thể thận trọng về mặt chính trị nhất có thể để giúp khuyến khích người khác thực hiện các bước để tự hiểu/viết bài kiểm tra. Hoặc có thể bạn là người dẫn đầu về công nghệ có thể áp đặt quy tắc mới cho đánh giá mã.

Khi bạn nói về việc thử nghiệm với các đồng nghiệp của mình, hy vọng điểm số 1 ở trên (hãy thực dụng) nhắc nhở tất cả chúng ta hãy tiếp tục lắng nghe trước và đừng trở nên quá khích.

Thứ ba, tập trung vào các yêu cầu, không phải là mã

Quá nhiều lần chúng tôi tập trung vào mã của mình và không hiểu sâu sắc bức tranh lớn hơn mà mã của chúng tôi được cho là sẽ giải quyết! Đôi khi bạn phải ngừng tranh luận về việc liệu mã này có sạch không, và bắt đầu đảm bảo rằng bạn hiểu rõ về các yêu cầu được cho là đang lái mã.

Điều quan trọng hơn là bạn phải thực hiện đúng điều quan trọng hơn là bạn cảm thấy rằng mã của mình "đẹp" theo các ý tưởng như KISS/DRY. Đó là lý do tại sao tôi ngần ngại quan tâm đến những cụm từ bắt đó, bởi vì (trong thực tế) chúng vô tình khiến bạn tập trung vào mã của bạn mà không nghĩ về thực tế rằng yêu cầu là những gì cung cấp phán đoán tốt về chất lượng mã tốt.


Nếu các yêu cầu của hai hàm là phụ thuộc lẫn nhau, thì hãy đưa logic thực hiện của yêu cầu đó vào một hàm trợ giúp. Các đầu vào cho hàm trợ giúp đó sẽ là các đầu vào cho logic nghiệp vụ cho yêu cầu đó.

Nếu các yêu cầu của các chức năng là khác nhau, sau đó sao chép/dán giữa chúng. Nếu cả hai tình cờ có cùng một mã vào thời điểm này, nhưng could thay đổi một cách độc lập, thì hàm trợ giúp là bad vì nó ảnh hưởng đến một hàm khác có requirement là thay đổi độc lập.

Ví dụ 1: bạn có một hàm gọi là "getReportForCustomerX" và "getReportForCustomerY" và cả hai đều truy vấn cơ sở dữ liệu theo cùng một cách. Chúng ta cũng giả vờ có một yêu cầu kinh doanh trong đó mỗi khách hàng có thể tùy chỉnh báo cáo của họ theo bất kỳ cách nào họ muốn. Trong trường hợp này, theo thiết kế, khách hàng muốn các số khác nhau trong báo cáo của họ. Vì vậy, nếu bạn có một khách hàng Z mới cần báo cáo, tốt nhất có thể sao chép/dán truy vấn từ một khách hàng khác, sau đó cam kết mã và di chuyển một báo cáo. Ngay cả khi các truy vấn là chính xác giống nhau, điểm xác định của các chức năng đó là riêng biệt thay đổi từ một khách hàng tác động đến khách hàng khác. Trong trường hợp bạn cung cấp một tính năng mới mà tất cả khách hàng sẽ muốn trong báo cáo của họ, thì có: bạn có thể sẽ nhập cùng một thay đổi giữa tất cả các chức năng.

Tuy nhiên, giả sử rằng chúng tôi quyết định đi trước và tạo một hàm trợ giúp có tên queryData. Lý do tồi tệ là vì sẽ có more xếp tầng thay đổi bằng cách giới thiệu chức năng trợ giúp. Nếu có một mệnh đề "where" trong truy vấn của bạn giống nhau cho tất cả các khách hàng, thì ngay khi một khách hàng muốn một trường khác với họ, thì thay vì 1) thay đổi truy vấn bên trong hàm X, bạn phải 1 ) thay đổi truy vấn để làm những gì khách hàng X muốn 2) thêm điều kiện vào truy vấn để không làm điều đó cho người khác. Thêm nhiều điều kiện vào một truy vấn là khác nhau về mặt logic. Tôi có thể biết cách thêm một mục phụ vào truy vấn, nhưng điều đó không có nghĩa là tôi biết cách làm cho điều khoản đó có điều kiện mà không ảnh hưởng đến hiệu suất cho những người không sử dụng nó.

Vì vậy, bạn nhận thấy rằng việc sử dụng chức năng của trình trợ giúp yêu cầu hai thay đổi thay vì một. Tôi biết đây là một ví dụ giả định, nhưng sự phức tạp của Boolean để duy trì tăng trưởng nhiều hơn tuyến tính, theo kinh nghiệm của tôi. Do đó, hành động thêm các điều kiện được tính là "một điều nữa" mọi người phải quan tâm và "một điều nữa" để cập nhật mỗi lần.

Ví dụ này, nó nghe có vẻ như với tôi, có thể giống như tình huống bạn đang gặp phải. Một số người xúc động trước ý tưởng sao chép/dán giữa các chức năng này và phản ứng cảm xúc như vậy là ổn. Nhưng nguyên tắc "giảm thiểu thay đổi xếp tầng" sẽ phân biệt khách quan các ngoại lệ khi sao chép/dán là OK.

Ví dụ 2: Bạn có ba khách hàng khác nhau, nhưng điều duy nhất bạn cho phép khác nhau giữa các báo cáo của họ là tiêu đề của các cột. Lưu ý rằng tình huống này rất khác nhau. Yêu cầu kinh doanh của chúng tôi không còn là "cung cấp giá trị cho khách hàng bằng cách cho phép cạnh tranh linh hoạt trong báo cáo". Thay vào đó, yêu cầu kinh doanh là "tránh công việc dư thừa bằng cách không cho phép khách hàng tùy chỉnh báo cáo nhiều". Trong tình huống này, lần duy nhất bạn sẽ thay đổi logic truy vấn là khi bạn cũng sẽ phải đảm bảo mọi khách hàng khác đều nhận được cùng một thay đổi. Trong trường hợp này, bạn chắc chắn muốn tạo một hàm trợ giúp với một mảng làm đầu vào - "tiêu đề" dành cho các cột là gì.

Trong tương lai, nếu chủ sở hữu sản phẩm quyết định rằng họ muốn cho phép khách hàng tùy chỉnh một cái gì đó về truy vấn, thì bạn sẽ thêm nhiều cờ vào chức năng của trình trợ giúp.

Phần kết luận

Bạn càng tập trung vào các yêu cầu thay vì mã, mã sẽ càng đồng hình với các yêu cầu theo nghĩa đen. Bạn tự nhiên viết mã tốt hơn.

4
Alexander Bird

Cố gắng tìm một khu vực giữa hợp lý. Thay vì một hàm với nhiều tham số và các điều kiện phức tạp nằm rải rác trong nó, hãy chia nó thành một vài hàm đơn giản hơn. Sẽ có một số sự lặp lại trong các cuộc gọi, nhưng không nhiều như thể bạn đã không chuyển mã phổ biến sang các chức năng ở vị trí đầu tiên.

Gần đây tôi đã gặp phải vấn đề này với một số mã tôi đang làm việc để giao diện với các cửa hàng ứng dụng Google và iTunes. Phần lớn dòng chảy chung là như nhau, nhưng có đủ sự khác biệt mà tôi không thể dễ dàng viết một hàm để gói gọn mọi thứ.

Vì vậy, mã được cấu trúc như:

Google::validate_receipt(...)
    f1(...)
    f2(...)
    some google-specific code
    f3(...)

iTunes::validate_receipt(...)
    some iTunes-specific code
    f1(...)
    f2(...)
    more iTunes-specific code
    f3(...)

Tôi không quá lo lắng rằng việc gọi F1 () và f2 () trong cả hai hàm xác thực đều vi phạm nguyên tắc DRY, vì kết hợp chúng sẽ làm cho nó phức tạp hơn và không thực hiện được một định nghĩa rõ ràng nào bài tập.

3
Barmar

Kent Beck tán thành 4 quy tắc thiết kế đơn giản, liên quan đến câu hỏi này. Theo phrased của Martin Fowler, họ là:

  • Vượt qua các bài kiểm tra
  • Tiết lộ ý định
  • Không trùng lặp
  • Ít yếu tố nhất

Có rất nhiều cuộc thảo luận về thứ tự của hai người ở giữa, vì vậy có thể đáng để nghĩ về họ cũng quan trọng không kém.

DRY là yếu tố thứ ba trong danh sách và KISS có thể được coi là sự kết hợp của thứ 2 và thứ 4, hoặc thậm chí toàn bộ danh sách với nhau.

Danh sách này cung cấp một cái nhìn khác cho sự phân đôi DRY/KISS. Mã DRY của bạn có tiết lộ ý định không? Mã KISS của bạn? Bạn có thể làm cho phiên bản ether tiết lộ hơn hoặc ít bị trùng lặp hơn không?

Mục tiêu không phải là DRY hoặc KISS, đó là mã tốt. DRY, KISS và các quy tắc này chỉ là công cụ để đạt được điều đó.

3
Blaise Pascal