Pre-processor usage to separate logic to different versions of product

In the .NET code base of a product, there are #if pre-processor statements to define whether certain functionality should be available. Like so:

shared logic

#if version1
    some logic
#endif
#if version2
    some other logic
#endif

Personally I twitch at this structuring. I find it harder to read, and it makes me nervous for some reason.
I was told that the reasoning behind having it like this is that there are too many changes that happen in all of the versions. If the versions were stored in separate repositories or some other method, then any future changes would have to be implemented several times over.

I find the “too much to change” argument a bit vague, but I’ll admit my experience with this kind of design is limited.

Is this really the best practice or the most common approach when it comes to handling multiple product versions with differing functionality? Is there a better alternative?

3

I’d avoid having separate code bases. All that branching and merging creates lots of unnecessary maintenance work. Even worse when they get out of sync it becomes hard to determine which differences were intentional and which by mistake.

I prefer replacing #if preprocessor directives by ordinary if statements, that way all variants of the code need to compile and refactoring tools see all the code.

Beyond this simple general change you should look into refactoring the code, but how to do that depends on the nature of the differences.

Some suggestions:

  • Don’t mix different variants within a single method. For example you could replace your code with:

    SharedPart();
    if(cond1)
        Variant1();
    if(cond2)
        Variant2();
    
  • Use delegates, interfaces or virtual methods to dispatch to the different variants instead of if statements.

  • Inject* an interface containing the configuration instead of using global state. This allows you to write unit tests for all variants without recompiling or restarting.
  • For features that are either enabled or disabled, I’d create a Feature enum, together with an injected* interface that determines if the feature is available. The interface could look something like:

    interface IGateKeeper
    {
        bool IsEnabled(Feature feature);
        void Require(Feature feature);
    }
    

    where Require checks if the feature is enabled and throws a meaningful exception otherwise.


* If you’re not familiar with dependency injection (DI) and inversion of control (IoC) learn about them.

I’ve been in this boat. It’s not fun.
It can’t always be a matter of using a runtime test:

if (IsConfigOne)
    ConfigOneImpl();
if (IsConfigTwo)
    ConfigTwoImpl();

Because of dependencies that are mutually exclusive (ie, I can’t reference ConfigOneImpl() when I’m in ConfigTwo because that would reference assemblies I don’t have in ConfigTwo…).

In general, you’re better off building an abstract class that implements most of the functionality and concrete classes that are made for each individual implementation. In your case, you might have something like this:

public abstract class SomeThing {
    public void Logic() {
        CommonLogic();
        SpecificLogic();
    }
    protected void CommonLogic() { /* ... */ }
    protected abstract void SpecificLogic();
}

public class SomeThingImplConfig1 : SomeThing {
    protected void SpecificLogic() { /* ... */ }
}

I’ve encountered other cases where this doesn’t work and was in a position where I couldn’t adapt or modify things because either didn’t own those classes or was bound by other constraints, I tried to isolate specific code in partial classes. For example, I had to build 3 different configurations of a set of classes that could work with unmanaged support routines, equivalents in pure managed code and equivalents for Silverlight, so I was able to isolate those by using 4 partials, one for all the shared code and interfaces, and 1 for each of the specific configurations.

Finally, there were some classes not available in Silverlight (System.Drawing, IIRC), but there were equivalents in Silverlight that had members with the same names (but were not compatible through an interface). Under those circumstances, I could use a C# type alias to make the Silverlight objects follow the System.Drawing equivalents:

#if SILVERLIGHT_CONFIG
using Rectangle = System.Windows.Rect;
#endif

It depends if your versioning problem is a compile-time or run-time problem.

For instance, you might want to be able to support a legacy message format in your API. That’s a run-time problem because both code paths are still available within your application and can be called arbitrarily. It requires a run-time solution: DI, ifs, delegates, etc. They are really well covered by CodesInChaos’ answer.

If you only want to build an older version of your code for legacy purpose, then the solution is clearly a compile-time solution. Even in a very large code base, I would still prefer to depend on a source control system for this and not on pre-processor conditions.

I would choose this path because, to my experience, the reasoning you mentioned is flawed. It doesn’t matter how you store your many code-bases. Whether they are in different files or the same one, you still have multiple versions of the same code to maintain. Merging should not be an issue because changes to incompatible logic will never reflect one to one in a different code-base. It needs to be implemented independently. If it does, then your logic is not incompatible which indicates an architecture/factoring problem in your code. Aka, you are not reusing everything that you can.

On the other side, dealing with twice as much code in a function, separated through pre-processor conditions that have no concept of scope, quickly turns code into a huge and unreadable mess. You can end up having conditions that crosses scopes and the only way to understand what is really going on is by doing the pre-processor’s job yourself. After-all, there is a reason why C++ developer considers the pre-processor like a demon that should have never existed. The only solution to this problem is the use of run-time if conditions to force the use of scoped logic. I would still avoid going this way because they can have a strong negative impact on performances if they are used in a critical path.

In the end, it all boils down to using the right tool for the job and an automatic text replacement tool is not the right tool to handle code versioning.

1

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật