FORM NOT VOID, MIND NO CORE

Chapter 2: The Theory of Conceptual Compression

2026.08.10

Prologue: The Ghost of Information Theory

In 1948, Claude Shannon published "A Mathematical Theory of Communication," which laid the foundation of modern information theory and planted a meaningful seed for the software complexity problems we face today.

One of Shannon's core insights is that information can be "compressed." By finding redundancy and patterns in data, we can represent the same information using fewer bits (under the setting of lossless coding; lossy compression adds a separate trade-off on distortion). For example, the English letter e appears far more frequently than z. Entropy coding (such as Huffman coding) will use a shorter code for high-frequency symbols and a longer code for low-frequency symbols, thereby reducing the overall size of the text.

This idea has profoundly influenced every corner of computer science. From ZIP files to JPEG images, from MP3 music to MP4 video, compression technology is the bedrock of our digital world. It has been so successful that we software engineers, almost by osmosis, have come to enshrine "compression" as a guiding principle. We don't just compress files; we try to "compress" our data models, our business concepts.

The user.status field we saw in Chapter 1 is one botched attempt at "conceptual compression." We tried to use a 32-bit integer to encode dozens of unrelated business facts that might befall a user. We naively believed this was "efficient" and "simple."

Yet we overlooked a crucial premise of this theory: lossless compression must use the same "codebook." When you compress a file, the decompression program must know precisely how you replaced high-frequency words with short codes and low-frequency words with long codes. That "codebook" is the key to lossless recovery of information.

In a software system, what is this "codebook"? It exists in no single file. Rather, it lives vaguely and fragmentarily in the minds of each programmer, in long-outdated requirement documents, in code comments, and in the branches of if/else statements.

When team members turn over, when requirements are forgotten, this "codebook" is lost. Later maintainers, like cryptographers without a key, face ciphers such as status=10 and can only guess, infer, and resort to trial and error. Every modification feels like a gamble.

In this chapter, we will formally define "conceptual compression," a core anti-pattern in software development. We will show that software complexity does not originate from the business itself, but from our bad habit of forcibly binding together things that change at different rhythms. Then we will propose the corresponding remedy -- "conceptual decompression," a powerful technique that makes implicit logic explicit by embracing "data redundancy." Finally, we will provide a clear set of criteria telling you when you should unhesitatingly pick up the scalpel and perform a "decompression" on your data model.


Section 1: Definition of Compression -- Forcibly Binding Things with Different Rhythms of Change

Let us first give a formal definition of "conceptual compression":

Conceptual compression is the act, during data modeling, of forcibly encoding multiple independent business concepts -- each with its own distinct frequency and cause of change -- into a single data carrier (such as one field, one table, or one object).

The heart of this definition is "different rhythms of change." This is precisely the most fundamental difference between "conceptual compression" in the software domain and "data compression" in information theory: data compression deals with static, unchanging patterns, whereas software systems deal with dynamic, evolving businesses. Data compression is lossless in the information-theoretic sense; "conceptual compression" is lossy in the cognitive sense -- which is precisely why it is an anti-pattern.

To make this more vivid, let us consider a real-world analogy.

Analogy: The Marriage of a Universal Remote and a Coffee Cup

Imagine you are a "life hacker" who pursues ultimate "efficiency." You discover that in your living room, the two things you use most often are the TV remote and your coffee cup. To avoid searching for the remote everywhere, you make a "genius" decision: with super-strong glue, you permanently fasten the remote to the side of your coffee cup.

(This is an imagined image, provided for illustrative purposes)

On the surface, this is a splendid piece of "reuse" and "compression." You have "compressed" two functional entities into one. Now, whenever you pick up the coffee cup, the remote is right there. How elegantly simple!

But disaster strikes soon enough.

  1. Inconsistent frequency of change

    • Coffee cup: You wash it every day. Now, every time you wash the cup, you must be extremely careful to keep the remote dry. This dramatically increases the complexity and risk of cleaning.
    • Remote: Its batteries may only need replacing every few months. But now, because it is constantly "moved" and "washed" along with the cup, it wears out far faster, and a single accidental splash could destroy it.
  2. Inconsistent cause of change

    • You decide to upgrade your TV to a new brand. The new TV comes with a new remote. What do you do now? You must painstakingly pry the old remote off the cup, leaving ugly glue marks behind, and then glue the new one on. An upgrade to the "TV system" unexpectedly "pollutes" your "drinking system."
    • Conversely, you accidentally break the coffee cup. To keep watching TV, you are forced to immediately buy an identical cup and glue the remote onto it. A simple "cup replacement" event causes a temporary paralysis of the "TV control system."

The analogy makes the point: conceptual compression appears to save on the carrier at compression time, but every read pays a cognitive "decompression" cost -- where is the codebook? This is the portrait of "conceptual compression."

  • Coffee cup: It represents one business concept, such as the user's "lifecycle state." It has its own rhythm of change (daily washing vs. user registration/deactivation).
  • Remote: It represents another business concept, such as the user's "access control state." It, too, has its own rhythm of change (battery replacement vs. risk-control locking/unlocking).

The user.status field is that tube of "super-strong glue." It forcibly fuses together two concepts whose rhythms and causes of change are completely different. When the "Risk Control Department" wants to add a new type of lock (equivalent to adding a button to the remote), they must modify the core definition of the user's "lifecycle," which inevitably affects every module that depends on the user's lifecycle (such as the registration, login, and deactivation flows).

"Different Rhythms" in the Software World

Let us apply this idea to software practice. Different business concepts in a system usually have their rhythms of change driven by different external forces, which may come from:

  • Different organizational departments:

    • The finance department may adjust invoice and billing rules once a year.
    • The marketing department may roll out new campaign rules every week.
    • The legal department may update user agreements and privacy policies irregularly in response to new regulations.
    • If we compress "billing type" and "marketing campaign tag" into the same order_type field, then every whim of the marketing department may require finance colleagues to join in review and testing, for fear it could affect billing logic. This is coupling at the level of organizational structure.
  • Different external systems:

    • A payment channel's API may be upgraded once a year.
    • A logistics company's API may change at any time because of a switch in service providers.
    • The SMS gateway's pricing and template rules may be adjusted every quarter.
    • If we use a single channel_config table with a type field to store all of this information, then when we merely want to edit one SMS template, we find ourselves sharing the same configuration table with an ongoing payment-channel integration project. This inevitably increases the risk of change and the likelihood of conflicts.
  • Different business lifecycles:

    • A user's core identity information (name, ID number), once established, is almost permanently immutable.
    • A user's shipping address changes frequently.
    • A "coupon" earned by a user in a particular campaign has a short-lived, one-time lifecycle.
    • If we place both the user's "default shipping address ID" and "this campaign's bound coupon ID" in the core user_profile table, that is a form of conceptual compression. The former is long-term state; the latter is transient state. A better approach is to create an independent activity_coupon_binding table. When the campaign ends, that table's data can be safely archived or deleted without "polluting" the core user table.

The essence of conceptual compression lies in ignoring "time" and "change" -- the two most central dimensions of a software system. It tries to describe a dynamic, multi-dimensional business world with a static, flat data structure. The shortsightedness of such a design is inevitably repaid in full over the course of system evolution, in the form of "code decay" and a "maintenance hell."


Section 2: Definition of Decompression -- Making Implicit Logic Explicit Through Data Redundancy

If "compression" is the root of the problem, then "decompression" is our remedy.

Conceptual decompression is the act of providing an independent storage space for each independently evolving business concept by adding data carriers (such as additional fields or tables). This transforms the implicit business logic that was hidden in code and required "translation" into explicit business facts that can be queried directly from the database.

The heart of this definition is "making implicit logic explicit." This is a fundamental shift in mindset: we no longer trust the intricate judgments embedded in code; we trust only the raw facts recorded in the database.

Let us return to the user.status example and walk through the full process of "decompression."

Original state (highly compressed):

  • Data model: The t_user table has a status field.
  • Implicit logic (the codebook):
    • status = 3 means "the user cannot log in."
    • status = 10 means "the user is from Channel A" AND "not verified."
    • status = 11 means "the user's profile cannot be modified."
  • Code manifestation: Numerous if (status == ...) checks scattered across various services.

The decompression process:

This is a process of "peeling the onion." We identify, one by one, the independent concepts that were compressed together, and give each its own "home."

First layer of decompression: separate "access control"

  • Identify the concept: "Can log in" and "can trade" constitute an independent "access control" concept, whose changes are driven by the risk control department.
  • Data change: Add an access_control_state field to the t_user table (or the more granular is_login_locked and is_trade_locked fields).
  • Making the logic explicit:
    • Before (implicit): The code must "know" that status=3 means "locked." if (user.getStatus() == 3)
    • After (explicit): The data directly "tells" the fact. if (user.isLoginLocked())
  • Benefit: The risk control department can now evolve its locking strategy independently (for instance, adding a "comment lock"), simply by extending access_control_state, without affecting the user's core lifecycle at all.

Second layer of decompression: separate "source" and "verification"

  • Identify the concepts: "User source" is a statistical dimension for the marketing department; "verification status" is a process dimension for the compliance department. Their rhythms and causes of change are completely different.
  • Data change: Add source_channel and verification_status fields to the t_user table.
  • Making the logic explicit:
    • Before (implicit): The code must "decode" the dual meaning of status=10. if (user.getStatus() == 10)
    • After (explicit): The data plainly states two independent facts. if (user.getSourceChannel().equals("A") && user.getVerificationStatus().equals("PENDING"))
  • Benefit:
    1. Liberation of queries: We can easily query "all unverified users" (WHERE verification_status = 'PENDING') or "all users from Channel A" (WHERE source_channel = 'A'), without maintaining an ever-growing list of statuses.
    2. Preservation of information: When a user completes identity verification, we need only change verification_status from PENDING to VERIFIED. The information in the source_channel field is preserved in full, opening the door to future data analysis.

Third layer of decompression: separate "temporary processes"

  • Identify the concept: "Marketing campaign freeze" is a temporary process state with clear start and end times; it should not belong among the user's permanent attributes.
  • Data change: Add a new table t_user_process_lock, containing fields such as user_id, lock_type, lock_reason, start_time, and end_time.
  • Making the logic explicit:
    • Before (implicit): The code must check a magic number status=11 and also rely on another mechanism (such as a scheduled task) to handle setting and restoring the state. The context of the whole process is fragmented.
    • After (explicit): The t_user_process_lock table itself is a complete, self-explanatory record of a business fact. By querying SELECT * FROM t_user_process_lock WHERE user_id = ? AND lock_type = 'PROFILE_UPDATE' AND NOW() BETWEEN start_time AND end_time, we obtain an unambiguous answer to "whether the current user's profile is locked."
  • Benefit: The core User service is completely decoupled from volatile marketing campaigns. The marketing department can freely "freeze" users by inserting rows into t_user_process_lock, without touching any core code. That table also naturally serves as an audit trail of marketing campaign operations.

The Essence of Decompression: From "Computing" State to "Recording" Facts

Notice this shift. The philosophical core of "conceptual decompression" is this:

We should go to great lengths to make our systems "record" business facts that have already occurred and are immutable, rather than "computing" or "inferring" what the current state should be, on demand, from scattered state markers.

  • status = 10 is a state marker that requires "inference." You must know its historical context to infer that it represents "Channel A" and "not verified."
  • source_channel = 'A' and verification_status = 'PENDING' are two business facts that have been "recorded." They are atomic and self-explanatory, requiring no inference at all.

The most intricate if/else mazes in code almost always arise from our attempts to "infer" state at runtime. "Decompression" pushes that inference logic forward to the point of data writing: by using richer data structures, we directly record the results of the inference -- the most primitive business facts themselves.

The consequence is that our "write" model may become slightly more complex (for example, at registration we might need to initialize source_channel based on the user's origin), but our "read" model becomes radically simpler. Given that in a typical business system the number and complexity of read operations far exceed those of writes, this trade is exceptionally worthwhile. It front-loads the complexity, settling it once at write time, so that countless subsequent reads and judgments can all enjoy the dividend of simplicity.

The Reputation of "Redundancy"

For a long time, "redundancy" has been a pejorative term in database design. We have been repeatedly taught to follow normalization and eliminate redundancy. But we must draw a strict distinction between two kinds of redundancy:

  1. Harmful redundancy: Storing the same business fact in multiple places. For example, storing the user's shipping address in both the order table and the shipment table. When the user changes their address, you must update both places, which can lead to data inconsistency. This is what we should strive to avoid.
  2. Beneficial redundancy: Data copies or derived data deliberately introduced for the sake of decoupling and performance. For example, redundantly storing a product_name field in the order table so that querying an order list does not require a JOIN against the product table each time. Going further, the "decompression" advocated by this book is, at bottom, "conceptual redundancy" -- we are willing to "waste" more fields or tables to give each independent concept a redundant, dedicated storage space.

What "conceptual decompression" embraces is the second kind of "beneficial redundancy." It is not mere data duplication, but a strategic investment -- trading a little space for a great deal of time (cognitive time). We "redund" a few fields, and in return we reap the immense reward of a clear, maintainable system for years to come.


Section 3: Judgment Criteria -- When Should You Split?

"The Theory of Conceptual Compression" gives us a powerful theoretical weapon, but theory must ultimately land in practice. In day-to-day development and code review, we need a set of simple, actionable heuristics to help us identify the bad smells most in need of "decompression."

The following three signals are strong warnings to "split." When you see any one of them, you should stop and re-examine your data model.

Signal One: Inconsistent Frequency of Change

This is the most fundamental and central criterion. When you find that different parts of a data carrier (field or table) are modified by business events occurring at different frequencies, it is a clear compression point.

How to spot it?

  • Observe the code: Examine every setter method of an entity object. If a method like setUserStatus() is invoked across completely unrelated business scenarios -- "user registration," "risk-control lock," "marketing campaign start," "manual modification by an admin" -- then the status field is almost certainly a "Swiss-Army-knife field" compressing multiple frequencies of change.
  • Scrutinize the requirements: When a product manager raises a new requirement, ask yourself: "Which business domain does this change belong to? Could the data it modifies also be affected by changes from other domains?"
    • Example: The product manager asks, "Add an 'Annual Member' badge to the user's profile page."
    • Wrong approach: Add a display_badge_type field to the user table, where 1=Annual Member, 2=Founding Member... Then another requirement arrives: "Show a 'Shopaholic' badge for users who joined the Black Friday campaign." So display_badge_type gains new values.
    • Analysis: The "Annual Member" status changes on a yearly scale, driven by the payment and membership systems. The "Shopaholic" badge changes on a "campaign cycle" scale, driven by the marketing system. This is a textbook case of inconsistent frequency of change.
    • Correct approach (decompression):
      1. The t_user table has a membership_level field, maintained by the membership system.
      2. Create a t_user_activity_badge table, written to by the marketing system during campaigns.
      3. The user profile page aggregates information from these two sources when rendering.

Code Review check:

"Are the triggers for modifying this field coming from multiple business domains? If so, should we split it so that each domain only touches its own data?"

Signal Two: The Presence of "Exceptional Case" Logic

When you see large amounts of if/else if/else in the code, or frequently encounter words like "but," "except," and "unless" in requirement documents and comments, that is a strong signal that your data model can no longer describe the business reality positively and affirmatively -- it can only keep slapping "patches" onto every "exception."

Every "exception" is, in essence, a "dimension" that the current data model has ignored.

How to spot it?

  • Keyword search: Search your codebase for else if and switch/case. When a switch has more than three case branches, and those branches contain complex business logic rather than simple value matching, it is usually a bad smell.
  • Listen to the business language: In requirement review meetings, pay close attention to what the product manager says.
    • "All Gold members get a 10% discount, except for the book category."
    • "Orders are shipped automatically after payment, unless it is a pre-sale order, which ships only after the pre-sale period ends."
    • "All users can draw three times a day, but during the anniversary, VIP users can draw an additional five times."

Case analysis: "Except for the book category"

  • Poor model: Hard-coding the category check into the discount logic.

    // DiscountService.java
    public Money applyDiscount(User user, Order order) {
        if (user.isGoldMember()) {
            if (order.getCategory().equals("BOOK")) { // "Exceptional case" logic
                return order.getOriginalPrice(); // No discount
            }
            return order.getOriginalPrice().multiply(0.9);
        }
        // ...
    }
    
  • The problem: The if (order.getCategory().equals("BOOK")) is an "exception" patch. It tells us that our discount-rule model is missing the "category" dimension. What if "electronics" must become non-discountable too? We would have to add another || order.getCategory().equals("ELECTRONICS"). The code grows uglier and uglier.

  • Decompression refactoring: Turn the "rules" into data and express them explicitly.

    1. Create a t_discount_rule table:

      • rule_id
      • membership_level_required (e.g., 'GOLD')
      • discount_rate (e.g., 0.9)
      • excluded_categories (e.g., JSON array ['BOOK', 'ELECTRONICS'])
    2. The refactored code:

      // DiscountService.java
      public Money applyDiscount(User user, Order order) {
          DiscountRule rule = ruleRepository.findRuleFor(user.getMembershipLevel());
          if (rule != null && !rule.getExcludedCategories().contains(order.getCategory())) {
              return order.getOriginalPrice().multiply(rule.getDiscountRate());
          }
          return order.getOriginalPrice();
      }
      

By moving the "exception" rules from code into data, our DiscountService becomes stable. No matter how the discount rules change in the future (new membership tiers, revised discount rates, added or removed excluded categories), we need only update the rule data in the database, without recompiling and redeploying the core business code.

Code Review check:

"Is this if/else handling an 'exceptional case'? Can we data-ify this 'exception' rule by adding a configuration table or a field, thereby eliminating this branch of judgment?"

Signal Three: The Presence of Audit Requirements

When a piece of data must be traceable to answer "why it came to be this way" and "who changed it, when, and for what reason," it is no longer merely a "state" -- it also carries "historical intent." Trying to use a single mutable field to satisfy both "display the current state" and "trace the historical trajectory" is a classic and severely harmful form of conceptual compression.

How to spot it?

  • Demands from the business:
    • Customer service asks: "Why was this user's order canceled? The backend operation log only shows the status changing from 'Paid' to 'Canceled,' but it doesn't tell us whether the user canceled it themselves or the system auto-canceled it on timeout."
    • Finance asks: "Who approved this refund, and when?"
    • Risk control asks: "When was this user's account locked, and what was the reason for the lock?"
  • Struggles on the technical side:
    • To investigate a problem, engineers must dig through the application log files and grep for keywords to reconstruct the scene of the operation.
    • Every data change triggers a manually written log line in the code, such as log.info("Order {} status changed from {} to {} by operator {} for reason {}", ...). This is essentially using unstructured logs to simulate an audit function that the database itself should provide.

Case analysis: Order cancellation

  • Poor model: The t_order table has only a status field. When the order is canceled, status changes from 2 (Paid) to 6 (Canceled). All historical information is lost.
  • The problem: The context of the "cancellation" action (who, when, why) is lost. status = 6 is simply an emotionless outcome; it cannot answer "why."
  • Decompression refactoring (introducing an event-sourcing mindset):
    1. Keep the state simple: The status field in t_order can remain for fast queries of the current state, serving the main business flow.

    2. Create an independent audit-log table: build a t_order_status_log table.

      • log_id
      • order_id
      • old_status
      • new_status
      • operator_id (e.g., user_id, system_id, admin_id)
      • operator_type (e.g., 'USER', 'SYSTEM', 'ADMIN')
      • change_reason_code (e.g., 'USER_CANCEL', 'PAYMENT_TIMEOUT', 'FRAUD_REJECT')
      • change_reason_notes (e.g., 'User proactively canceled from personal center')
      • timestamp
    3. Refactor the business logic: Any operation that changes the order status must also insert an immutable record into t_order_status_log.

      // OrderService.java
      @Transactional
      public void cancelOrderByUser(long orderId, long userId) {
          Order order = orderRepository.findById(orderId);
          int oldStatus = order.getStatus();
      
          // 1. Update the current status
          order.setStatus(OrderStatus.CANCELLED);
          orderRepository.save(order);
      
          // 2. Record the audit log (a business fact)
          OrderStatusLog log = new OrderStatusLog(orderId, oldStatus, OrderStatus.CANCELLED,
                                                userId, "USER", "USER_CANCEL", "User proactively canceled");
          logRepository.save(log);
      }
      

Now, when customer service asks why an order was canceled, we need only query the t_order_status_log table to produce a perfect, structured, and non-repudiable "chain of evidence."

Code Review check:

"Might a change to this field need to be audited in the future? If so, should we create an independent, immutable log table for it, rather than simply overwriting the old value?"


Chapter Summary: Freeing Ourselves from the Inertia of "Compression"

In this chapter, we laid the theoretical cornerstone for the entire book -- the Theory of Conceptual Compression.

We first defined "conceptual compression," identifying its core as the forcible binding of things with different rhythms of change. This practice is rooted in our mistaken application of the "data compression" mindset, designed for static information, to dynamically evolving software systems. Through the "remote control and coffee cup" analogy, we vividly demonstrated the absurdity of such binding and its catastrophic consequences.

Next, we proposed the remedy -- "conceptual decompression." Its essence is making implicit logic explicit through beneficial data redundancy (adding fields and tables). This is a paradigm shift from "computing state" to "recording facts." It asks us to move complexity out of the countless "read" operations and into the few "write" operations, thereby drastically reducing the system's overall cognitive cost.

Finally, to put the theory into practice, we provided three concrete, actionable criteria to help us identify the "compression points" in our code:

  1. Inconsistent frequency of change: when a piece of data is driven by events from different business domains.
  2. The presence of "exceptional case" logic: when the code is full of if/else handling "exceptions."
  3. The presence of audit requirements: when we need to trace a piece of data's historical trajectory.

Once you have internalized the "Theory of Conceptual Compression" as a frame of mind, you possess a pair of "X-ray glasses." You will see through the surface of complex business logic to the structural defects lurking in the data model beneath it. You will no longer be a "code firefighter" passively fixing bugs, but an "architecture planner" who proactively prevents system decay.

In the parts that follow, we will apply this theory to three different layers of the software system:

  • Part Two: Microscopic Decompression -- We will focus on refactoring fields and dimensions, to root out "universal fields" and "exceptional case" code once and for all.
  • Part Three: Mesoscopic Decompression -- We will explore table structure design, learning how to separate "current state" from "historical intent" and achieve clean access control and auditing.
  • Part Four: Macroscopic Decompression -- We will raise our gaze to system boundaries and discuss how, in distributed environments, we can establish trust and achieve eventual consistency through data redundancy.

The spark of theory has been lit. Now, let us roll up our sleeves and perform the first "decompression surgery" on our code. The next chapter begins at the most common lesion -- that "universal field" which has startled you awake at midnight more times than you can count.