Why Ignoring Compatibility When Coding Will Cost You Later
The article explains how changing enum values in a contract approval system without handling legacy data caused failures, outlines the time gap between code deployment and data flow, and provides a systematic compatibility strategy—including retaining old enum values, semantic checks, and a pre‑release checklist—to prevent similar incidents.
New Code vs. Old Data Time Gap
When code is deployed at a point we call T, data generated before T follows the old rules, while data generated after T follows the new rules. The new code starts running at T but must also handle data that was created earlier and is still in transit.
In an OA approval process that typically takes 3–5 days, a deployment on Wednesday means that approvals submitted on Monday or Tuesday still carry the old enum values when they reach the new code.
Compatibility Solution for the Approval System
The original contract signing options were "Both Parties Sign" and "Single Sign (Our Side Only)". The new version splits the former into "Both Sign (Our Side First)" and "Both Sign (Their Side First)" while keeping the single‑sign option.
Because the old enum value remains in the database and may appear in in‑flight approvals, the solution includes two key design decisions:
Retain old enum values with comments. The legacy value is kept in the enum definition and annotated, e.g., "Historical value, still used by online processes, keep for compatibility". This prevents future developers from mistakenly removing it.
Replace exact string matches with a semantic check method. Instead of comparing the signing mode to a specific string, provide a method that determines whether the mode belongs to the "both‑sides" category.
// Determine if the signing mode belongs to both‑sides (compatible with old and new values)
public static boolean isBothSidesSign(String desc) {
return BOTH_SIDES.getDesc().equals(desc)
|| BOTH_SIDES_OURS_FIRST.getDesc().equals(desc)
|| BOTH_SIDES_THEIRS_FIRST.getDesc().equals(desc);
}This design means that adding a new signing mode later only requires updating this single method, avoiding widespread changes and reducing the risk of missed updates.
When Compatibility Is Needed
Compatibility is required whenever the changed data has "in‑flight" instances at the moment of release—i.e., data has been produced but not yet fully processed. Typical scenarios include:
Approval forms where submitted values have changed but existing approvals still carry old values.
Message queues where producers and consumers cannot be upgraded simultaneously, requiring consumers to understand both old and new message formats.
API response structure changes while clients may still cache or use older versions.
When Compatibility Is Not Needed
If there is no in‑flight data, compatibility can be omitted. Examples:
Pure internal utility methods whose signatures change together with a single release.
Simultaneous front‑end and back‑end releases without caching concerns.
Adding a new database column where existing rows have null and the new code already handles null defaults.
Gray areas require judgment; for instance, if a field’s meaning changes from A to B but the business only queries recent data, the urgency of migration may be low.
Pre‑Release Compatibility Checklist (5 Questions)
Are there accumulated old‑format messages in the queue that could be consumed after the restart?
Do any scheduled tasks scan data that might still be in the old format?
Will the new query logic match any rows that still contain old values?
Could external asynchronous callbacks (payment, logistics, approval) deliver parameters in the previous format?
Is there front‑end caching that might cause users to submit requests using the old payload?
Beyond Simple Compatibility
True robustness comes from encapsulating judgment logic in semantic methods rather than scattered exact matches. This limits the impact of external value changes to a single location.
In message‑queue scenarios, a version field in the message header lets the consumer choose the appropriate deserialization path, avoiding hard‑coded field names that would break when the format evolves.
Conclusion
The root cause of compatibility bugs is a mental bias: developers assume "new code only handles new data" while, in production, data flows independently of code versions. The moment of deployment is not a clean boundary; old data persists.
Silent failures are more dangerous than visible errors because they can corrupt data unnoticed for days. A simple habit—spending a few seconds before each release to ask whether any old data might reach the new code—prevents most compatibility incidents.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
samdeepthink
Knowledge Planet: Old Dock's Tech Chronicles Zhihu: SamDeepThinking A technical manager who still codes heavily on the front line. From junior developer to tech lead, then tech manager, now leading the whole front‑ and back‑end development team—leveling up along the way. I have some insights on programming, career development, and tech management.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
