Sao chép mã ảo tưởng


56

Bản năng thông thường là loại bỏ bất kỳ sự trùng lặp mã nào mà bạn thấy trong mã. Tuy nhiên, tôi thấy mình trong một tình huống mà sự trùng lặp là ảo tưởng .

Để mô tả tình huống chi tiết hơn: Tôi đang phát triển một ứng dụng web và hầu hết các chế độ xem đều giống nhau - chúng hiển thị danh sách các mục mà người dùng có thể cuộn và chọn, danh sách thứ hai chứa các mục đã chọn và "Lưu "Để lưu danh sách mới.

Dường như với tôi rằng vấn đề là dễ dàng. Tuy nhiên, mỗi chế độ xem có một quirks riêng - đôi khi bạn cần tính toán lại một cái gì đó, đôi khi bạn phải lưu trữ một số dữ liệu bổ sung, v.v. Những điều này, tôi đã giải quyết bằng cách chèn các móc gọi lại trong mã logic chính.

rất nhiều sự khác biệt nhỏ giữa các khung nhìn mà nó ngày càng trở nên ít bảo trì hơn, bởi vì tôi cần cung cấp các cuộc gọi lại về cơ bản cho tất cả các chức năng, và logic chính bắt đầu giống như một chuỗi các lệnh gọi lại rất lớn. Cuối cùng, tôi không tiết kiệm bất kỳ thời gian hay mã nào, bởi vì mọi chế độ xem đều có mã riêng được thực thi - tất cả trong các cuộc gọi lại.

Các vấn đề là:

  • sự khác biệt rất nhỏ đến mức mã trông gần như giống hệt nhau trong tất cả các khung nhìn,
  • rất nhiều sự khác biệt mà khi bạn nhìn vào các chi tiết, để mã không giống nhau một chút

Tôi nên xử lý tình huống này như thế nào?
Là có logic cốt lõi bao gồm hoàn toàn các cuộc gọi lại là một giải pháp tốt?
Hay tôi nên sao chép mã và loại bỏ sự phức tạp của mã dựa trên cuộc gọi lại?


38
Tôi thường thấy hữu ích khi để cho sự trùng lặp ban đầu. Khi tôi có một vài ví dụ, sẽ dễ dàng hơn để thấy những gì phổ biến và những gì không và đưa ra cách chia sẻ những phần chung.
Winston Ewert

7
Câu hỏi rất hay - một điều cần xem xét không chỉ là sự trùng lặp vật lý của mã mà là sự trùng lặp về ngữ nghĩa. Nếu một thay đổi trong một đoạn mã nhất thiết có nghĩa là cùng một thay đổi sẽ được nhân đôi trên các đoạn khác, thì phần đó có thể là một ứng cử viên để tái cấu trúc hoặc trừu tượng hóa. Đôi khi bạn có thể bình thường hóa đến mức bạn thực sự tự nhốt mình, vì vậy tôi cũng sẽ xem xét ý nghĩa thực tế của việc coi sự trùng lặp là khác biệt về mặt ngữ nghĩa - chúng có thể bị ảnh hưởng bởi hậu quả của việc cố gắng lặp lại.
Ant P

Hãy nhớ rằng, bạn chỉ có thể sử dụng lại mã làm điều tương tự. Nếu ứng dụng của bạn thực hiện những điều khác nhau trên các màn hình khác nhau, nó sẽ yêu cầu các cuộc gọi lại khác nhau. Không IFS, ands, hoặc buts về nó.
corsiKa

13
Nguyên tắc cá nhân của tôi về điều này là: Nếu tôi thực hiện thay đổi mã ở một nơi, đó có phải là lỗi không nếu tôi không thực hiện cùng một thay đổi chính xác ở mọi nơi khác? Nếu vậy, đó là loại trùng lặp xấu. Nếu tôi không chắc chắn, hãy đi với bất cứ nơi nào dễ đọc hơn bây giờ. Trong ví dụ của bạn, sự khác biệt trong hành vi là có chủ ý và không được coi là lỗi, do đó một số sao chép là ổn.
Ixrec

Bạn có thể thích đọc về Lập trình hướng theo khía cạnh.
Ben Jackson

Câu trả lời:


53

Cuối cùng, bạn phải thực hiện một cuộc gọi phán xét về việc có nên kết hợp mã tương tự để loại bỏ trùng lặp hay không.

Dường như có một xu hướng đáng tiếc để thực hiện các nguyên tắc như "Đừng lặp lại chính mình" như các quy tắc phải được tuân theo bởi vẹt mọi lúc. Trên thực tế, đây không phải là các quy tắc phổ quát mà là các hướng dẫn sẽ giúp bạn suy nghĩ và phát triển thiết kế tốt.

Như mọi thứ trong cuộc sống, bạn phải xem xét lợi ích so với chi phí. Bao nhiêu mã trùng lặp sẽ được gỡ bỏ? Mã lặp đi lặp lại bao nhiêu lần? Sẽ có bao nhiêu nỗ lực để viết một thiết kế chung chung hơn? Bao nhiêu bạn có khả năng phát triển mã trong tương lai? Và như vậy.

Không biết mã cụ thể của bạn, điều này là không rõ ràng. Có lẽ có một cách thanh lịch hơn để loại bỏ sự trùng lặp (chẳng hạn như đề xuất của LindaJeanne). Hoặc, có lẽ đơn giản là không đủ sự lặp lại thực sự để đảm bảo sự trừu tượng.

Sự quan tâm không đầy đủ đến thiết kế là một cạm bẫy, nhưng cũng hãy cẩn thận với thiết kế quá mức.


Nhận xét của bạn về "xu hướng không may" và theo hướng dẫn mù quáng là tại chỗ, tôi nghĩ.
Mael

1
@Mael Bạn có nói rằng nếu bạn không duy trì mã này trong tương lai, bạn sẽ không có lý do chính đáng để có được thiết kế phù hợp? (không xúc phạm, chỉ muốn biết bạn nghĩ gì về điều đó)
Phát hiện

2
@Mael Tất nhiên chúng ta có thể coi đó chỉ là một cụm từ đáng tiếc! : D Tuy nhiên, tôi nghĩ chúng ta nên nghiêm khắc với chính mình như đối với những người khác khi viết mã (tôi coi mình như một người khác khi tôi đọc mã của chính mình 2 tuần sau khi viết nó).
Phát hiện

2
@ user61852 thì bạn sẽ không thích Bộ luật không mã .
RubberDuck

1
@ user61852, haha - nhưng những gì nếu nó không tất cả phụ thuộc (vào các thông tin không được đưa ra trong câu hỏi)? Vài điều ít hữu ích hơn sự chắc chắn dư thừa.

43

Hãy nhớ rằng DRY là về kiến ​​thức . Không có vấn đề gì nếu hai đoạn mã trông giống nhau, giống hệt nhau hoặc hoàn toàn khác nhau, điều quan trọng là nếu cùng một kiến thức về hệ thống của bạn có thể được tìm thấy trong cả hai.

Một phần kiến ​​thức có thể là một thực tế ("độ lệch tối đa được phép so với giá trị dự định là 0,1%") hoặc nó có thể là một khía cạnh nào đó trong quy trình của bạn ("hàng đợi này không bao giờ chứa nhiều hơn ba mục"). Về cơ bản, đó là bất kỳ mẩu thông tin nào được mã hóa trong mã nguồn của bạn.

Vì vậy, khi bạn quyết định liệu có thứ gì đó bị trùng lặp hay không, hãy hỏi xem đó có phải là sự trùng lặp về kiến ​​thức không. Nếu không, có thể là sự trùng lặp ngẫu nhiên và trích xuất nó đến một nơi phổ biến sẽ gây ra vấn đề khi sau này bạn muốn tạo một thành phần tương tự trong đó phần trùng lặp rõ ràng là khác nhau.


12
Điều này! Trọng tâm của DRY là tránh những thay đổi trùng lặp .
Matthieu M.

Điều này khá hữu ích.

1
Tôi nghĩ trọng tâm của DRY là để đảm bảo rằng không có hai bit mã mà nên cư xử giống hệt nhau nhưng không có. Vấn đề không phải là công việc nhân đôi vì thay đổi mã phải được áp dụng hai lần, vấn đề thực sự là khi thay đổi mã cần được áp dụng hai lần nhưng không được.
gnasher729

3
@ gnasher729 Đúng, đó là điểm. Nếu hai đoạn mã có sự trùng lặp về kiến ​​thức , thì bạn sẽ mong đợi rằng khi một thứ cần thay đổi, thì cái kia cũng sẽ cần thay đổi, dẫn đến vấn đề bạn mô tả. Nếu chúng có sự trùng lặp ngẫu nhiên , thì khi một người cần thay đổi, người kia cũng có thể cần giữ nguyên. Trong trường hợp đó nếu bạn đã trích xuất một phương thức phổ biến (hoặc bất cứ điều gì), bây giờ bạn có một vấn đề khác cần giải quyết
Ben Aaronson

1
Ngoài ra sự trùng lặp thiết yếutrùng lặp ngẫu nhiên , hãy xem Một Doppelgänger tình cờ trong Rubytôi DRY-ed Up My Code và Bây giờ thật khó để làm việc. Chuyện gì đã xảy ra? . Sự trùng lặp ngẫu nhiên cũng xảy ra ở cả hai phía của một ranh giới bối cảnh . Tóm tắt: chỉ hợp nhất các bản sao nếu nó có ý nghĩa đối với các máy khách của chúng để các phụ thuộc này được sửa đổi đồng thời .
Eric

27

Bạn đã cân nhắc sử dụng mẫu Chiến lược chưa? Bạn sẽ có một lớp View chứa mã & thói quen chung được gọi bởi một số khung nhìn. Trẻ em của lớp View sẽ chứa mã cụ thể cho các trường hợp đó. Tất cả đều sẽ sử dụng giao diện chung mà bạn đã tạo cho Chế độ xem và do đó, sự khác biệt sẽ được gói gọn và kết hợp.


5
Không, tôi đã không xem xét nó. Cảm ơn vì đã góp ý. Từ một bài đọc nhanh về mẫu Chiến lược, có vẻ như thứ gì đó tôi đang tìm kiếm. Tôi chắc chắn sẽ điều tra thêm.
Mael

3
mẫu phương thức mẫu . Bạn cũng có thể xem xét điều đó
Shakil

5

Tiềm năng thay đổi là gì? Ví dụ: ứng dụng của chúng tôi có 8 lĩnh vực kinh doanh khác nhau với tiềm năng từ 4 loại người dùng trở lên cho mỗi khu vực. Lượt xem được tùy chỉnh dựa trên loại người dùng và khu vực.

Ban đầu, điều này được thực hiện bằng cách sử dụng cùng một chế độ xem với một vài kiểm tra ở đây và ở đó để xác định xem những thứ khác nhau có nên hiển thị hay không. Theo thời gian, một số lĩnh vực kinh doanh đã quyết định làm những việc khác nhau mạnh mẽ. Cuối cùng, về cơ bản, chúng tôi đã chuyển sang một chế độ xem (một phần khung nhìn, trong trường hợp ASP.NET MVC) cho mỗi phần chức năng cho mỗi khu vực kinh doanh. Không phải tất cả các lĩnh vực kinh doanh đều có cùng chức năng, nhưng nếu một người muốn có chức năng mà người khác có, thì khu vực đó có quan điểm riêng. Nó ít rườm rà hơn để hiểu mã, cũng như khả năng kiểm tra. Ví dụ: thực hiện thay đổi cho một khu vực sẽ không gây ra thay đổi không mong muốn cho khu vực khác.

Như @ dan1111 đã đề cập, nó có thể đi đến một cuộc gọi phán xét. Trong thời gian, bạn có thể tìm thấy liệu nó hoạt động hay không.


2

Một vấn đề có thể là bạn đang cung cấp một giao diện (giao diện lý thuyết, không phải tính năng ngôn ngữ) cho một cấp độ duy nhất của chức năng:

A(a,b,c) //a,b,c are your callbacks or other dependencies

Thay vì nhiều cấp độ tùy thuộc vào mức độ kiểm soát được yêu cầu:

//high level
A(a,b,c)
//lower
A myA(a,b)
B(myA,c)
//even lower
A myA(a)
B myB(myA,b)
C myC(myB,c)
//all the way down to you just having to write the code yourself

Theo tôi hiểu, bạn chỉ để lộ giao diện cấp cao (A), ẩn các chi tiết thực hiện (những thứ khác ở đó).

Ẩn chi tiết triển khai có những ưu điểm và bạn chỉ thấy nhược điểm - kiểm soát bị hạn chế, trừ khi bạn bổ sung rõ ràng các tính năng cho mọi điều có thể có khi sử dụng trực tiếp (các) giao diện cấp thấp.

Vì vậy, bạn có hai lựa chọn. Hoặc bạn chỉ sử dụng giao diện cấp thấp, sử dụng giao diện cấp thấp vì giao diện cấp cao quá nhiều công việc để duy trì hoặc để lộ cả giao diện cấp cao và cấp thấp. Tùy chọn hợp lý duy nhất là cung cấp cả giao diện cấp cao và cấp thấp (và mọi thứ nằm giữa), giả sử bạn muốn tránh mã thừa.

Sau đó, khi viết một trong những thứ khác của bạn, bạn xem xét tất cả các chức năng có sẵn mà bạn đã viết cho đến nay (vô số khả năng, tùy thuộc vào bạn để quyết định những cái nào có thể được sử dụng lại) và ghép chúng lại với nhau.

Sử dụng một đối tượng duy nhất mà bạn cần kiểm soát ít.

Sử dụng chức năng cấp thấp nhất khi một số điều kỳ lạ cần phải xảy ra.

Nó cũng không phải là rất đen trắng. Có thể lớp cấp cao lớn của bạn CÓ THỂ bao gồm một cách hợp lý tất cả các trường hợp sử dụng có thể. Có thể các trường hợp sử dụng rất khác nhau, không có gì ngoài chức năng nguyên thủy cấp thấp nhất đủ. Tùy bạn để tìm sự cân bằng.


1

Đã có những câu trả lời hữu ích khác. Tôi sẽ thêm của tôi.

Sao chép là xấu vì

  1. nó làm mã
  2. nó làm giảm sự thống kê của chúng tôi về mã nhưng quan trọng nhất
  3. bởi vì nếu bạn thay đổi một cái gì đó ở đây và bạn cũng phải thay đổi một cái gì đó ở đó , bạn có thể quên / giới thiệu các lỗi / .... và thật khó để không bao giờ quên.

Vì vậy, vấn đề là: bạn không loại bỏ sự trùng lặp vì lợi ích của nó hoặc bởi vì ai đó nói rằng nó quan trọng. Bạn đang làm điều đó bởi vì bạn muốn giảm lỗi / vấn đề. Trong trường hợp của bạn, có vẻ như nếu bạn thay đổi một cái gì đó trong một khung nhìn, có lẽ bạn sẽ không cần phải thay đổi cùng một dòng chính xác trong tất cả các khung nhìn khác. Vì vậy, bạn có sự trùng lặp rõ ràng , không phải sự trùng lặp thực tế.

Một điểm quan trọng khác là không bao giờ viết lại từ đầu một cái gì đó hiện đang hoạt động chỉ dựa trên vấn đề nguyên tắc, như Joel nói (bạn có thể đã nghe nói về anh ta ....). Vì vậy, nếu quan điểm của bạn đang hoạt động, hãy tiến hành cải thiện từng bước và đừng trở thành "sai lầm chiến lược tồi tệ nhất mà bất kỳ công ty phần mềm nào cũng có thể mắc phải".

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.