寻求无持续集成的软件开发方案:规避特性移除难题
Great question—this is a super common pain point with CI that doesn’t get enough airtime. The core issue here is that merging incomplete or interdependent features into the main trunk early creates tight coupling that’s a nightmare to untangle later. Here are some proven alternatives and adjustments to CI workflows that solve this problem:
1. Feature Flags (特性开关)
This is the most straightforward fix for this exact scenario. Instead of merging Feature A’s code directly into the main branch and making it active immediately, wrap all of A’s logic behind a runtime toggle.
- When developing Feature B, you can choose to use A’s functionality only when the flag is enabled (so B doesn’t hardcode dependencies on A’s code).
- If you later need to remove Feature A, you just delete the flag and all associated code—no need to refactor Feature B, as it was never relying on A being present by default.
Example snippet:
// Feature A code wrapped in a flag if (featureFlags.isEnabled("feature_a")) { initiateFeatureAWorkflow(); } // Feature B code that uses A conditionally if (featureFlags.isEnabled("feature_a")) { const data = fetchFeatureAData(); processWithFeatureBLogic(data); }
2. Branch by Abstraction
Popularized by Martin Fowler, this approach is perfect for larger, more complex features. Instead of merging Feature A directly into main, first introduce an abstraction layer (interface, base class, or service contract) into the main trunk that defines what Feature A will provide.
- Feature A is developed in a separate branch that implements this abstraction.
- Feature B is developed against the abstraction, not A’s concrete implementation.
- If you need to remove Feature A later, you either swap in a different implementation of the abstraction or deprecate the abstraction entirely—Feature B’s code remains untouched because it never depended on A specifically.
3. Ephemeral Feature Branches with Delayed Trunk Merges
You don’t have to abandon CI entirely—just adjust when you merge features into the main trunk. Instead of merging Feature A as soon as it’s done, keep both A and B in their own short-lived branches until both are fully tested and validated.
- Regularly sync the main trunk into both branches to avoid merge conflicts.
- Once both features are ready, merge them into main together (or one after the other, but only when both are stable).
- If you decide to scrap Feature A mid-development, you just discard its branch—Feature B’s code is completely unaffected, since it never integrated A’s changes.
4. Monorepo with Strict Dependency Boundaries
If you’re working in a monorepo, enforce clear module boundaries between features. Use tools like module systems (Node.js modules, Go modules) or static analysis to ensure Feature B only interacts with Feature A’s public, stable API—not its internal implementation.
- When removing Feature A, you only need to update or remove the public API, and adjust Feature B if it was using that API (but the scope is limited to the API surface, not tangled internal code).
- This keeps coupling loose and makes removals far more manageable.
A Quick Note on CI
CI itself isn’t the enemy here—it’s the practice of merging incomplete, unvalidated features into the main trunk prematurely. All of these strategies work alongside CI (you can still run tests on feature branches, flag-enabled code, etc.) while avoiding the "merge first, fix later" trap that causes the removal headache you’re describing.
内容的提问来源于stack exchange,提问作者Ken Taylor

