Chapter 4: Eliminate 'Exceptional Cases'
2026.08.10Prologue: The Special Customer Who "Never Participates in Campaigns"
In the "Starfish E-commerce" platform's user table (t_user), there is a special account with an ID of 88888. (The case in this chapter is a synthetic example.) This account belongs to the company's CEO. The CEO is a tech enthusiast who likes to personally experience the company's products, but he doesn't want his account to receive any marketing texts, coupon pushes, or to have his orders used by the data analytics team for user profiling.
The earliest implementation of this requirement was done by a former employee who has since left. In all relevant code, they added this "simple" check:
// CouponService.java
public void sendCouponsToActiveUsers(List<User> users) {
for (User user : users) {
if (user.getId() == 88888) { // CEO's account, skip it.
continue;
}
// ... logic to send coupons
}
}
// SmsService.java
public void sendPromoMessage(List<User> users, String message) {
for (User user : users) {
if (user.getId() == 88888) { // Do not disturb the boss.
continue;
}
// ... logic to send marketing SMS
}
}
// AnalyticsService.java
public void generateUserPortraits(Date date) {
List<Order> orders = orderRepository.findByDate(date);
for (Order order : orders) {
if (order.getUserId() == 88888) { // Exclude CEO's data from analytics.
continue;
}
// ... analyze order data
}
}
This code worked well for the first few years. The ID 88888 lurked like a ghost in various corners of the system, silently guarding the CEO's peace. Everyone on the team knew about this "customary" rule, and new hires were given this piece of "secret knowledge" verbally by existing team members.
Until one day, the company brought in a new CTO, whose user ID was 99999. He made the same request as the CEO. So, the team members embarked on a "treasure hunt," needing to find all the places in the code with == 88888 and change them to == 88888 || user.getId() == 99999.
But the disaster didn't end there. The legal department proposed that for compliance, all "risk users" under investigation by regulatory bodies must also immediately cease all marketing activities and be excluded from regular data analysis. Now, this "special user list" had become a dynamically changing set, maintained by the legal and risk control departments.
At this point, the code had become unbearably ugly:
// CouponService.java
public void sendCouponsToActiveUsers(List<User> users) {
List<Long> specialUsers = Arrays.asList(88888L, 99999L); // C-level executives
List<Long> riskUsers = riskManagementService.getCurrentRiskUserIds(); // From another service
for (User user : users) {
if (specialUsers.contains(user.getId()) || riskUsers.contains(user.getId())) {
continue;
}
// ...
}
}
Every execution of the sendCouponsToActiveUsers method might require an RPC call to riskManagementService, causing serious performance issues. Worse still, the developers of SmsService and AnalyticsService each had to independently re-implement this same complex logic. The system's consistency and maintainability were being mercilessly eroded by this seemingly small "exceptional case" requirement.
This user.getId() == 88888 judgment is the protagonist of this chapter -- a hard-coded "exceptional case" in the code. It's like a tiny cancer cell, seemingly harmless at first, but over time, as requirements evolve, it replicates and spreads, eventually causing the healthy tissue of the system to become diseased.
In this chapter, we will learn how to identify and eradicate these "exceptional cases." We will delve into a core idea: any "except for..." logic in the code signals the absence of a data model. Our goal is to learn how to replace special-case judgments with general rules, and the only way to achieve this is by data-ifying business rules.
Section 1: Beware of Exceptions in Code -- When "Except For..." Appears, It Means Data is Missing
In software engineering, the word "Exception" usually refers to a runtime error. But in this chapter, we give it a broader meaning related to business logic:
A business logic "exceptional case" is a segment of hard-coded judgment logic added to the code specifically to handle a particular, non-standard, edge-case business scenario that does not conform to the general rule.
These exceptional cases in code typically manifest as:
- Magic value checks:
if (userId == 88888),if (city == "Beijing"),if (productSku == "SPECIAL-GIFT-001") - Special date checks:
if (LocalDate.now().getMonth() == Month.DECEMBER && LocalDate.now().getDayOfMonth() == 25) - Hard-coded configuration or flags:
private static final boolean USE_NEW_ALGORITHM = false;
Why are we so wary of these "exceptional case" logic patterns? Because they are a strong "bad smell," revealing a deeper flaw in our design.
The Essence of "Exceptional Cases": A Missing Data Dimension
Let's go back to the "CEO doesn't participate in campaigns" example. The line if (user.getId() == 88888) superficially checks the user ID, but what is the real business intent behind it?
The intent is: "Check whether this user has the attribute of 'do not disturb'."
ID=88888 is just a specific instance of this attribute at a particular point in time. The code mistakenly equates the "intent" with that specific "instance," hard-coding the instance itself.
This is a severe data model deficiency. In our User model, there is simply no place to record the business fact of "whether this user needs do-not-disturb." Because this dimension is missing from the data model, the code is forced to "fabricate" this fact out of thin air using an if statement.
When a business rule (a user needing special treatment) cannot be clearly and affirmatively expressed at the data level, it can only appear in the code logic as an "exceptional case patch."
In other words: every "except for..." points to a missing or unmodeled "what is."
- "Send coupons to all users, except for user with ID 88888" -> Points to a missing data dimension: "Is the user a do-not-disturb user?"
- "Give a 10% discount on all products, except for SKU
SPECIAL-GIFT-001" -> Points to a missing data dimension: "Does the product participate in the discount?" - "Free shipping nationwide, except for Xinjiang and Tibet" -> Points to a missing data dimension: "Is the shipping address in a remote area?"
An "exceptional case" in the code is like a puddle of water on the floor. An inexperienced repairman might choose to mop it up (add more else if), without finding and fixing the leaking pipe. A good engineer, on the other hand, sees the puddle as a signal that your data pipeline is leaking somewhere.
The Five Sins of Hard-Coded "Exceptional Cases"
Why must we be so resolute about eliminating these hard-coded exceptional cases? Because they systematically destroy software quality in five ways:
Eroding Knowledge Integrity
- Problem: The important business knowledge about "which users are do-not-disturb users" is not stored centrally and authoritatively in the database, but is fragmented across various service codes.
- Consequence: No one can easily answer the question "how many types of special users do we have in our system?" Knowledge becomes dependent on programmers' "human memory." When personnel change, this knowledge is permanently lost.
Tightening Logic Coupling
- Problem:
CouponService,SmsService, andAnalyticsService-- three services that should be unrelated -- are now implicitly coupled by the hard-codeduserId == 88888. - Consequence: When the "do not disturb" business rule changes (e.g., adding the CTO's account), you need to modify all three services simultaneously. This synchronous modification across services is a major taboo in microservices architecture, as it easily leads to inconsistency and omissions.
- Problem:
Reducing Testability
- Problem: How do you write a comprehensive unit test for the
sendCouponsToActiveUsersmethod? You need tomocka user object with ID 88888 in your test case. Your test code is now also dependent on this magic number. - Consequence: If the business rule changes to "all users with
user_level > 5are do-not-disturb," all test cases relying onID=88888will break and need rewriting. Tests become fragile because they test a specific "instance" rather than a general "rule."
- Problem: How do you write a comprehensive unit test for the
Stifling Business Flexibility
- Problem: When the legal department needs to dynamically and frequently add or remove "risk users," the hard-coded model completely fails. The business side cannot manage this list themselves. Every change must be converted into a technical requirement, submitted as a ticket, scheduled, developed, tested, and deployed.
- Consequence: The technology department becomes a bottleneck for business development. An operation that should take an operations or legal person seconds to complete is stretched into a development process spanning days or weeks.
Breeding Technical Debt
- Problem: The existence of one "exceptional case" dramatically lowers the psychological barrier for later developers to add new "exceptional cases." When a developer sees
if (userId == 88888), they naturally add|| userId == 99999after it. - Consequence: This is the "broken windows effect." The first hard-coded exceptional case opens the floodgates to chaos. The code will go further and further down this wrong path until one day the cost of refactoring becomes unbearably high.
- Problem: The existence of one "exceptional case" dramatically lowers the psychological barrier for later developers to add new "exceptional cases." When a developer sees
Therefore, eliminating "exceptional cases" is not a code-cleanliness obsession, but an engineering practice with high economic value that takes responsibility for the system's long-term health. Our goal is to let code return to its purest responsibility -- executing general, stateless logic -- while handing over all special, stateful "judgments" to data.
Section 2: Replace Hard-Coded with Configuration Tables -- Data-ifying Business Rules
If hard-coded "exceptional cases" are poison, what is the antidote? The answer was hinted at in the previous chapter, and here we will deepen and generalize it: data-ifying business rules.
This process is like evolving from "orally transmitted imperial decrees" to "promulgated written codes." An "imperial decree" (hard-coded) is temporary, opaque, and volatile. A "code" (configuration table) is persistent, public, and has clear revision procedures.
Let's use the "do-not-disturb user" case to fully demonstrate how to implement rule data-ification.
Refactoring Step One: Identify the Rule and Model It
First, we need to abstract the general rule behind the if (userId == 88888) "instance."
- Rule Name: User tag or user attribute.
- Rule Description: The system needs a mechanism to tag users with different types of labels, and downstream services can change their behavior based on these tags.
- Rule Dimensions:
user_id: The user the tag is applied to.tag_name: The name of the tag, e.g.,DO_NOT_DISTURB,RISK_ACCOUNT,VIP_LEVEL_1.source: Who applied this tag, e.g.,CEO_SPECIAL_RULE,LEGAL_DEPARTMENT.expiry_time: The validity period of the tag.- ...
Based on this model, we can design a new database table:
t_user_tag table:
| Field Name | Type | Meaning |
|---|---|---|
id | bigint | Primary Key |
user_id | bigint | User ID (indexed) |
tag_name | varchar | Tag Name (indexed), e.g., 'DO_NOT_DISTURB' |
tag_value | varchar | Tag Value (optional, for more complex tags) |
source | varchar | Source system or department |
created_at | datetime | Creation time |
expires_at | datetime | Expiration time (null means never expires) |
This table is the "home" we built for the missing data dimension of "do not disturb." It is a generic tagging system, far more powerful than just solving the "CEO do-not-disturb" problem.
Refactoring Step Two: Data Migration and Population
Next, we need to move the "tacit knowledge" hidden in the code into this new table explicitly.
Tag the CEO and CTO:
INSERT INTO t_user_tag (user_id, tag_name, source, expires_at) VALUES (88888, 'DO_NOT_DISTURB', 'MANUAL_ADMIN', NULL); INSERT INTO t_user_tag (user_id, tag_name, source, expires_at) VALUES (99999, 'DO_NOT_DISTURB', 'MANUAL_ADMIN', NULL);Refactor the risk control system:
- Before:
riskManagementService.getCurrentRiskUserIds()returned aList<Long>. - Now: When the risk control system marks a user as a risk, instead of maintaining its own internal list, it calls the
UserTagServiceinterface to tag the user with'RISK_ACCOUNT'.
// RiskManagementService.java (New version) public void markUserAsRisk(long userId, String reason) { userTagService.addTag(userId, "RISK_ACCOUNT", "FRAUD_DETECTION_SYSTEM", reason, null); }- Before:
Through this step, we consolidate all "special user information" -- previously scattered in hard-coded checks and the risk control system's internal list -- into the t_user_tag table, which becomes the single source of truth.
Refactoring Step Three: Refactor Business Logic
This is the most critical step. We will completely delete those if (userId == ...) checks and replace them with calls to UserTagService.
Create UserTagService
This service encapsulates access to the t_user_tag table and provides efficient query interfaces. To avoid N+1 query problems and frequent database access, it should have an internal caching mechanism.
@Service
public class UserTagService {
@Autowired
private UserTagRepository userTagRepo;
// Use Caffeine or Redis for caching
@Cacheable(value = "userTags", key = "#userId")
public Set<String> getTagsForUser(long userId) {
List<UserTag> tags = userTagRepo.findActiveTagsByUserId(userId);
return tags.stream().map(UserTag::getTagName).collect(Collectors.toSet());
}
public boolean userHasTag(long userId, String tagName) {
return getTagsForUser(userId).contains(tagName);
}
// ... addTag, removeTag and other methods
}
Refactor Downstream Services
Now, the code for CouponService, SmsService, and AnalyticsService becomes astonishingly concise and generic.
CouponService.java (after refactoring):
public void sendCouponsToActiveUsers(List<User> users) {
for (User user : users) {
// The rule changes from "checking a specific person" to "checking an abstract attribute"
if (userTagService.userHasTag(user.getId(), "DO_NOT_DISTURB") ||
userTagService.userHasTag(user.getId(), "RISK_ACCOUNT")) {
continue;
}
// ... logic to send coupons
}
}
The transformation of SmsService and AnalyticsService is identical.
Wait, there still seems to be duplicate code?
The string literals "DO_NOT_DISTURB" and "RISK_ACCOUNT", along with the || logic, are repeated across multiple services. This indicates our abstraction is not yet complete. This repeated logic itself represents a higher-level business rule: "Which tags mean a user should be excluded from marketing activities?"
Refactoring Step Four: Re-Abstracting the Rules -- Introducing "Metadata"
The rule "which tags should not participate in marketing" is itself volatile. Perhaps in the future, a new 'INTERNAL_TEST_ACCOUNT' tag will also need to be excluded. We should not hard-code this || logic in Java code.
It's time to introduce another configuration table, which we'll call a "metadata configuration table."
t_tag_behavior_mapping table:
id | tag_name | behavior_flag |
|---|---|---|
| 1 | DO_NOT_DISTURB | EXCLUDE_FROM_MARKETING |
| 2 | RISK_ACCOUNT | EXCLUDE_FROM_MARKETING |
| 3 | RISK_ACCOUNT | EXCLUDE_FROM_ANALYTICS |
| 4 | INTERNAL_TEST_ACCOUNT | EXCLUDE_FROM_MARKETING |
| 5 | INTERNAL_TEST_ACCOUNT | EXCLUDE_FROM_ANALYTICS |
This table defines the mapping between "tags" and "system behaviors." It is a "rule of rules."
Now, our service logic can reach its final elegant form:
MarketingDecisionService.java (new service):
@Service
public class MarketingDecisionService {
@Autowired
private UserTagService userTagService;
@Autowired
private TagBehaviorRepository tagBehaviorRepo;
@Cacheable(value = "marketingExclusionTags")
public Set<String> getMarketingExclusionTags() {
return tagBehaviorRepo.findTagsByBehavior("EXCLUDE_FROM_MARKETING");
}
public boolean isExcludedFromMarketing(long userId) {
Set<String> userTags = userTagService.getTagsForUser(userId);
Set<String> exclusionTags = getMarketingExclusionTags();
// Check if the two sets have an intersection
return !Collections.disjoint(userTags, exclusionTags);
}
}
CouponService.java (final version):
public void sendCouponsToActiveUsers(List<User> users) {
for (User user : users) {
if (marketingDecisionService.isExcludedFromMarketing(user.getId())) {
continue;
}
// ... logic to send coupons
}
}
Now, CouponService is completely decoupled from specific tag names and specific exclusion logic. It only cares about a very high-level business question: "Should this user be excluded from marketing activities?" All the complex judgment is encapsulated in MarketingDecisionService, whose decision basis comes entirely from database configuration tables.
Let's review this perfect evolutionary path:
- Initially: Hard-coded
if (userId == 88888). - First refactoring: Introduced the
t_user_tagtable, code becameif (userTagService.userHasTag(id, "TAG_A") || ...). Eliminated magic numbers, but rule logic was still in the code. - Second refactoring: Introduced the
t_tag_behavior_mappingtable, code becameif (marketingDecisionService.isExcludedFromMarketing(id)). Data-ified the rule logic as well.
Ultimately, the code in CouponService has become so generic that it might never need modification for years, even if the company's marketing exclusion rules undergo drastic changes.
Empowering the Business: From "Technical Requirements" to "Backend Operations"
The greatest value of this refactoring is empowerment.
- Scenario: The legal department needs to add a new "internal audit account," ID
77777, which also needs do-not-disturb. - Operation: An operations person (or designated admin) logs into the backend management system, goes to the "User Tag Management" page, and adds an
'INTERNAL_TEST_ACCOUNT'tag for user77777. - Scenario: Management decides that the
'INTERNAL_TEST_ACCOUNT'tag should not only be excluded from marketing but also from data analysis. - Operation: The admin, on the "Tag-Behavior Mapping" page, adds a new mapping from
'INTERNAL_TEST_ACCOUNT'to'EXCLUDE_FROM_ANALYTICS'.
All these changes are made on a user-friendly web interface, requiring no code changes and no application release. The change cycle for rules shrinks from "days/weeks" to "seconds/minutes." This is true technology-driven business agility.
Chapter Summary: Rules Should Be Queried, Not Executed
In this chapter, we declared war on hard-coded "exceptional cases" in code. We first defined what a business logic "exceptional case" is, pointing out that any "except for..." judgment strongly suggests a missing dimension in our data model.
We dissected the "five sins" of hard-coded exceptional cases: they erode knowledge, tighten coupling, reduce testability, stifle flexibility, and ultimately become a breeding ground for technical debt.
To eradicate this persistent problem, we proposed and practiced an effective refactoring method: replacing hard-coded checks with configuration tables. This process consists of four key steps:
- Identify the rule and model it: Abstract a general "rule" from a specific "instance" and design a data table for it (e.g.,
t_user_tag). - Data migration and population: Move the tacit knowledge scattered in code to the new configuration table, establishing a single source of truth.
- Refactor business logic: Delete hard-coded judgments and replace them with calls to a generic service that encapsulates data querying and caching.
- Re-abstract the rules: When the rules themselves become complex, introduce a "metadata" configuration table (e.g.,
t_tag_behavior_mapping) to data-ify the rule logic as well.
Through this process, we arrived at a profound conclusion: good code should have logic that is generic and stable. Variable, special business rules should be stored as data, queried by the code at runtime, and not directly executed by the code.
The next time you see a colleague write something like if (order.getChannel() == "douyin") during a code review, kindly remind them. This line of code itself is an alarm bell for an "exceptional case." Guide them to think:
- What makes the "Douyin channel" special? Is it different billing? Different revenue sharing?
- Can we abstract this "difference" into a generic attribute and record it in a "Channel Configuration Table"?
- Can the code be modified to
if (channelConfig.needsSpecialBilling(order.getChannel())), so that specific channel names ("douyin") disappear from the business logic code?
Having completed the micro-level decompression surgeries for "fields" and "exceptional cases," our code is now free of many of the most common "bad smells." However, complexity can also lurk at a higher dimension -- in the relationships between tables.
In the next part, "Mesoscopic Decompression," we will shift our focus from individual fields to the design of entire table structures. We will explore a more challenging topic: how to handle the "time" dimension? How to design tables that cleanly separate "current state" from "historical intent," thereby building an unassailable access control and audit system.