Do you assume or confirm?
Note: This article describes a debugging case from 2023. Some of the technologies mentioned here have since changed in relevance. In particular, Moment.js is now considered a legacy project in maintenance mode and is generally not recommended for new applications. The debugging lessons and reasoning process described here are still applicable, but the specific technical choices should be understood in their original context. Chapter 1: How I learnt to distrust JavaScript Date Daniel Kahneman’s famous book Thinking, Fast and Slow describes two modes of thinking: System 1 is automatic, fast, emotional, and quick to respond to stimuli. It constantly operates in the background and often bases its output on assumptions. System 2 is invoked consciously. It is slower, more deliberate, logical, and methodical, basing its output on reasoning. When it comes to problem-solving, we usually expect System 2 to take over. However, familiarity with a domain gradually builds a stack of assumptions that System 1 may stop revalidating. That overconfidence can mislead us and make our decisions less reliable than we think. If we were to visualize both systems while debugging, a good analogy would be Wile E. Coyote realizing that he has been running on thin air while chasing the Road Runner. Leaving System 1 in command for too long, without verifying the premises we have already accepted as valid, is basically us playing Coyote. Therefore, it is worth regularly asking ourselves: “Am I assuming these premises, or have I already confirmed what I’m basing this on?” This question can help prevent us from getting trapped in debugging dead ends. What criteria should trigger this question? I tend to use a simple one: “For the next hypothesis I’m about to accept, have I confirmed its foundations, or am I assuming them?” This simple question can save a significant amount of time and effort. Application problem A bit of context: Two systems communicate through event-based messaging. The originator system sends events in a specific order. The receiving system applies the event data to its state and updates its UI accordingly. Problem Even though the originator system sends events in the expected order, the receiver's UI does not always update in that same order. These symptoms were already known, although the root cause remained unclear. A race condition in the receiver's event interpreter had become the main suspect. The fact that multiple events were emitted within the same second appeared to support the race-condition theory. We were considering changes in the originator system because, after a recent optimization, some events were being produced in roughly half the previous processing time. When I was asked why the originator needed to change, I could not provide a solid explanation. I was under pressure to deliver a fix quickly, but my reasoning was mostly based on assumptions. I then learned that the receiver had an event timestamp comparator implemented using Moment.js. Event A and Event B could arrive within the same second and even share the same first three fractional-second digits. Event A could take slightly longer to process than Event B because additional business rules were applied to it. Before updating the UI, the receiver's timestamp comparator would skip an event if its timestamp was older than the timestamp of the event previously applied. JavaScript's legacy Date object stores time with millisecond precision. This means that fractional seconds beyond three digits are not preserved by Date, and Moment.js inherits that limitation when relying on JavaScript dates. As a workaround, the comparison was changed to operate on the original timestamp strings so that additional fractional-second digits could be taken into account when two events appeared otherwise equal. Lesson Read the documentation. Get to the root of the problem before proposing a convenient workaround. Do not mistake an assumption for a confirmed constraint. Solution Keep access to the raw timestamp representation. When two events are equal at millisecond precision, use the additional fractional-second digits from the original timestamp as a tiebreaker. Another problem The previous solution improved things, but another issue appeared. I expected timestamps to compare correctly, yet some reported cases still produced unexpected tiebreaker values. Simple string slicing turned out to be unreliable because the fractional-second representation was not always identical. I switched to a regular expression that identified the relevant fractional-second portion more explicitly. Reliability improved, but some additional reports still came in. This time, the problem was not the comparison logic itself. Some timestamps contained fewer fractional-second digits than expected. The affected timestamps corresponded to values whose complete representations would have contained trailing zeroes. The extraction function behaved correctly when trailing zeroes were present, so the next question became: where were those zeroes disappearing? A teammate suggested checking the serialization layer. Testing confirmed that serialization was modifying the timestamp representation. The application was using Newtonsoft.Json, and its date serialization behavior could omit trailing fractional-second zeroes depending on the configured format. An IsoDateTimeConverter was configured with an explicit datetime format that preserved the required fractional-second representation. After that change, the event timestamp comparison became considerably more reliable. Lesson If the bug is not in the business layer, inspect the supporting layers as well. Serialization, parsing, formatting, and transport layers can silently transform data that the business logic assumes to be unchanged. When working with structured strings, identify the exact format you expect instead of relying on positional assumptions such as arbitrary slicing. Solution Use an explicit and validated format when extracting structured values from strings. Configure serialization behavior deliberately when formatting affects application logic. Avoid relying on default serializer behavior for data whose exact representation matters. Looking back There is another architectural lesson I would add today. If event ordering is a fundamental property of the system, relying exclusively on timestamps to establish that order can be fragile. Timestamp precision, serialization behavior, clock synchronization, processing delays, and formatting differences can all affect the result. When strict ordering is required, an explicit sequence number, event version, or another monotonic ordering mechanism can provide a stronger guarantee than timestamps alone. That does not invalidate the debugging solution described above—it simply moves the responsibility for ordering closer to the domain itself instead of deriving it indirectly from time. References Daniel Kahneman, Thinking, Fast and Slow (2011) Moment.js issue regarding sub-millisecond precision: https://github.com/moment/moment/issues/3256 Moment.js project status and documentation: https://momentjs.com/docs/ Newtonsoft.Json issue regarding trailing fractional-second zeroes: https://github.com/JamesNK/Newtonsoft.Json/issues/1511 I’ll come back to this series whenever I stumble upon situations that bring up the same question: Are you assuming or confirming? I want to keep challenging the reasoning patterns that affect not only how we work, but also how we approach everyday problems.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to