User Journey: Debugging of RxJS-Based Applications

This document describes two user journeys of Frank Flow, a software engineer working on an application implemented using RxJS. The application has a web-based user interface and was part of a study by Alabor et al. [1]. Frank will use different debugging techniques and utilities to solve the task given to him. The journeys follow the debugging process model proposed by Layman et al. [2].

Actor

Frank Flow started to work for ReactiBank 2 years ago as a frontend software engineer. As part of a small, interdisciplinary team of 7 people, Frank' and his team are responsible for developing and maintaining a trading application. This application relies heavily on real-time data, so the group decided to use reactive programming principles throughout the application. Frank knows traditional programming paradigms and the related debugging tools from his studies and personal experiences. He built up knowledge on RP and RxJS for the frontend part of their application after joining the team quickly, however.

Today, Frank uses RxJS efficiently to build new features. He can solve simple problems reported by the product owner on his own. Working on more complicated issues is still something Frank struggles with: He often feels like his knowledge of traditional programming techniques and its debugging utilities are not enough. These tools feel "out of place" to him. All to often, they do not provide any helpful insights and turn out to be time-sink. Frank does not like that, eventually, he has to consult one of his colleagues who have experience in RxJS for a longer time.

Context

Frank participates in a study about RxJS-specific debugging techniques. He is asked to work on an application allowing a user to change and display a value. The user can achieve a change of the value either by two buttons to increase and decrease a numeric value or by entering an arbitrary text in an input field. The two buttons get disabled once a text is entered. There is a third button labeled "Reset", which clears all user input and reverts the application to its initial state.

Goal

Frank receives a bug report for the application: Once a user had clicked the reset button, the application started to behave strangely by showing multiple values in quick succession when the increase or decrease button was clicked. It is Franks task to analyse the bug and find a solution.

User Journey

Beginning from the same point, we are going to present two distinct user journeys in which Frank uses different tools to achieve his goal:

  1. Branch A on the left describes a debugging session using traditional debugging techniques and practices as described by Alabor et al. [1]. Frank will use imperative debugging utilities built-in to Visual Studio Code and manual code modification to reach his goal.
  2. Branch B on the right describes how Frank uses the prototype of an extension for Visual Studio Code providing a less invasive debugging technique for RxJS-based source code.

Step 1 Build Hypothesis

After Frank recreated the behavior reported in the bug report, he starts analyzing the source code of the application. In this process, he formulates his first hypothesis about what could cause the unexpected behavior: Frank suspects the flatMap operator in Line 19 to be responsible.

Frank wants to have a closer look on the operators runtime behavior, which is why he decides to use debugging utilities.

Step 2.A Instrument Hypothesis

Using the built-in debugger of Visual Studio Code, Frank adds a breakpoint to Line 19 where the flatMap operator is called with the intention to stop the program execution every time a values "flows" through the operator.

Step 2.B Instrument Hypothesis

Frank navigates to the flatMap operator in Line 19 and selects the "Add Log Point to Operator..." code action provided by the Visual Studio Code extension.

Step 3.A Test Hypothesis

Frank launches the application. Before the web browser can display anything, the debugger in Visual Studio Code halts the program execution at the breakpoint in Line 19.

Contrary to Franks expectation, the application was already paused during the creation of the flatMap operator rather than when a value was processed by it.

Frank resumes the program execution, though the breakpoint never pauses the application again.

Step 3.B Test Hypothesis

Frank launches the application. While reproducing the reported bug in the browser, the extension produces a log of all events detected at the flatMap operator in Line 19.

Frank recognizes a peculiar pattern: After the reset button was clicked, the log point reports multiple values emitted for each click on the increase/decrease button.

This confirms Franks hypothesis about the flatMap operator: After the user clicked the reset button, the operator emits multiple values.

Step 4.A Instrument Hypothesis

After Franks first failed instrumentation attempt using breakpoints, he decides to add trace log statements using the tap operator manually. He adds multiple of them on Lines 19, 24, 28, and 32.

Step 4.B Resolve Bug

After researching the RxJS documentation, Frank realizes that the flatMap operator [3] does not unsubscribe observables it created earlier. This sounds like a reasonable explanation for the observed behavior.

Frank replaces the faulty operator with the switchMap [4] operator.

Step 5.A Test Hypothesis

Frank launches the application again. The manually added code generates the expected trace log in the debuggers console as Frank executes the steps necessary to recreate the reported bug.

Once the application produced enough logs, Frank starts to analyse them. Even though it is hard to reassign a log entry to a piece of code and a related action, Frank recognizes a peculiar pattern after some time: After the reset button was clicked, the log statement on Line 32 is executed multiple times, even though the increase/decrease button gets clicked only once.

This confirms Franks hypothesis about the flatMap operator: After the user clicked the reset button, the operator emits multiple values.

Step 5.B Verify

Frank launches the application again and repeats the steps necessary to reproduce the reported bug.

The application appears to work correctly now. A quick look at the log point monitor confirms that the previously observed behavior of multiple values emitted is fixed.

Frank has reached his goal successfully.

Step 6.A Resolve Bug

After researching the RxJS documentation, Frank knows that the flatMap operator [3] does not unsubscribe observables it created earlier. This sounds like a reasonable explanation for the observed behavior.

Frank replaces the faulty operator with the switchMap [4] operator.

Step 7.A Verify

Frank launches the application again and repeats the steps necessary to reproduce the reported bug.

The application appears to work correctly now. A look at the trace log confirms that the previously observed behavior of multiple values emitted is gone.

Step 8.A Revert Hypothesis Instrumentation

Frank removes all tap operators he added solely for the trace log generation, except the one in Line 24.

Another engineer notices this leftover during the code review, fortunately. Frank removes the forgotten statement afterwards.

Finally, Frank has reached his goal.