Sử dụng #ifdef để chuyển đổi giữa các loại hành vi khác nhau trong quá trình phát triển


28

Có phải là một cách thực hành tốt để sử dụng #ifdef trong quá trình phát triển để chuyển đổi giữa các loại hành vi khác nhau? Ví dụ, tôi muốn thay đổi hành vi của mã hiện tại, tôi có một số ý tưởng về cách thay đổi hành vi và cần phải chuyển đổi giữa các triển khai khác nhau để kiểm tra và so sánh các cách tiếp cận khác nhau. Thông thường các thay đổi trong mã rất phức tạp và ảnh hưởng đến các phương thức khác nhau trong các tệp khác nhau.

Tôi thường giới thiệu một số định danh và làm một cái gì đó như thế

void foo()
{
    doSomething1();
#ifdef APPROACH1
    foo_approach1();
#endif
    doSomething2();
#ifdef APPROACH2
    foo_approach2();
#endif
}

void bar()
{
    doSomething3();
#ifndef APPROACH3
    doSomething4();
#endif
    doSomething5();
#ifdef APPROACH2
    bar_approach2();
#endif
}

int main()
{
    foo();
    bar();
    return 0;
}

Điều này cho phép chuyển đổi giữa các cách tiếp cận khác nhau một cách nhanh chóng và thực hiện mọi thứ chỉ trong một bản sao của mã nguồn. Đó có phải là một cách tiếp cận tốt để phát triển hay có một thực tiễn tốt hơn?



2
Vì bạn đang nói về sự phát triển, tôi tin rằng bạn phải làm bất cứ điều gì bạn thấy dễ làm để chuyển đổi và thử nghiệm với các triển khai khác nhau. Điều này giống như sở thích cá nhân trong quá trình phát triển, không phải là một cách thực hành tốt nhất để giải quyết một vấn đề cụ thể.
Emerson Cardoso

1
Tôi khuyên bạn nên sử dụng mẫu chiến lược hoặc tính đa hình tốt của ol, vì điều này giúp giữ một điểm bổ trợ duy nhất cho hành vi có thể chuyển đổi.
pmf

4
Hãy nhớ rằng một số IDE không đánh giá bất cứ thứ gì trong #ifdefcác khối nếu khối bị tắt. Chúng tôi gặp phải trường hợp mã có thể dễ dàng bị lỗi thời và không biên dịch nếu bạn không thường xuyên xây dựng tất cả các đường dẫn.
Berin Loritsch

Có một cái nhìn vào câu trả lời này tôi đã đưa ra một câu hỏi khác. Nó đưa ra một số cách để làm cho rất nhiều #ifdefsít cồng kềnh.
dùng1118321

Câu trả lời:


9

Tôi muốn sử dụng các nhánh kiểm soát phiên bản cho trường hợp sử dụng này. Điều đó cho phép bạn khác biệt giữa các lần triển khai, duy trì một lịch sử riêng biệt cho từng triển khai và khi bạn đã đưa ra quyết định và cần xóa một trong các phiên bản, bạn chỉ cần loại bỏ chi nhánh đó thay vì thực hiện chỉnh sửa dễ bị lỗi.


gitđặc biệt lão luyện ở loại điều này. Có thể không quá nhiều trường hợp với svn, hghoặc những người khác, nhưng nó vẫn có thể được thực hiện.
twalberg

Đó cũng là suy nghĩ ban đầu của tôi. "Muốn lộn xộn với một cái gì đó khác nhau?" git branch!
Wes Toleman

42

Khi bạn đang cầm một cái búa, mọi thứ trông giống như một cái đinh. Thật hấp dẫn khi bạn biết cách #ifdefsử dụng nó như một phương tiện để có được hành vi tùy chỉnh trong chương trình của mình. Tôi biết vì tôi đã phạm sai lầm tương tự.

Tôi đã kế thừa một chương trình cũ được viết bằng MFC C ++ đã được sử dụng #ifdefđể xác định các giá trị cụ thể cho nền tảng. Điều này có nghĩa là tôi có thể biên dịch chương trình của mình để sử dụng trên nền tảng 32 bit hoặc nền tảng 64 bit chỉ bằng cách xác định (hoặc trong một số trường hợp không xác định) các giá trị macro cụ thể.

Sau đó, vấn đề nảy sinh là tôi cần phải viết hành vi tùy chỉnh cho khách hàng. Tôi có thể đã tạo một chi nhánh và tạo một cơ sở mã riêng cho khách hàng, nhưng điều đó sẽ tạo ra một địa ngục bảo trì. Tôi cũng có thể đã xác định các giá trị cấu hình được chương trình đọc khi khởi động và sử dụng các giá trị này để xác định hành vi, nhưng sau đó tôi sẽ phải tạo các thiết lập tùy chỉnh để thêm các giá trị cấu hình phù hợp vào các tệp cấu hình cho mỗi máy khách.

Tôi đã bị cám dỗ và tôi đã nhượng bộ. Tôi đã viết #ifdefcác phần trong mã của mình để phân biệt các hành vi khác nhau. Không có lỗi, nó không có gì trên đầu trang. Những thay đổi hành vi rất nhỏ đã được thực hiện cho phép tôi phân phối lại các phiên bản của chương trình cho khách hàng của chúng tôi và tôi không cần có nhiều hơn một phiên bản của cơ sở mã.

Qua thời gian này đã trở thành địa ngục bảo trì nào vì chương trình không còn cư xử một cách nhất quán trên bảng. Nếu tôi muốn thử nghiệm một phiên bản của chương trình, tôi nhất thiết phải biết khách hàng là ai. Mã, mặc dù tôi đã cố gắng giảm nó xuống một hoặc hai tệp tiêu đề, nhưng rất lộn xộn và phương pháp khắc phục nhanh #ifdefcung cấp các giải pháp như vậy lan truyền khắp chương trình như một căn bệnh ung thư ác tính.

Tôi đã học được bài học của mình và bạn cũng vậy. Sử dụng nó nếu bạn hoàn toàn phải, và sử dụng nó một cách nghiêm ngặt để thay đổi nền tảng. Cách tốt nhất để tiếp cận sự khác biệt về hành vi giữa các chương trình (và do đó là máy khách) là chỉ thay đổi cấu hình được tải khi khởi động. Chương trình vẫn nhất quán và cả hai đều trở nên dễ đọc hơn cũng như gỡ lỗi.


những gì về phiên bản gỡ lỗi, như "nếu gỡ lỗi, xác định biến x ..." thì điều này có vẻ hữu ích cho những thứ như đăng nhập, nhưng sau đó nó cũng có thể thay đổi hoàn toàn cách chương trình của bạn hoạt động khi bật gỡ lỗi và khi không .
whn

8
@snb Tôi nghĩ về điều đó. Tôi vẫn thích có thể thay đổi một tập tin cấu hình và làm cho nó đăng nhập với nhiều chi tiết hơn. Nếu không, có vấn đề với chương trình trong sản xuất và bạn không có cách nào gỡ lỗi mà không thay thế hoàn toàn việc thực thi. Ngay cả trong các tình huống lý tưởng, điều này là ít hơn mong muốn. ;)
Neil

Ồ vâng, sẽ lý tưởng hơn nếu không phải biên dịch lại để gỡ lỗi, tôi đã không nghĩ về điều đó!
whn

9
Để biết ví dụ cực đoan về những gì bạn đang mô tả, hãy xem đoạn 2 dưới tiêu đề "Vấn đề bảo trì" trong bài viết này về lý do tại sao MS đạt đến điểm họ phải viết lại hầu hết thời gian chạy C từ đầu vài năm trước . blog.msdn.microsoft.com/vcblog/2014/06/10/ Từ
Dan Neely

2
@snb Hầu hết các thư viện ghi nhật ký đều cho rằng một cơ chế cấp ghi nhật ký. Nếu bạn muốn một số thông tin nhất định được ghi lại trong quá trình gỡ lỗi, bạn hãy đăng nhập nó với mức ghi nhật ký thấp ("Gỡ lỗi" hoặc "Verbose" thường). Sau đó, ứng dụng có một tham số cấu hình cho biết mức độ cần đăng nhập. Vì vậy, câu trả lời vẫn là cấu hình cho vấn đề này. Điều này cũng có lợi ích to lớn là có thể bật mức ghi nhật ký thấp này trong môi trường máy khách .
jpmc26

21

Tạm thời không có gì sai với những gì bạn đang làm (giả sử, trước khi đăng ký): đó là một cách tuyệt vời để kiểm tra các kết hợp kỹ thuật khác nhau hoặc bỏ qua một phần mã (mặc dù nói về các vấn đề trong chính nó).

Nhưng một lời cảnh báo: đừng giữ các nhánh #ifdef có chút bực bội hơn là lãng phí thời gian của tôi để đọc cùng một cách thực hiện bốn cách khác nhau, chỉ để tìm ra tôi nên đọc cái nào .

Đọc qua một #ifdef cần nỗ lực vì bạn phải thực sự nhớ bỏ qua nó! Đừng làm cho nó khó hơn bất kỳ điều gì phải hoàn toàn.

Sử dụng #ifdefs một cách tiết kiệm nhất có thể. Nhìn chung, có những cách mà bạn có thể làm điều này trong môi trường phát triển của mình cho những khác biệt vĩnh viễn , chẳng hạn như các bản dựng Debug / Release hoặc cho các kiến ​​trúc khác nhau.

Tôi đã viết các tính năng thư viện phụ thuộc vào các phiên bản thư viện đi kèm, yêu cầu chia tách #ifdef. Vì vậy, đôi khi nó có thể là cách duy nhất, hoặc dễ nhất, nhưng ngay cả khi đó bạn nên buồn bã về việc giữ chúng.


1

Sử dụng #ifdefs như thế làm cho mã rất khó đọc.

Vì vậy, không, đừng sử dụng #ifdefs như thế.

Có thể có vô số lý lẽ tại sao không sử dụng ifdefs, đối với tôi điều này là đủ.

void foo()
{
    doSomething1();
#ifdef APPROACH1
    foo_approach1();
#endif
    doSomething2();
#ifdef APPROACH2
    foo_approach2();
#endif
}

Có thể làm rất nhiều thứ nó có thể làm:

void foo()
{
    doSomething1();
    doSomething2();
}

void foo()
{
    doSomething1();
    foo_approach1();
    doSomething2();
}

void foo()
{
    doSomething1();
    doSomething2();
    foo_approach2();
}

void foo()
{
    doSomething1();
    foo_approach1();
    doSomething2();
    foo_approach2();
}

Tất cả tùy thuộc vào cách tiếp cận được xác định hay không. Những gì nó làm là hoàn toàn không rõ ràng về cái nhìn đầu tiê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.