Có thể áp dụng DRY mà không tăng khớp nối?


14

Giả sử chúng ta có một mô-đun phần mềm A thực hiện một chức năng F. Một mô-đun B khác thực hiện chức năng tương tự như F '.

Có một số cách để loại bỏ mã trùng lặp:

  1. Cho A sử dụng F 'từ B.
  2. Cho B sử dụng F từ A.
  3. Đặt F vào mô-đun C của chính nó và cho cả A và B sử dụng nó.

Tất cả các tùy chọn này tạo ra sự phụ thuộc bổ sung giữa các mô-đun. Họ áp dụng nguyên tắc DRY với chi phí tăng khớp nối.

Theo như tôi có thể thấy, khớp nối luôn tăng hoặc ở mức cho thuê được chuyển lên mức cao hơn khi áp dụng DRY. Dường như có một mâu thuẫn giữa hai nguyên tắc cơ bản nhất của thiết kế phần mềm.

(Trên thực tế tôi không thấy ngạc nhiên khi có những xung đột như vậy. Đây có lẽ là điều khiến cho việc thiết kế phần mềm tốt trở nên khó khăn. Tôi thấy đáng ngạc nhiên là những xung đột này thường không được giải quyết trong các văn bản giới thiệu.)

Chỉnh sửa (để làm rõ): Tôi cho rằng sự bình đẳng của F và F 'không chỉ là sự trùng hợp ngẫu nhiên. Nếu F sẽ phải được sửa đổi, F 'có thể sẽ phải được sửa đổi theo cùng một cách.


2
... Tôi nghĩ DRY có thể là một chiến lược rất hữu ích, nhưng câu hỏi này minh họa cho sự không hiệu quả với DRY. Một số (ví dụ, những người đam mê OOP) có thể lập luận rằng bạn nên sao chép / dán F vào B chỉ vì mục đích duy trì quyền tự chủ về khái niệm của A và B nhưng tôi đã không gặp phải tình huống tôi sẽ làm điều đó. Tôi nghĩ rằng mã sao chép / dán chỉ là lựa chọn tồi tệ nhất, tôi không thể chịu được cảm giác "mất trí nhớ ngắn hạn" mà tôi chắc chắn rằng tôi đã viết một phương thức / chức năng để làm một cái gì đó; sửa một lỗi trong một chức năng và quên cập nhật một chức năng khác có thể là một vấn đề lớn khác.
jrh

3
Có những nguyên tắc của nguyên tắc OO này mâu thuẫn với nhau. Trong hầu hết các trường hợp, bạn phải tìm một sự đánh đổi hợp lý. Nhưng IMHO nguyên tắc DRY là có giá trị nhất. Như @jrh đã viết: có cùng một hành vi được thực hiện ở nhiều nơi là một cơn ác mộng bảo trì nên tránh bằng mọi giá. Phát hiện ra rằng bạn quên cập nhật một trong những bản sao dư thừa trong sản xuất có thể làm giảm doanh nghiệp của bạn.
Timothy Truckle

2
@TimothyTruckle: Chúng ta không nên gọi chúng là các nguyên tắc OO vì chúng cũng áp dụng cho các mô hình lập trình khác. Và vâng, DRY có giá trị nhưng cũng nguy hiểm nếu dùng quá liều. Không chỉ bởi vì nó có xu hướng tạo ra sự phụ thuộc và do đó phức tạp. Nó cũng thường được áp dụng cho các bản sao gây ra bởi sự trùng hợp ngẫu nhiên có lý do khác nhau để thay đổi.
Frank Puffer

1
... đôi khi, khi tôi gặp phải tình huống này, tôi đã có thể chia F thành các phần có thể sử dụng được cho những gì A cần và những gì B cần.
jrh

1
Khớp nối vốn không phải là một điều xấu, và thường là cần thiết để giảm lỗi và tăng năng suất. Nếu bạn sử dụng hàm parseInt từ thư viện chuẩn của ngôn ngữ trong hàm của bạn, bạn sẽ ghép chức năng của mình với thư viện chuẩn. Tôi đã không thấy một chương trình không làm điều này trong nhiều năm. Điều quan trọng là không tạo ra các khớp nối không cần thiết. Thông thường, một giao diện được sử dụng để tránh / loại bỏ khớp nối như vậy. Chẳng hạn, hàm của tôi có thể chấp nhận triển khai parseInt làm đối số. Tuy nhiên, điều này không phải lúc nào cũng cần thiết, cũng không phải lúc nào cũng khôn ngoan.
Joshua Jones

Câu trả lời:


14

Tất cả các tùy chọn này tạo ra sự phụ thuộc bổ sung giữa các mô-đun. Họ áp dụng nguyên tắc DRY với chi phí tăng khớp nối.

Tại sao có họ làm. Nhưng họ giảm khớp nối giữa các dòng. Những gì bạn nhận được là sức mạnh để thay đổi khớp nối. Khớp nối có nhiều hình thức. Trích xuất mã làm tăng sự gián tiếp và trừu tượng. Tăng mà có thể là tốt hoặc xấu. Điều số một quyết định cái bạn nhận được là tên bạn sử dụng cho nó. Nếu nhìn vào cái tên khiến tôi ngạc nhiên khi tôi nhìn vào bên trong thì bạn đã không làm bất cứ ai ủng hộ.

Ngoài ra, không theo DRY trong chân không. Nếu bạn giết chết sự trùng lặp, bạn có trách nhiệm dự đoán rằng hai cách sử dụng mã đó sẽ thay đổi cùng nhau. Nếu họ có khả năng thay đổi một cách độc lập, bạn đã gây nhầm lẫn và làm thêm vì lợi ích ít. Nhưng một cái tên thực sự tốt có thể làm cho điều đó trở nên ngon miệng hơn. Nếu tất cả những gì bạn có thể nghĩ là một tên xấu thì xin vui lòng, hãy dừng lại ngay bây giờ.

Khớp nối sẽ luôn tồn tại trừ khi hệ thống của bạn bị cô lập đến mức không ai biết liệu nó có hoạt động không. Vì vậy, tái cấu trúc khớp nối là một trò chơi chọn chất độc của bạn. Theo DRY có thể trả hết bằng cách giảm thiểu khớp nối được tạo bằng cách thể hiện cùng một quyết định thiết kế ở nhiều nơi cho đến khi rất khó thay đổi. Nhưng DRY có thể làm cho không thể hiểu mã của bạn. Cách tốt nhất để cứu vãn tình huống đó là tìm một cái tên thực sự tốt. Nếu bạn không thể nghĩ ra một cái tên hay, tôi hy vọng bạn có kỹ năng tránh những cái tên vô nghĩa


Để chắc chắn rằng tôi hiểu chính xác quan điểm của bạn, hãy để tôi nói khác đi một chút: Nếu bạn gán tên tốt cho mã được trích xuất, mã được trích xuất không còn phù hợp để hiểu phần mềm vì tên này nói lên tất cả (lý tưởng). Khớp nối vẫn tồn tại ở cấp độ kỹ thuật nhưng không ở cấp độ nhận thức. Do đó, nó tương đối vô hại, phải không?
Frank Puffer

1
Nevermind, xấu của tôi: meta.stackexchange.com/a/263672/143358
Basilevs

@FrankPuffer tốt hơn?
candied_orange

3

Có nhiều cách để phá vỡ sự phụ thuộc rõ ràng. Một phổ biến là tiêm phụ thuộc trong thời gian chạy. Bằng cách này, bạn có được DRY, loại bỏ khớp nối với chi phí an toàn tĩnh. Ngày nay nó phổ biến đến mức mọi người thậm chí không hiểu, đó là một sự đánh đổi. Ví dụ, các thùng chứa ứng dụng thường xuyên cung cấp phần mềm quản lý phụ thuộc vô cùng phức tạp bằng cách che giấu sự phức tạp. Ngay cả tiêm xây dựng cũ đơn giản không đảm bảo một số hợp đồng do thiếu hệ thống loại.

Để trả lời tiêu đề - có, có thể, nhưng hãy chuẩn bị cho hậu quả của việc gửi thời gian chạy.

  • Xác định giao diện F A trong A, cung cấp chức năng của F
  • Xác định giao diện F B trong B
  • Đặt F vào C
  • Tạo mô-đun D để quản lý tất cả các phụ thuộc (nó phụ thuộc vào A, B và C)
  • Thích ứng F với F A và F B trong D
  • Tiêm (vượt qua) các hàm bao đến A và B

Theo cách này, loại phụ thuộc duy nhất bạn sẽ có là D phụ thuộc vào từng mô-đun khác.

Hoặc đăng ký C trong vùng chứa ứng dụng với tính năng tiêm phụ thuộc tích hợp và tận hưởng vận may của việc tự động tải các vòng lặp tải lớp thời gian chạy chậm và các khóa chết.


1
+1, nhưng với sự cảnh báo rõ ràng rằng đây thường là một sự đánh đổi tồi tệ. Sự an toàn tĩnh đó là để bảo vệ bạn, và phá vỡ nó bằng một loạt các hành động ma quái ở xa chỉ là yêu cầu các lỗi khó theo dõi sâu hơn nữa khi sự phức tạp trong dự án của bạn tăng lên một chút ...
Mason Wheeler

1
DI thực sự có thể được nói để phá vỡ sự suy giảm? Ngay cả chính thức, bạn cần một giao diện với chữ ký của F để thực hiện nó. Tuy nhiên, nếu mô-đun A sử dụng F từ C, nó sẽ thay thế nó, bất kể C được tiêm vào thời gian chạy hay liên kết trực tiếp .DI không phá vỡ sự phụ thuộc, lỗi chỉ trì hoãn lỗi nếu không cung cấp phụ thuộc
max630

@ max630 nó thay thế sự phụ thuộc thực hiện bằng hợp đồng, yếu hơn.
Basilevs

@ max630 Bạn đã đúng. DI không thể nói để phá vỡ một phụ thuộc. Trên thực tế, DI là một phương pháp giới thiệu các phụ thuộc và thực sự trực giao với câu hỏi được hỏi liên quan đến khớp nối. Một thay đổi trong F (hoặc giao diện đóng gói nó) vẫn sẽ yêu cầu thay đổi cả A và B.
vua trượt bên

1

Tôi không chắc rằng một câu trả lời mà không có ngữ cảnh xa hơn có ý nghĩa.

Liệu Ađã phụ thuộc vào Bhoặc ngược lại? - trong trường hợp đó chúng ta có thể có một sự lựa chọn rõ ràng về nhà cho F.

Làm ABđã chia sẻ bất kỳ phụ thuộc phổ biến có thể là một ngôi nhà tốt cho F?

Làm thế nào lớn / phức tạp là F? Những gì khác không Fphụ thuộc vào?

Là các mô-đun ABđược sử dụng trong cùng một dự án?

Sẽ ABcuối cùng chia sẻ một số phụ thuộc phổ biến nào?

Hệ thống ngôn ngữ / mô-đun nào đang được sử dụng: Làm thế nào đau đớn là một mô-đun mới, trong nỗi đau của lập trình viên, trong chi phí hoạt động? Ví dụ: nếu bạn đang viết bằng C / C ++ với hệ thống mô-đun là COM, gây đau cho mã nguồn, yêu cầu công cụ thay thế, có ý nghĩa về gỡ lỗi và có hàm ý hiệu suất (đối với các yêu cầu liên mô-đun), tôi có thể tạm dừng nghiêm túc

Mặt khác, nếu bạn đang nói về các tệp Java hoặc C # kết hợp khá liền mạch trong một môi trường thực thi duy nhất, thì đó lại là một vấn đề khác.


Một chức năng là một sự trừu tượng và hỗ trợ DRY.

Tuy nhiên, trừu tượng tốt cần phải được hoàn thành - trừu tượng không đầy đủ rất có thể khiến khách hàng tiêu dùng (lập trình viên) bù đắp sự thiếu hụt bằng cách sử dụng kiến ​​thức về triển khai cơ bản: điều này dẫn đến kết hợp chặt chẽ hơn so với việc trừu tượng hóa được cung cấp thay vì hoàn thiện hơn.

Vì vậy, tôi sẽ tranh luận để tìm cách tạo ra một sự trừu tượng hóa tốt hơn ABphụ thuộc vào việc thay vì chỉ đơn giản là chuyển một chức năng duy nhất vào một mô-đun mới C

Tôi đang tìm kiếm một tập hợp các hàm để trêu chọc một sự trừu tượng mới, nghĩa là, tôi có thể đợi cho đến khi cơ sở mã được tiếp tục để xác định một phép tái cấu trúc đầy đủ / hoàn chỉnh hơn để thực hiện thay vì dựa trên một trên một mã chức năng duy nhất cho biết.


2
Nó không nguy hiểm đối với cặp trừu tượng và biểu đồ phụ thuộc?
Basilevs

Does A already depend on B or vice versa? — in which case we might have an obvious choice of home for F.Điều này giả định rằng A sẽ luôn dựa vào B (hoặc ngược lại), đây là một giả định rất nguy hiểm. Việc OP thấy F không phải là một phần của A (hoặc B) cho thấy F tồn tại mà không phải là vốn có của một thư viện. Nếu F thuộc về một thư viện (ví dụ: phương thức mở rộng DbContext (F) và thư viện trình bao bọc Entity Framework (A hoặc B)), thì câu hỏi của OP sẽ được đưa ra.
Flater

0

Các câu trả lời ở đây tập trung vào tất cả các cách bạn có thể tối thiểu hóa vấn đề này, vấn đề này đang làm bạn trở thành một kẻ bất đồng. Và các giải pháp của MIT chỉ đơn giản là cung cấp các cách khác nhau để tạo ra khớp nối hoàn toàn không phải là giải pháp.

Sự thật là không hiểu chính vấn đề bạn đã tạo ra. Vấn đề với ví dụ của bạn không liên quan gì đến DRY, thay vào đó (rộng hơn) với thiết kế ứng dụng.

Hãy tự hỏi tại sao các mô-đun A và B tách biệt nếu cả hai đều phụ thuộc vào cùng một chức năng F? Tất nhiên bạn sẽ gặp vấn đề với quản lý phụ thuộc / trừu tượng / khớp nối / bạn đặt tên cho nó nếu bạn cam kết thiết kế kém.

Mô hình ứng dụng thích hợp được thực hiện theo hành vi. Như vậy, các phần của A và B phụ thuộc vào F cần được trích xuất thành mô-đun độc lập của riêng chúng. Nếu điều này là không thể, thì A và B cần được kết hợp. Trong cả hai trường hợp, A và B không còn hữu ích cho hệ thống và sẽ không còn tồn tại.

DRY là một hiệu trưởng có thể được sử dụng để phơi bày thiết kế kém, không gây ra nó. Nếu bạn không thể đạt được DRY ( khi nó thực sự áp dụng - lưu ý chỉnh sửa của bạn) do cấu trúc của ứng dụng của bạn, đó là một dấu hiệu rõ ràng rằng cấu trúc đã trở thành một trách nhiệm pháp lý. Đây là lý do tại sao, tái cấu trúc liên tục, cũng là một hiệu trưởng cần tuân thủ.

Của gốc khác thiết kế (RẮN, khô, vv) ra khỏi đó được ABC tất cả sử dụng để làm thay đổi (bao gồm cả refactoring) một ứng dụng không đau hơn. Tập trung vào đó , và tất cả các vấn đề khác bắt đầu biến mất.


Bạn có đề nghị có chính xác một mô-đun trong mỗi ứng dụng không?
Basilevs

@Basilevs Hoàn toàn không (trừ khi nó được bảo hành). Tôi đề nghị có càng nhiều mô-đun tách rời hoàn toàn càng tốt. Rốt cuộc, đó là toàn bộ mục đích của một mô-đun.
vua trượt bên

Vâng, câu hỏi ngụ ý các mô-đun A và B có chứa các hàm không liên quan và đã được trích xuất tương ứng, tại sao và làm thế nào để chúng ngừng tồn tại? Giải pháp của bạn cho vấn đề như đã nêu là gì?
Basilevs

@Basilevs Câu hỏi ngụ ý A và B chưa được mô hình hóa đúng. Chính sự thiếu hụt vốn có này đang gây ra vấn đề ngay từ đầu. Chắc chắn thực tế đơn giản rằng họ làm tồn tại không phải là bằng chứng cho thấy họ nên tồn tại. Đó là điểm tôi đang làm ở trên. Rõ ràng, một thiết kế thay thế là cần thiết để tránh phá vỡ DRY. Điều quan trọng là phải hiểu rằng mục đích chính của tất cả các hiệu trưởng thiết kế của thành phố này là làm cho ứng dụng dễ thay đổi hơn.
vua trượt bên

Là một tấn các phương pháp khác, với các phụ thuộc hoàn toàn khác nhau được mô hình hóa xấu và phải được làm lại, chỉ vì một phương pháp không tình cờ này? Hoặc bạn có cho rằng, các mô-đun A và B chứa một phương thức duy nhất không?
Basilevs

0

Tất cả các tùy chọn này tạo ra sự phụ thuộc bổ sung giữa các mô-đun. Họ áp dụng nguyên tắc DRY với chi phí tăng khớp nối.

Tôi có ý kiến ​​khác, ít nhất là cho lựa chọn thứ ba:

Từ mô tả của bạn:

  • A cần F
  • B cần F
  • Cả A và B đều không cần nhau.

Đưa F vào mô-đun C sẽ không tăng khả năng ghép vì cả A và B đều cần tính năng của C.

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.