2009-09-21 35 views
5

Tôi sắp bắt đầu duy trì một dòng sản phẩm có chứa các biến thể của cùng một phần mềm được nhúng. Vì tôi đã chơi với git trong một năm và đánh giá cao nó rất nhiều, tôi có thể sử dụng nó để kiểm soát nguồn.Theo dõi các biến thể mã nguồn

Có một số tùy chọn tôi có thể thấy để duy trì các biến thể của chương trình cơ sở, nhưng không có gì làm tôi hài lòng quá nhiều. Các phương pháp hay nhất mà bạn áp dụng cho công việc của mình là gì?

Alternatives tôi có thể nghĩ:

  • định nghĩa. Sơ chế. Ưu điểm: mọi thứ luôn có mặt trong mã nguồn, rất khó bỏ lỡ bản cập nhật của một trong các sản phẩm. Nhược điểm: khó đọc hơn. Nó có thể là ok trong khi chúng tôi chỉ có hai biến thể, khi nó trở thành bốn hoặc nhiều hơn nó sẽ là một nỗi đau. Ngoài ra, có vẻ khó áp dụng nguyên tắc DRY (Đừng lặp lại chính mình).

  • một nhánh cho mỗi biến thể sản phẩm. Khi thay đổi áp dụng cho tất cả các sản phẩm được bao gồm, thay đổi phải được hợp nhất với các sản phẩm khác. Nhược điểm: nếu cam kết chứa cả hai thay đổi cho tất cả các sản phẩm và thay đổi cho biến thể cụ thể, sẽ có sự cố. Tất nhiên, bạn có thể đảm bảo rằng cam kết chỉ chứa một loại thay đổi: thay đổi sản phẩm này hoặc toàn bộ thay đổi cho gia đình. Nhưng cố gắng để ép buộc đó vào một đội ?? Thêm vào đó, việc hợp nhất sẽ không hoạt động, thay vào đó chúng ta nên chọn lựa anh đào. Đúng?

  • a kho lưu trữ lõi dưới dạng mô hình con. Làm cho tất cả các tệp có chứa một chức năng cốt lõi một kho lưu trữ riêng. Tất cả các sản phẩm đều chứa một phiên bản của kho lưu trữ lõi như một mô-đun phụ. Nhược điểm: Tôi không thể thấy rằng cuối cùng sẽ không có biến thể của mô-đun con lõi. Sau đó, chúng tôi gặp rắc rối một lần nữa, và sau đó chúng tôi sẽ sử dụng định nghĩa hoặc một cái gì đó xấu một lần nữa. Kho lưu trữ lõi với các chi nhánh? Sau đó, chúng tôi trở lại giải pháp thay thế trước: thay đổi áp dụng cho tất cả các chi nhánh phải được hợp nhất nhưng việc hợp nhất cũng bao gồm cả nội dung cụ thể của sản phẩm.

  • tạo một kho lưu trữ cho mỗi mô-đun. Ví dụ một kho lưu trữ cho trình điều khiển hiển thị, một kho lưu trữ khác cho phần cứng quản lý nguồn, một phần khác cho giao diện đầu vào của người dùng, ... Ưu điểm: mô đun tốt. Thực hiện một sản phẩm mới bằng cách chỉ cần chọn lên các mô-đun bạn cần như submodules! Tất cả các mô-đun con có thể có các nhánh, nếu ví dụ một biến thể sử dụng phần cứng theo một cách khác. Nhược điểm: Rất nhiều và nhiều mô-đun, mỗi mô-đun theo dõi một vài tệp (bao gồm tệp và tệp nguồn). Một rắc rối. Ai đó thực hiện cập nhật quan trọng trong một số mô-đun? Sau đó, ai đó cần phải bao gồm sự thay đổi trong các nhánh khác của mô-đun này, nếu thích hợp. Sau đó, ai đó cũng phải cập nhật mô-đun con trong mỗi kho lưu trữ sản phẩm. Khá nhiều công việc, và chúng tôi sẽ mất mặt snap của git.

Bạn làm như thế nào và cách hoạt động? Hay bạn sẽ làm như thế nào?

Tôi có cảm giác rằng tôi nên trải nghiệm với việc hái hoa anh đào.

+0

Với một giải pháp chi nhánh cho mỗi sản phẩm, bạn luôn có thể thực hiện phát triển trên một chi nhánh mở rộng, sau đó hợp nhất chi nhánh này vào các biến thể (mỗi sản phẩm). –

Trả lời

5

Tôi sẽ cố gắng truy cập #define s càng nhiều càng tốt. Với mã thích hợp, bạn có thể giảm thiểu tác động đến khả năng đọc và lặp lại.

Nhưng đồng thời cách tiếp cận #define có thể được kết hợp an toàn với chia tách và phân nhánh, ứng dụng phụ thuộc vào bản chất của codebase.

+0

Xác định là giải pháp tồi tệ nhất có thể. Chúng tạo mã nguồn mà bạn thấy trong trình soạn thảo khác với trình biên dịch thấy. – xpmatteo

+2

Và 'if's tạo mã nguồn mà bạn nhìn thấy trong trình chỉnh sửa khác với mã được thực hiện. Toàn bộ điều đó được gọi là lập trình. –

+0

Toàn bộ các ngôn ngữ cấp cao là làm cho việc lập trình dễ dàng hơn cho con người. Có để giải thích trong IFs tâm trí của bạn tại thời gian biên dịch và cũng IFs tại thời gian chạy làm cho nó khó khăn hơn nhiều và lỗi dễ hiểu những gì chương trình nào. – xpmatteo

5

Tôi không chắc đó có phải là "thực hành tốt nhất" hay không, nhưng dự án Scintilla sử dụng thứ gì đó trong nhiều năm vẫn còn khá dễ quản lý. Nó chỉ có một nhánh chung cho tất cả các nền tảng (chủ yếu là Windows/GTK +/Mac, nhưng với các biến thể cho VMS, Fox, v.v.). Bằng cách nào đó, nó sử dụng tùy chọn đầu tiên khá phổ biến: nó sử dụng định nghĩa để quản lý các phần cụ thể nền tảng nhỏ bên trong các nguồn, nơi không thực tế hoặc không thể đặt mã phổ biến.
Lưu ý rằng tùy chọn này không thể thực hiện được với một số ngôn ngữ (ví dụ: Java).

Nhưng cơ chế chính cho tính di động là sử dụng OO: nó tóm tắt một số thao tác (vẽ, hiển thị menu ngữ cảnh, v.v.) và sử dụng tệp Nền tảng (hoặc vài) cho mỗi mục tiêu, cung cấp triển khai cụ thể.
Makefile chỉ biên dịch (các) tệp thích hợp và sử dụng liên kết để nhận mã thích hợp.

+0

OO sẽ được tốt đẹp, nhưng điều này là nhúng mã trong assembler, thậm chí không có một trình biên dịch C có sẵn. Tuy nhiên, tôi hiểu rằng các khái niệm về OO có thể được áp dụng. Việc lắp ráp và liên kết chỉ các tệp cần thiết là một tùy chọn, nhưng tôi tin rằng hai tệp như vậy (một tệp cho mỗi biến thể) sẽ rất giống nhau, do đó lặp lại một tệp khác. – Gauthier

+0

Có các tệp 'compat', có thể là một hoặc một tệp cho mỗi nền tảng duy nhất, tôi nghĩ đó là một ý tưởng hay. –

+0

Cách giải quyết cho Java là sử dụng các plugin bổ sung như Antenna cho Eclipse. Không hoàn hảo, nhưng khá hữu ích. – David

2

Tôi nghĩ rằng câu trả lời phù hợp phụ thuộc một phần vào sự khác biệt của các biến thể hoàn toàn.

Nếu có các phần nhỏ khác nhau, sử dụng trình biên dịch có điều kiện trên một tệp nguồn là hợp lý. Nếu việc triển khai biến thể chỉ phù hợp ở giao diện cuộc gọi, thì có thể tốt hơn khi sử dụng các tệp riêng biệt. Bạn có thể bao gồm việc triển khai biến thể hoàn toàn trong một tệp đơn với biên dịch có điều kiện; làm thế nào lộn xộn đó là phụ thuộc vào khối lượng của mã biến thể. Nếu nó là, nói, bốn biến thể của khoảng 100 dòng mỗi, có thể một tập tin là OK. Nếu nó là bốn biến thể của 100, 300, 500 và 900 dòng, sau đó một tập tin có lẽ là một ý tưởng tồi.

Bạn không nhất thiết cần các biến thể trên các nhánh riêng biệt; thực sự, bạn chỉ nên sử dụng nhánh khi cần thiết (nhưng hãy sử dụng chúng khi cần thiết!). Bạn có thể có bốn tệp, giả sử tất cả trên một nhánh chung, luôn hiển thị. Bạn có thể sắp xếp việc biên dịch để chọn đúng biến thể. Một khả năng (có nhiều người khác) đang soạn thảo một tập tin nguồn duy nhất mà biết mà biến nguồn để bao gồm cho môi trường biên soạn hiện tại:

#include "config.h" 
#if defined(USE_VARIANT_A) 
#include "variant_a.c" 
#elif defined(USE_VARIANT_B) 
#include "variant_b.c" 
#else 
#include "basecase.c" 
#endif 
+0

Tôi muốn làm điều này trong makefile, tôi không thích bao gồm các tệp c với #include: chúng sẽ không hiển thị trong tệp makefile của tôi. Tôi đồng ý rằng không có viên đạn bạc, giải pháp phụ thuộc vào phương sai của các biến thể :) Điều tôi sợ là bắt đầu với #defines và xem gia đình sản phẩm phát triển thành 10 biến thể. – Gauthier

+0

Nếu bạn đang tạo các tệp makefiles (trái ngược với việc sử dụng một tệp makefile được kiểm soát phiên bản), thì hãy tiếp cận trực tiếp việc liệt kê các tệp chính xác trong tệp makefile. Cách tiếp cận này hoạt động OK (không nhất thiết phải đề nghị, nhưng nó hoạt động) khi makefile không được tạo động. Thông điệp cơ bản của tôi là "một kích thước không phù hợp với tất cả" và các giải pháp khác nhau có liên quan tùy thuộc vào chi tiết. Nếu bạn nghĩ rằng bạn có thể có 10 mục biến thể, sau đó thiết kế hệ thống của bạn để làm việc với nhiều biến thể như vậy. Bạn không nói liệu đó là 10 quyết định nhị phân hay 1 trong 10 lựa chọn thay thế, hay cái gì khác. –

+0

Cho đến nay tôi có nghĩa là 1 trong 10 lựa chọn thay thế. Cảm ơn Chúa :) Tôi không hiểu tại sao có phiên bản makefiles kiểm soát (một cho mỗi biến thể) sẽ làm mất hiệu lực phương pháp này? – Gauthier

6

Bạn nên cố gắng, càng nhiều càng tốt, giữ mã tùy chỉnh của mỗi biến trong tập hợp các tệp riêng của nó. Sau đó, hệ thống xây dựng của bạn (Makefile hoặc bất cứ điều gì) chọn nguồn nào để sử dụng dựa trên biến thể bạn đang xây dựng.

Lợi thế của việc này là khi làm việc trên một biến thể cụ thể, bạn sẽ thấy tất cả mã của nó cùng nhau, mà không có mã của các biến thể khác trong đó để gây nhầm lẫn mọi thứ. Khả năng đọc cũng tốt hơn nhiều so với xả rác nguồn bằng #ifdef, #elif, #endif, v.v.

Chi nhánh hoạt động tốt nhất khi bạn biết rằng trong tương lai bạn sẽ muốn hợp nhất tất cả mã từ chi nhánh vào chi nhánh chính (hoặc các chi nhánh khác). Nó không hoạt động tốt khi chỉ hợp nhất một số thay đổi từ chi nhánh này sang chi nhánh khác (mặc dù nó có thể được thực hiện). Vì vậy, việc giữ các nhánh riêng biệt cho mỗi biến thể có thể sẽ không mang lại kết quả tốt.

Nếu bạn sử dụng cách tiếp cận nói trên, bạn không cần phải cố gắng sử dụng các thủ thuật như vậy trong điều khiển phiên bản để hỗ trợ tổ chức mã của bạn.

+0

+1 để được tư vấn về phân nhánh chỉ khi lập kế hoạch hợp nhất lại (mặc dù có ngoại lệ). Ngoài ra đối với nguy cơ sử dụng thủ thuật VCS để hỗ trợ mã. Xem nhận xét của tôi về PhiLho về DRY với các tệp mã tùy chỉnh như vậy. – Gauthier

+0

Khi tôi đang làm việc trên một biến thể cụ thể, tôi thường muốn xem mã của các biến thể khác. Đây là một cách sử dụng tốt để gập mã .. –

1

Bạn đang ở trong một thế giới bị tổn thương!

Dù bạn làm gì, bạn cần một môi trường xây dựng tự động. Ít nhất bạn cần một số cách tự động để xây dựng tất cả các phiên bản phần mềm khác nhau của bạn. Tôi đã có vấn đề sửa lỗi trong một phiên bản và phá vỡ bản dựng của một phiên bản khác.

Lý tưởng nhất là bạn có thể tải các mục tiêu khác nhau và chạy một số thử nghiệm khói.

Nếu bạn đi con đường #define, tôi sẽ đặt một nơi nào đó sau khi các biến thể được kiểm tra:

#else 
    #error You MUST specify a variant! 
#endif 

Điều này sẽ đảm bảo tất cả các tập tin được xây dựng cho các biến thể tương tự trong quá trình xây dựng.

+0

Chắc chắn, #error. Và thậm chí, nếu một số biến thể xác định được xác định, không xác định tất cả, nhưng một (hoặc gửi #error khác). – Gauthier

0

Tôi đang gặp phải vấn đề tương tự. Tôi muốn chia sẻ nguồn không chỉ trong số các mục tiêu, mà còn trên các nền tảng (ví dụ: Mono vs. Visual studio. Có vẻ như không có cách nào dễ dàng làm điều đó chỉ với phiên bản phân nhánh/gắn thẻ do Git cung cấp. muốn duy trì một chi nhánh mã thông thường, và lưu trữ/nhắm mục tiêu các chi nhánh cụ thể và chỉ cần nhập mã phổ biến vào và ra khỏi các chi nhánh đó Nhưng tôi không biết cách thực hiện điều đó. Rõ ràng, tôi sẽ sử dụng trình biên dịch điều kiện nếu cần ở nơi tôi có thể, vì vậy sẽ chỉ có một tệp nguồn trong điều khiển phiên bản, nhưng đối với các tệp công cụ khác chỉ xuất hiện trong một máy chủ/nhánh đích, hoặc nếu nội dung sẽ khác hoàn toàn, cần có một cơ chế khác, nếu bạn có thể sử dụng các kỹ thuật OO để giải quyết vấn đề đó, nó sẽ là desirabl e.

Vì vậy, câu trả lời có vẻ là bạn cần một số loại cấu hình quản lý cấu hình/xây dựng trên đầu trang của git để quản lý việc này. Nhưng điều đó dường như làm tăng thêm sự phức tạp, vì vậy có lẽ việc hái hoa anh đào sáp nhập vào một nhánh chung sẽ không phải là một cách tồi tệ để đi. Đặc biệt nếu bạn sử dụng các kỹ thuật khác để giữ cho các biến thể nguồn ở mức tối thiểu. (Có thể gắn thẻ giúp tự động hóa điều này không?).

Các vấn đề liên quan