Tái cấu trúc và nguyên tắc mở / đóng


12

Gần đây tôi đang đọc một trang web về phát triển mã sạch (tôi không đặt liên kết ở đây vì nó không phải bằng tiếng Anh).

Một trong những nguyên tắc được quảng cáo bởi trang web này là Nguyên tắc đóng mở : mỗi thành phần phần mềm nên được mở để mở rộng và đóng để sửa đổi. Ví dụ, khi chúng tôi đã triển khai và thử nghiệm một lớp, chúng tôi chỉ nên sửa đổi nó để sửa lỗi hoặc thêm chức năng mới (ví dụ: các phương thức mới không ảnh hưởng đến các lớp hiện có). Các chức năng hiện có và thực hiện không nên được thay đổi.

Tôi thường áp dụng nguyên tắc này bằng cách định nghĩa một giao diện Ivà một lớp thực hiện tương ứng A. Khi lớp Ađã ổn định (được triển khai và thử nghiệm), tôi thường không sửa đổi nó quá nhiều (có thể, hoàn toàn không), tức là

  1. Nếu các yêu cầu mới xuất hiện (ví dụ hiệu năng hoặc triển khai giao diện hoàn toàn mới) yêu cầu thay đổi lớn đối với mã, tôi viết một triển khai mới Bvà tiếp tục sử dụng Amiễn Blà chưa hoàn thiện. Khi Btrưởng thành, tất cả những gì cần thiết là thay đổi cách Ikhởi tạo.
  2. Nếu các yêu cầu mới cũng đề xuất thay đổi giao diện, tôi xác định giao diện mới I'và cách triển khai mới A'. Vì vậy I, Arất đông lạnh và duy trì việc thực hiện cho hệ thống sản xuất chừng nào I'A'không đủ ổn định để thay thế chúng.

Vì vậy, theo quan sát của những quan sát này, tôi hơi ngạc nhiên khi trang web sau đó đề xuất sử dụng các phép tái cấu trúc phức tạp , "... bởi vì không thể viết mã trực tiếp ở dạng cuối cùng."

Không có mâu thuẫn / xung đột giữa việc thực thi Nguyên tắc Mở / Đóng và đề xuất sử dụng các phép tái cấu trúc phức tạp như một cách thực hành tốt nhất? Hoặc ý tưởng ở đây là người ta có thể sử dụng các phép tái cấu trúc phức tạp trong quá trình phát triển một lớp A, nhưng khi lớp đó đã được thử nghiệm thành công thì nó có nên bị đóng băng không?

Câu trả lời:


9

Tôi nghĩ về nguyên tắc Mở-Đóng như một mục tiêu thiết kế . Nếu bạn cuối cùng phải vi phạm nó, thì điều đó có nghĩa là thiết kế ban đầu của bạn thất bại, điều đó chắc chắn là có thể, và thậm chí có khả năng.

Tái cấu trúc có nghĩa là bạn đang thay đổi thiết kế mà không thay đổi chức năng. Có khả năng bạn đang thay đổi thiết kế của mình vì có vấn đề với nó. Có lẽ vấn đề là khó thực hiện theo nguyên tắc đóng mở khi thực hiện sửa đổi mã hiện có và bạn đang cố gắng khắc phục điều đó.

Bạn có thể thực hiện tái cấu trúc để có thể triển khai tính năng tiếp theo của mình mà không vi phạm OCP khi bạn thực hiện.


Bạn chắc chắn không nên nghĩ bất kỳ nguyên tắc nào là mục tiêu thiết kế . Chúng là các công cụ - bạn không làm cho phần mềm trở nên đẹp và đúng về mặt lý thuyết ở bên trong, bạn đang cố gắng tạo ra giá trị cho khách hàng của mình. Đó là một hướng dẫn , không có gì hơn.
T. Sar

@ T.Sar Một nguyên tắc là một hướng dẫn, một cái gì đó bạn phấn đấu, chúng được định hướng ở khả năng duy trì và khả năng mở rộng. Đó trông giống như một mục tiêu thiết kế với tôi. Tôi không thể thấy một nguyên tắc như một công cụ theo cách tôi thấy một patttern thiết kế hoặc một khung làm công cụ.
Tulains Córdova

@ TulainsCórdova Khả năng duy trì, Hiệu suất, Tính chính xác, Khả năng mở rộng - đó là những mục tiêu. Nguyên tắc mở-đóng là một phương tiện đối với họ - chỉ là một trong số nhiều. Bạn không cần phải thúc đẩy một cái gì đó theo nguyên tắc đóng mở nếu nó không áp dụng được với nó hoặc nó sẽ làm mất đi các mục tiêu thực tế của dự án. Bạn không bán "Đóng cửa mở" cho khách hàng. Như một hướng dẫn đơn thuần , sẽ không tốt hơn một quy tắc ngón tay cái có thể bị loại bỏ nếu cuối cùng bạn tìm ra cách để làm việc của mình theo cách dễ đọc và rõ ràng hơn. Nguyên tắc là công cụ, sau tất cả, không có gì hơn.
T. Sar

@ T.Sar Có rất nhiều thứ bạn không thể bán cho khách hàng ... Mặt khác, tôi đồng ý với bạn rằng người ta không được làm những điều làm mất đi mục tiêu của dự án.
Tulains Córdova

9

Nguyên tắc Đóng-Đóng là một chỉ số cho thấy phần mềm của bạn được thiết kế tốt như thế nào ; không phải là một nguyên tắc để làm theo nghĩa đen Đó cũng là một nguyên tắc giúp chúng ta không vô tình thay đổi các giao diện hiện có (các lớp & phương thức bạn gọi và cách bạn mong đợi chúng hoạt động).

Mục tiêu là viết phần mềm chất lượng. Một trong những phẩm chất này là khả năng mở rộng. Điều này có nghĩa là dễ dàng thêm, xóa, thay đổi mã với những thay đổi có xu hướng bị giới hạn ở một số lớp hiện có như thực tế. Thêm mã mới ít rủi ro hơn so với thay đổi mã hiện có, vì vậy về mặt này, Đóng mở là một việc nên làm. Nhưng những gì chúng ta đang nói về chính xác? Tội phạm vi phạm OC ít hơn nhiều khi bạn có thể thêm các phương thức mới vào một lớp thay vì cần phải thay đổi các phương thức hiện có.

OC là fractal . Nó táo ở tất cả các độ sâu của thiết kế của bạn. Mọi người đều cho rằng nó chỉ được áp dụng ở cấp lớp. Nhưng nó được áp dụng như nhau ở cấp độ phương pháp hoặc ở cấp độ lắp ráp.

Việc vi phạm OC quá thường xuyên ở mức độ phù hợp cho thấy có lẽ đã đến lúc phải cấu trúc lại . "Mức độ phù hợp" là một lời kêu gọi phán xét có liên quan đến thiết kế tổng thể của bạn.

Theo Open-Đóng theo nghĩa đen có nghĩa là số lượng các lớp sẽ bùng nổ. Bạn sẽ tạo (vốn "tôi") Các giao diện không cần thiết. Bạn sẽ kết thúc với các bit chức năng trải đều trên các lớp và sau đó bạn phải viết nhiều mã hơn để nối tất cả lại với nhau. Tại một số điểm, nó sẽ cho bạn thấy rằng việc thay đổi lớp ban đầu sẽ tốt hơn.


2
"Tội phạm vi phạm OC ít hơn nhiều khi bạn có thể thêm các phương thức mới vào một lớp thay vì cần thay đổi các phương thức hiện có.": Theo tôi hiểu, việc thêm các phương thức mới không vi phạm nguyên tắc OC (mở rộng) . Vấn đề là thay đổi các phương thức hiện có thực hiện giao diện được xác định rõ và do đó đã có ngữ nghĩa được xác định rõ (đã đóng để sửa đổi). Về nguyên tắc, tái cấu trúc không thay đổi ngữ nghĩa, vì vậy rủi ro duy nhất tôi có thể thấy là đưa ra các lỗi trong mã đã được kiểm tra tốt và ổn định.
Giorgio

1
Dưới đây là câu trả lời CodeReview minh họa mở cho phần mở rộng . Đó là thiết kế lớp học có thể mở rộng. Ngược lại, thêm một phương thức là sửa đổi lớp.
radarbob

Thêm các phương thức mới vi phạm LSP, không phải OCP.
Tulains Córdova

1
Thêm các phương thức mới không vi phạm LSP. Nếu bạn thêm một phương thức, bạn đã giới thiệu một giao diện mới @ TulainsCórdova
RubberDuck

6

Nguyên tắc Đóng-Đóng dường như là một nguyên tắc xuất hiện trước khi TDD phổ biến hơn. Ý tưởng là rủi ro khi tái cấu trúc mã bởi vì bạn có thể phá vỡ thứ gì đó để an toàn hơn khi để lại mã hiện có và chỉ cần thêm vào mã. Trong trường hợp không có bài kiểm tra này có ý nghĩa. Nhược điểm của phương pháp này là teo mã. Mỗi lần bạn mở rộng một lớp thay vì tái cấu trúc nó, bạn sẽ có thêm một lớp. Bạn chỉ đơn giản là bắt đầu mã trên đầu trang. Mỗi khi bạn chốt thêm mã vào, bạn sẽ tăng cơ hội sao chép. Hãy tưởng tượng; có một dịch vụ trong cơ sở mã của tôi mà tôi muốn sử dụng, tôi thấy nó không có những gì tôi muốn vì vậy tôi tạo một lớp mới để mở rộng nó và bao gồm chức năng mới của tôi. Một nhà phát triển khác xuất hiện sau đó và cũng muốn sử dụng dịch vụ tương tự. Thật không may, họ không ' t nhận ra rằng phiên bản mở rộng của tôi tồn tại. Họ mã chống lại việc thực hiện ban đầu nhưng họ cũng cần một trong những tính năng mà tôi đã mã hóa. Thay vì sử dụng phiên bản của tôi, giờ đây họ cũng mở rộng việc triển khai và thêm tính năng mới. Bây giờ chúng tôi đã có 3 lớp, một phiên bản gốc và hai phiên bản mới có một số chức năng trùng lặp. Theo nguyên tắc mở / đóng và sự trùng lặp này sẽ tiếp tục được xây dựng trong suốt thời gian tồn tại của dự án dẫn đến một cơ sở mã phức tạp không cần thiết.

Với một hệ thống được kiểm tra tốt, không cần phải chịu sự teo mã này, bạn có thể cấu trúc lại mã một cách an toàn cho phép thiết kế của bạn đồng hóa các yêu cầu mới thay vì phải liên tục chốt mã mới. Phong cách phát triển này được gọi là thiết kế nổi dần và dẫn đến các cơ sở mã có khả năng duy trì trạng thái tốt trong suốt cuộc đời của họ thay vì thu thập dần dần.


1
Tôi không phải là người ủng hộ nguyên tắc đóng mở cũng như TDD (theo nghĩa là tôi đã không phát minh ra chúng). Điều làm tôi ngạc nhiên là ai đó đã đề xuất nguyên tắc đóng mở VÀ sử dụng tái cấu trúc VÀ TDD cùng một lúc. Điều này có vẻ mâu thuẫn với tôi và vì vậy tôi đã cố gắng tìm ra cách đưa tất cả các hướng dẫn này lại với nhau thành một quy trình mạch lạc.
Giorgio

"Ý tưởng là rủi ro khi tái cấu trúc mã bởi vì bạn có thể phá vỡ thứ gì đó để an toàn hơn khi để lại mã hiện có và chỉ cần thêm vào nó.": Thật ra tôi không thấy nó theo cách này. Ý tưởng là để có các đơn vị nhỏ, khép kín mà bạn có thể thay thế hoặc mở rộng (do đó cho phép phần mềm phát triển), nhưng bạn không nên chạm vào từng đơn vị một khi nó đã được kiểm tra kỹ lưỡng.
Giorgio

Bạn phải nghĩ rằng lớp sẽ không chỉ được sử dụng trong cơ sở mã của bạn. Thư viện bạn viết có thể được sử dụng trong các dự án khác. Vì vậy, OCP rất quan trọng. Ngoài ra, một lập trình viên mới không biết về một lớp mở rộng với chức năng anh ta / cô ta cần là một vấn đề về giao tiếp / tài liệu, không phải là vấn đề thiết kế.
Tulains Córdova

@ TulainsCórdova trong mã ứng dụng này không liên quan. Đối với mã thư viện, tôi cho rằng phiên bản ngữ nghĩa là phù hợp hơn để truyền đạt các thay đổi đột phá.
opsb

1
@ TulainsCórdova với tính ổn định API của mã thư viện quan trọng hơn nhiều vì không thể kiểm tra mã máy khách. Với mã ứng dụng, phạm vi kiểm tra của bạn sẽ thông báo cho bạn về bất kỳ sự cố nào ngay lập tức. Nói cách khác, mã ứng dụng có thể thực hiện các thay đổi không có rủi ro trong khi mã thư viện phải quản lý rủi ro bằng cách duy trì API ổn định và báo hiệu sự cố bằng cách sử dụng phiên bản ngữ nghĩa
opsb

6

Theo cách nói của giáo dân:

A. Nguyên tắc O / C có nghĩa là chuyên môn hóa phải được thực hiện bằng cách mở rộng, không phải bằng cách sửa đổi một lớp để phù hợp với nhu cầu chuyên biệt.

B. Thêm chức năng bị thiếu (không chuyên biệt) có nghĩa là thiết kế chưa hoàn thành và bạn phải thêm nó vào lớp cơ sở, rõ ràng mà không vi phạm hợp đồng. Tôi nghĩ rằng điều này không vi phạm nguyên tắc.

C. Tái cấu trúc không vi phạm nguyên tắc.

Khi một thiết kế đáo hạn , giả sử, sau một thời gian sản xuất:

  • Nên có rất ít lý do để làm như vậy (điểm B), có xu hướng về 0 theo thời gian.
  • (Điểm C) sẽ luôn luôn có thể mặc dù không thường xuyên hơn.
  • Tất cả các chức năng mới được coi là một chuyên môn, có nghĩa là các lớp phải được mở rộng (kế thừa từ) (điểm A).

Nguyên tắc mở / đóng là rất nhiều hiểu lầm. Điểm A và B của bạn có được điều đó chính xác.
gnasher729

1

Đối với tôi, Nguyên tắc đóng mở là một hướng dẫn, không phải là quy tắc cứng và nhanh.

Đối với phần mở của nguyên tắc, các lớp cuối cùng trong Java và các lớp trong C ++ với tất cả các hàm tạo được khai báo là riêng tư vi phạm phần mở của nguyên tắc đóng mở. Có các trường hợp sử dụng chất rắn tốt (lưu ý: solid, không RẮN) cho các lớp cuối cùng. Thiết kế cho khả năng mở rộng là quan trọng. Tuy nhiên, điều này cần rất nhiều tầm nhìn xa và nỗ lực, và bạn luôn luôn theo đuổi dòng vi phạm YAGNI (bạn sẽ không cần nó) và tiêm mùi mã của tính tổng quát đầu cơ. Các thành phần phần mềm chính có nên được mở rộng? Đúng. Tất cả? Không. Điều đó trong bản thân nó là tổng quát đầu cơ.

Đối với phần đóng, khi chuyển từ phiên bản 2.0 sang 2.1 sang 2.2 đến 2.3 của một số sản phẩm, không sửa đổi hành vi là một ý tưởng rất tốt. Người dùng thực sự không thích nó khi mỗi bản phát hành nhỏ phá vỡ mã riêng của họ. Tuy nhiên, trên đường đi, người ta thường thấy rằng việc triển khai ban đầu trong phiên bản 2.0 đã bị phá vỡ cơ bản hoặc các ràng buộc bên ngoài làm hạn chế thiết kế ban đầu không còn được áp dụng. Bạn có cười và chịu đựng nó và duy trì thiết kế đó trong phiên bản 3.0, hoặc bạn có làm cho 3.0 không tương thích ngược về mặt nào đó không? Khả năng tương thích ngược có thể là một hạn chế rất lớn. Ranh giới phát hành chính là nơi phá vỡ khả năng tương thích ngược được chấp nhận. Bạn cần phải cẩn thận rằng làm điều này có thể khiến người dùng của bạn khó chịu. Phải có một trường hợp tốt cho lý do tại sao sự phá vỡ này với quá khứ là cần thiết.


0

Tái cấu trúc, theo định nghĩa, là thay đổi cấu trúc mã mà không thay đổi hành vi. Vì vậy, khi bạn tái cấu trúc, bạn không thêm các tính năng mới.

Những gì bạn đã làm như một ví dụ cho nguyên tắc Đóng Đóng nghe có vẻ ổn. Nguyên tắc này là về việc mở rộng mã hiện có với các tính năng mới.

Tuy nhiên, đừng hiểu sai câu trả lời này. Tôi không ngụ ý rằng bạn chỉ nên thực hiện các tính năng hoặc chỉ thực hiện tái cấu trúc cho các khối dữ liệu lớn. Cách lập trình phổ biến nhất là thực hiện một chút tính năng hơn là ngay lập tức thực hiện một chút tái cấu trúc (kết hợp với các bài kiểm tra tất nhiên để đảm bảo bạn không thay đổi bất kỳ hành vi nào). Tái cấu trúc phức tạp không có nghĩa là tái cấu trúc "lớn", nó có nghĩa là áp dụng các kỹ thuật tái cấu trúc phức tạp và được cân nhắc kỹ lưỡng.

Về các nguyên tắc RẮN. Chúng thực sự là những hướng dẫn tốt để phát triển phần mềm nhưng chúng không có quy tắc tôn giáo nào được tuân theo một cách mù quáng. Đôi khi, nhiều lần, sau khi bạn thêm tính năng thứ hai và thứ ba và thứ n, bạn nhận ra rằng thiết kế ban đầu của bạn, ngay cả khi nó tôn trọng Open-Close, nó không tôn trọng các nguyên tắc hoặc yêu cầu phần mềm khác. Có những điểm trong sự phát triển của một thiết kế và của một phần mềm khi những thay đổi phức tạp hơn phải được thực hiện. Toàn bộ vấn đề là tìm và nhận ra những vấn đề này càng sớm càng tốt và áp dụng các kỹ thuật tái cấu trúc tốt nhất có thể.

Không có những thứ như thiết kế hoàn hảo. Không có thiết kế nào có thể và nên tôn trọng tất cả các nguyên tắc hoặc mẫu hiện có. Đó là mã hóa không tưởng.

Tôi hy vọng câu trả lời này đã giúp bạn trong tình trạng khó xử của bạn. Hãy yêu cầu làm rõ nếu cần.


1
"Vì vậy, khi bạn tái cấu trúc, bạn không thêm các tính năng mới.": Nhưng tôi có thể giới thiệu các lỗi trong một phần mềm được thử nghiệm.
Giorgio

"Đôi khi, nhiều lần, sau khi bạn thêm tính năng thứ hai và thứ ba và thứ n, bạn nhận ra rằng thiết kế ban đầu của bạn, ngay cả khi nó tôn trọng Open-Close, nó không tôn trọng các nguyên tắc hoặc yêu cầu phần mềm khác.": Đó là khi tôi sẽ bắt đầu viết một triển khai mới Bvà khi đã sẵn sàng, thay thế triển khai cũ Abằng triển khai mới B(đó là một lần sử dụng giao diện). AMã của nó có thể làm cơ sở cho Bmã của và sau đó tôi có thể sử dụng tái cấu trúc Bmã trong quá trình phát triển mã, nhưng tôi nghĩ rằng Amã đã được kiểm tra nên vẫn bị đóng băng.
Giorgio

@Giorgio Khi bạn tái cấu trúc, bạn có thể giới thiệu các lỗi, đó là lý do tại sao bạn viết bài kiểm tra (hoặc thậm chí tốt hơn làm TDD). Cách an toàn nhất để tái cấu trúc là thay đổi mã khi bạn biết nó đang hoạt động. Bạn biết điều này bằng cách có một bộ các bài kiểm tra đang qua. Sau khi bạn thay đổi mã sản xuất, các bài kiểm tra vẫn phải vượt qua, vì vậy bạn biết rằng bạn không đưa ra lỗi. Và hãy nhớ rằng, các bài kiểm tra cũng quan trọng như mã sản xuất, vì vậy bạn áp dụng quy tắc tương tự với chúng như mã sản xuất và giữ chúng sạch sẽ và tái cấu trúc chúng theo định kỳ và thường xuyên.
Patkos Csaba

@Giorgio Nếu mã Bđược xây dựng dựa trên mã Anhư một sự tiến hóa của A, hơn, khi Bđược phát hành, Anên được loại bỏ và không bao giờ được sử dụng lại. Khách hàng trước đây sử dụng Asẽ chỉ sử dụng Bmà không biết về thay đổi, vì giao diện Ikhông được thay đổi (có thể là một chút Nguyên tắc thay thế Liskov ở đây? ... L từ RẮN)
Patkos Csaba

Vâng, đây là những gì tôi đã nghĩ: đừng vứt bỏ mã làm việc cho đến khi bạn có một sự thay thế hợp lệ (được kiểm tra tốt).
Giorgio

-1

Theo hiểu biết của tôi - nếu bạn thêm các phương thức mới vào lớp hiện tại thì nó sẽ không phá vỡ OCP. tuy nhiên tôi hơi bối rối với việc thêm các biến mới trong Class. Nhưng nếu bạn thay đổi phương thức và tham số hiện có trong phương thức hiện có thì chắc chắn nó sẽ phá vỡ OCP, vì mã đã được kiểm tra và thông qua nếu chúng tôi cố tình thay đổi phương thức [Khi yêu cầu thay đổi] thì đó sẽ là vấn đề.

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.