Cách duy trì các phiên bản tùy chỉnh khác nhau của cùng một phần mềm cho nhiều khách hàng


46

chúng tôi có nhiều khách hàng với các nhu cầu khác nhau. Mặc dù phần mềm của chúng tôi được mô đun hóa ở một mức độ, nhưng gần như chắc chắn rằng chúng tôi cần điều chỉnh logic kinh doanh của mọi mô-đun ở đây và một chút cho mỗi khách hàng. Những thay đổi có lẽ là quá nhỏ để biện minh cho việc chia mô-đun thành một mô-đun (vật lý) riêng biệt cho mỗi khách hàng, tôi sợ vấn đề với bản dựng, một sự hỗn loạn liên kết. Tuy nhiên, những thay đổi đó quá lớn và quá nhiều để cấu hình chúng bằng các công tắc trong một số tệp cấu hình, vì điều này sẽ dẫn đến các vấn đề trong quá trình triển khai và có thể gặp nhiều rắc rối hỗ trợ, đặc biệt là với quản trị viên loại tinker.

Tôi muốn hệ thống xây dựng tạo nhiều bản dựng, một bản cho mỗi máy khách, trong đó các thay đổi được chứa trong một phiên bản chuyên biệt của mô-đun vật lý duy nhất được đề cập. Vì vậy, tôi có một số câu hỏi:

Bạn có thể khuyên hệ thống xây dựng tạo nhiều bản dựng không? Tôi nên lưu trữ các tùy chỉnh khác nhau trong kiểm soát nguồn, cụ thể là svn như thế nào?


4
#ifdeflàm việc cho bạn?
ohho

6
Các chỉ thị tiền xử lý có thể nhanh chóng trở nên rất phức tạp và làm cho mã khó đọc hơn và khó gỡ lỗi hơn.
Falcon

1
Bạn nên bao gồm chi tiết về nền tảng thực tế và loại ứng dụng (máy tính để bàn / web) để có câu trả lời tốt hơn. Tùy chỉnh trong ứng dụng C ++ trên máy tính để bàn hoàn toàn khác với ứng dụng web PHP.
GrandmasterB

2
@Falcon: kể từ khi bạn chọn năm 2011 một câu trả lời tôi có rất nhiều nghi ngờ, bạn có thể cho chúng tôi biết nếu bạn có bất kỳ kinh nghiệm nào về việc sử dụng SVN theo cách được đề xuất ở giữa không? Là sự phản đối của tôi không sáng lập?
Doc Brown

2
@Doc Brown: sự phân nhánh và hợp nhất thật tẻ nhạt và phức tạp. Trước đó, chúng tôi đã sử dụng một hệ thống plugin với các plugin cụ thể của khách hàng hoặc "bản vá" thay đổi hành vi hoặc cấu hình. Bạn sẽ có một số chi phí nhưng nó có thể được quản lý bằng Dependendy Injection.
Falcon

Câu trả lời:


7

Những gì bạn cần là tính năng tổ chức mã giống như phân nhánh . Trong kịch bản cụ thể của bạn, điều này nên được gọi là phân nhánh thân cụ thể của Khách hàng vì bạn cũng có thể sử dụng tính năng phân nhánh tính năng trong khi bạn phát triển công cụ mới (hoặc giải quyết lỗi).

http://svnbook.red-bean.com/en/1.5/svn.branchmerge.commonpotypes.html

Ý tưởng là để có một thân mã với các nhánh tính năng mới được hợp nhất vào. Tôi có nghĩa là tất cả các tính năng không dành riêng cho khách hàng.

Sau đó, bạn cũng có các chi nhánh dành riêng cho khách hàng của mình , nơi bạn cũng hợp nhất các chi nhánh của các tính năng tương tự theo yêu cầu (chọn anh đào nếu bạn muốn).

Các nhánh khách này trông tương tự như các nhánh tính năng mặc dù chúng không tạm thời hoặc ngắn hạn. Chúng được duy trì trong một thời gian dài hơn và chúng chủ yếu chỉ được hợp nhất vào. Cần có càng ít sự phát triển chi nhánh đặc trưng của khách hàng càng tốt.

Các nhánh dành riêng cho khách hàng là các nhánh song song với thân cây và đang hoạt động miễn là chính thân cây và thậm chí không được hợp nhất ở bất kỳ đâu.

            feature1
            ———————————.
                        \
trunk                    \
================================================== · · ·
      \ client1            \
       `========================================== · · ·
        \ client2            \
         `======================================== · · ·
              \ client2-specific feature   /
               `——————————————————————————´

7
+1 cho phân nhánh tính năng. Bạn có thể sử dụng một chi nhánh cho mỗi khách hàng. Tôi chỉ muốn đề xuất một điều: sử dụng một VCS phân tán (hg, git, bzr) thay vì SVN / CVS để thực hiện điều này;)
Herberth Amaral 21/03

43
-1. Đó không phải là "nhánh tính năng" để làm gì; nó mâu thuẫn với định nghĩa của họ là "các nhánh tạm thời hợp nhất khi quá trình phát triển một tính năng kết thúc".
P Shved

14
Bạn vẫn phải duy trì tất cả các đồng bằng trong kiểm soát nguồn và giữ chúng xếp hàng - mãi mãi. Ngắn hạn điều này có thể làm việc. Về lâu dài nó có thể là khủng khiếp.
quick_now

4
-1, đây không phải là vấn đề đặt tên - sử dụng các nhánh khác nhau cho các máy khách khác nhau chỉ là một hình thức sao chép mã khác. Đây là IMHO một mô hình chống, một ví dụ làm thế nào để không làm điều đó.
Doc Brown

7
Robert, tôi nghĩ rằng tôi đã hiểu khá rõ những gì bạn đề xuất, ngay cả trước khi chỉnh sửa, nhưng tôi nghĩ đây là một cách tiếp cận khủng khiếp. Giả sử bạn có N máy khách, bất cứ khi nào bạn thêm một tính năng cốt lõi mới vào thân cây, có vẻ như SCM sẽ giúp bạn dễ dàng truyền bá tính năng mới cho các nhánh N. Nhưng sử dụng các nhánh theo cách này làm cho quá dễ dàng để tránh sự tách biệt rõ ràng của các sửa đổi dành riêng cho khách hàng. Do đó, giờ đây bạn có N cơ hội nhận được xung đột hợp nhất cho mỗi thay đổi trong trung kế. Hơn nữa, bây giờ bạn phải chạy N kiểm tra tích hợp thay vì một.
Doc Brown

38

Đừng làm điều này với các chi nhánh SCM. Làm cho mã chung trở thành một dự án riêng biệt tạo ra thư viện hoặc tạo khung xương dự án. Mỗi dự án khách hàng là một dự án riêng biệt, sau đó phụ thuộc vào một dự án chung như một sự phụ thuộc.

Điều này là dễ nhất nếu ứng dụng cơ sở chung của bạn sử dụng khung tiêm phụ thuộc như Spring, để bạn có thể dễ dàng tiêm các biến thể thay thế khác nhau của các đối tượng trong mỗi dự án của khách hàng vì cần có các tính năng tùy chỉnh. Ngay cả khi bạn chưa có khung DI, việc thêm một khung và thực hiện theo cách này có thể là lựa chọn ít đau đớn nhất của bạn.


Một cách tiếp cận khác để đơn giản hóa là sử dụng kế thừa (và một dự án duy nhất) thay vì sử dụng phép nội xạ phụ thuộc, bằng cách giữ cả mã chung và các tùy chỉnh trong cùng một dự án (sử dụng kế thừa, lớp một phần hoặc sự kiện). Với thiết kế đó, các nhánh khách sẽ hoạt động tốt (như được mô tả trong câu trả lời đã chọn) và người ta luôn có thể cập nhật các nhánh khách bằng cách cập nhật mã chung.
Drizin

Nếu tôi không bỏ lỡ điều gì, điều bạn gợi ý là nếu bạn có 100 khách hàng, bạn sẽ tạo (100 x NumberOfChangedProjects ) và sử dụng DI để quản lý họ? Nếu nó là như vậy, tôi chắc chắn sẽ tránh xa loại giải pháp này vì khả năng bảo trì sẽ rất tệ ..
sotn

13

Lưu trữ phần mềm cho tất cả khách hàng trong một chi nhánh. Không cần phải phân kỳ các thay đổi được thực hiện cho các khách hàng khác nhau. Hầu hết, bạn sẽ muốn phần mềm của bạn có chất lượng tốt nhất cho tất cả các khách hàng và lỗi cho cơ sở hạ tầng cốt lõi của bạn sẽ ảnh hưởng đến tất cả mọi người mà không cần kết hợp chi phí không cần thiết, điều này cũng có thể gây ra nhiều lỗi hơn.

Mô đun hóa mã chung và triển khai mã khác nhau trong các tệp khác nhau hoặc bảo vệ mã với các định nghĩa khác nhau. Làm cho hệ thống xây dựng của bạn có các mục tiêu cụ thể cho từng khách hàng, mỗi mục tiêu biên dịch một phiên bản chỉ có mã liên quan đến một khách hàng. Ví dụ, nếu mã của bạn là C, bạn có thể muốn bảo vệ các tính năng cho các máy khách khác nhau bằng " #ifdef" hoặc bất kỳ cơ chế nào mà ngôn ngữ của bạn có để quản lý cấu hình xây dựng và chuẩn bị một bộ định nghĩa tương ứng với số lượng tính năng mà khách hàng đã trả.

Nếu bạn không thích ifdef, hãy sử dụng "giao diện và triển khai", "functor", "tệp đối tượng" hoặc bất kỳ công cụ nào mà ngôn ngữ của bạn cung cấp để lưu trữ những thứ khác nhau ở một nơi.

Nếu bạn phân phối nguồn cho khách hàng của mình, tốt nhất là làm cho tập lệnh xây dựng của bạn có "mục tiêu phân phối nguồn" đặc biệt. Khi bạn gọi một mục tiêu như vậy, nó sẽ tạo ra một phiên bản nguồn phần mềm đặc biệt của bạn, sao chép chúng vào một thư mục riêng để bạn có thể gửi chúng và không biên dịch chúng.


8

Như nhiều người đã tuyên bố: tính hệ số mã của bạn một cách chính xác và tùy chỉnh dựa trên mã chung chungthththis sẽ cải thiện đáng kể khả năng bảo trì. Cho dù bạn có đang sử dụng ngôn ngữ / hệ thống hướng đối tượng hay không, điều này là có thể (mặc dù điều này khá khó thực hiện trong C so với điều gì đó thực sự hướng đối tượng). Đây chính xác là loại vấn đề mà kế thừa và đóng gói giúp giải quyết!


5

Rất cẩn thận

Phân nhánh tính năng là một tùy chọn nhưng tôi thấy nó hơi nặng. Ngoài ra, nó làm cho các sửa đổi sâu dễ dàng có thể dẫn đến việc loại bỏ hoàn toàn ứng dụng của bạn nếu không được kiểm soát. Lý tưởng nhất là bạn muốn thúc đẩy càng nhiều càng tốt các tùy chỉnh trong nỗ lực giữ cho cơ sở mã lõi của bạn càng phổ biến và chung chung càng tốt.

Đây là cách tôi sẽ làm điều đó mặc dù tôi không biết liệu nó có thể áp dụng cho cơ sở mã của bạn mà không cần sửa đổi và hệ số lại không. Tôi đã có một dự án tương tự trong đó các chức năng cơ bản là như nhau nhưng mỗi khách hàng yêu cầu một bộ tính năng rất cụ thể. Tôi đã tạo ra một tập hợp các mô-đun và bộ chứa mà sau đó tôi lắp ráp thông qua cấu hình (à la IoC).

sau đó, với mỗi khách hàng, tôi đã tạo một dự án về cơ bản chứa các cấu hình và tập lệnh xây dựng để tạo một bản cài đặt được cấu hình đầy đủ cho trang web của họ. Thỉnh thoảng tôi cũng đặt một số thành phần tùy chỉnh cho khách hàng này. Nhưng điều này là hiếm và bất cứ khi nào có thể, tôi cố gắng làm cho nó ở dạng chung chung hơn và đẩy nó xuống để các dự án khác có thể sử dụng chúng.

Kết quả cuối cùng là tôi đã đạt được mức độ tùy chỉnh cần thiết, tôi đã nhận được các tập lệnh cài đặt tùy chỉnh để khi tôi đến trang web của khách hàng, tôi không giống như tôi đang sử dụng hệ thống mọi lúc và như một phần thưởng RẤT đáng kể mà tôi nhận được để có thể tạo các bài kiểm tra hồi quy được nối trực tiếp trên bản dựng. Bằng cách này, bất cứ khi nào tôi gặp một lỗi cụ thể của khách hàng, tôi có thể viết một bài kiểm tra sẽ khẳng định hệ thống khi nó được triển khai và do đó có thể thực hiện TDD ngay cả ở cấp độ đó.

tóm lại

  1. Hệ thống mô đun hóa nặng nề với cấu trúc dự án phẳng.
  2. Tạo một dự án cho mỗi hồ sơ cấu hình (khách hàng, mặc dù nhiều người có thể chia sẻ một hồ sơ)
  3. Lắp ráp các bộ chức năng cần thiết như một sản phẩm khác và xử lý nó như vậy.

Nếu được thực hiện đúng cách lắp ráp sản phẩm của bạn sẽ chứa tất cả trừ một vài tệp cấu hình.

Sau khi sử dụng một thời gian, cuối cùng tôi đã tạo ra các gói meta lắp ráp các hệ thống chủ yếu được sử dụng hoặc thiết yếu làm đơn vị cốt lõi và sử dụng gói meta này cho các cụm khách hàng. Sau một vài năm, cuối cùng tôi đã có một hộp công cụ lớn mà tôi có thể lắp ráp rất nhanh để tạo ra các giải pháp khách hàng. Tôi hiện đang xem xét Spring Roo và xem liệu tôi không thể đẩy ý tưởng thêm một chút với hy vọng một ngày nào đó tôi có thể tạo bản nháp đầu tiên của hệ thống ngay với khách hàng trong cuộc phỏng vấn đầu tiên của chúng tôi ... Tôi đoán bạn có thể gọi nó là User Driven Phát triển ;-).

Hy vọng điều này sẽ giúp


3

Những thay đổi có lẽ là quá nhỏ để biện minh cho việc chia mô-đun thành một mô-đun (vật lý) riêng biệt cho mỗi khách hàng, tôi sợ vấn đề với bản dựng, một sự hỗn loạn liên kết.

IMO, chúng không thể quá nhỏ. Nếu có thể, tôi sẽ tính ra mã dành riêng cho khách hàng bằng cách sử dụng mẫu chiến lược khá nhiều ở mọi nơi. Điều này sẽ giảm số lượng mã phải được phân nhánh và giảm việc hợp nhất cần thiết để giữ mã chung đồng bộ cho tất cả các máy khách. Nó cũng sẽ đơn giản hóa việc kiểm tra ... bạn có thể kiểm tra mã chung bằng cách sử dụng các chiến lược mặc định và kiểm tra riêng các lớp dành riêng cho khách hàng.

Nếu các mô-đun của bạn được mã hóa sao cho sử dụng chính sách X1 trong mô-đun A yêu cầu sử dụng chính sách X2 trong mô-đun B, hãy nghĩ đến việc tái cấu trúc để X1 và X2 có thể được kết hợp thành một lớp chính sách duy nhất.


1

Bạn có thể sử dụng SCM của bạn để duy trì các chi nhánh. Giữ nhánh chính nguyên sơ / sạch từ mã tùy chỉnh của máy khách. Làm phát triển chính trên chi nhánh này. Đối với mỗi phiên bản tùy chỉnh của ứng dụng duy trì các nhánh riêng biệt. Bất kỳ công cụ SCM tốt nào cũng sẽ thực sự tốt với các nhánh hợp nhất (Git đến với tâm trí). Bất kỳ cập nhật nào trong nhánh chính nên được hợp nhất vào các nhánh tùy chỉnh, nhưng mã cụ thể của máy khách có thể nằm trong nhánh riêng của nó.


Mặc dù, nếu có thể, hãy cố gắng thiết kế hệ thống theo cách mô đun và cấu hình. Nhược điểm của các nhánh tùy chỉnh này có thể là nó di chuyển quá xa khỏi lõi / chủ.


1

Nếu bạn đang viết bằng chữ C, đây là một cách khá xấu để làm điều đó.

  • Mã chung (ví dụ: đơn vị "frangulator.c")

  • mã dành riêng cho khách hàng, các phần nhỏ và chỉ được sử dụng cho mỗi khách hàng.

  • trong mã đơn vị chính, sử dụng #ifdef và #include để làm một cái gì đó như

#ifdef KHÁCH HÀNG = KHÁCH HÀNG
#include "frangulator_client_a.c"
#endif

Sử dụng điều này như một mẫu lặp đi lặp lại trong tất cả các đơn vị mã yêu cầu tùy chỉnh riêng của khách hàng.

Điều này RẤT xấu, và dẫn đến một số rắc rối khác nhưng cũng đơn giản và bạn có thể so sánh chéo các tệp cụ thể của khách hàng với nhau một cách khá dễ dàng.

Điều đó cũng có nghĩa là tất cả các phần dành riêng cho khách hàng đều có thể nhìn thấy rõ ràng (mỗi phần trong tệp riêng) và có mối quan hệ rõ ràng giữa tệp mã chính và phần dành riêng cho tệp của khách hàng.

Nếu bạn thực sự thông minh, bạn có thể thiết lập tệp tạo tệp để tạo định nghĩa máy khách chính xác để đại loại như:

làm cho khách hàng

sẽ xây dựng cho client_a và "make clientb" sẽ tạo cho client_b, v.v.

(và "thực hiện" không có mục tiêu được cung cấp có thể đưa ra cảnh báo hoặc mô tả sử dụng.)

Tôi đã sử dụng một ý tưởng tương tự trước đây, phải mất một thời gian để thiết lập nhưng nó có thể rất hiệu quả. Trong trường hợp của tôi, một cây nguồn được xây dựng khoảng 120 sản phẩm khác nhau.


0

Trong git, cách tôi sẽ làm là có một nhánh chính với tất cả các mã chung và các nhánh cho mỗi máy khách. Bất cứ khi nào thay đổi mã lõi được thực hiện, chỉ cần khởi động lại tất cả các nhánh dành riêng cho khách hàng trên đỉnh chính, để bạn có một bộ các bản vá di chuyển cho các máy khách được áp dụng trên đường cơ sở hiện tại.

Bất cứ khi nào bạn thực hiện thay đổi cho khách hàng và nhận thấy một lỗi cần được đưa vào các nhánh khác, bạn có thể chọn nó vào chủ hoặc vào các nhánh khác cần sửa chữa (mặc dù các nhánh khách khác nhau đang chia sẻ mã , có lẽ bạn nên để cả hai phân nhánh ra một nhánh chung từ chủ).

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.