BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Functional UI - a Model-Based Approach

Functional UI - a Model-Based Approach

Bookmarks

Key Takeaways

  • User interfaces are reactive systems that are specified by the relation between the events received by the user interface application and the actions the application must undertake on the interfaced systems
  • Functional UI is a set of implementation techniques for user interface applications that emphasizes clear boundaries between the effectful and purely functional parts of an application
  • User interfaces' behavior can be modelized by state machines that, on receiving events, transition between the different behavior modes of the interface. A state machine model can be visualized intuitively and economically in a way that is appealing to diverse constituencies (product owner, testers, developers), and surfaces design bugs earlier in the development process
  • Having a model of the user interface allows to auto-generate both the implementation and the tests for the user interface, leading to more resilient and reliable software
  • Property-based testing and metamorphic testing leverage the auto-generated test sequences to find bugs without having to define the complete and exact response of the user interface to a test sequence. Such  testing techniques have found 100+ new bugs in two popular C compilers (GCC and LLVM)

Introduction

Functional UI relies on an explicit functional relation linking events received by the user interface and the actions the interface application must exert on the interfaced systems:

(1) (action_n, state_n+1) = f(state_n, event_n) where:

- n is the nth event processed by the application,
- state_n is the state of the reactive system when the nth event is processed,
- f is called the reactive function

That functional equation has been made popular by the Elm front-end framework and is used across a series of languages and frameworks inspired by Elm.

This article introduces another functional UI technique that relies on a model of the user interface application behavior. That model uses state machines to describe the application reaction to an event as a transition between states of the machine.

First, equation (1) is refined into a form that surfaces the state machine model, by dividing the state in the equation into control states and extended state. Then an intuitive yet rigorous visual formalism is presented which describes an application behavior both accurately and concisely. An example application, with a concrete JavaScript implementation, will illustrate the approach.

The previous article also explained how functional UI supports reliable applications by making it easy to unit-test user scenarios. Model-based testing goes a step further by allowing developers to automate completely or partially both code generation and user scenarios generation. When faced with a large number of tests, stateful property-based testing can detect errors by testing against specified invariants of the interface. Having a model of the user interface means developers can test against specifications (the model) instead of against a specific implementation, making tests less brittle. An example application will equally serve to illustrate the previous assertions.

When the model approach fits only a part of application behavior, it can be mixed with the Elm-like approach as described by equation (1). That flexibility is a direct consequence of using pure functions, which ensure better composability.

The many ways in which the model can be used to ensure alignment between specification and implementation is the reason why model-driven software is commonly used in safety-critical industries.

Modeling user interface behavior with state machines

In the previous article, we gave the example of a trivial cat gif search application:

One reactive function mapping is:

 

EVENT ACTION
More please request a cat gif URL, display loading message
Ok display a cat gif
Err display an error message

Let’s complicate things a bit and require the application to wait for the image link to be fetched before accepting More please button clicks. The application now has two modes, one when the application is fetching, and one when it is not. That means we have two distinct reactive functions, applying in one or the other application mode.

When the application is ready to fetch:

 

EVENT ACTION NEW MODE
More please request a cat gif URL, display loading message busy
Ok ignore ready
Err ignore ready

When the application is busy fetching:

 

EVENT ACTION NEW MODE
More please ignore busy
Ok display a cat gif ready
Err display an error message ready

Such cases can be expressed by separating the mode (termed as control state) from the rest of the state (termed as extended state) in the aforementioned fundamental equation. In that case:

f :: State -> Event ->  (Actions, State)

becomes:

f :: (ControlState, ExtendedState) -> Event -> (Actions, (ControlState, ExtendedState))

from which we can select which reactive sub-function to apply based on the mode (control state):

f :: (ControlState, ExtendedState) -> Event -> (Actions, (ControlState, ExtendedState))
f (cs, es) e = case cs of
  -- application is not busy fetching
  ready -> f_ready es e
  -- application is busy fetching
  busy  -> f_busy es e

It turns out that many user interfaces exhibit a behavior characterized by discrete modes in which the reactive function has a simpler formulation. Routing in web applications is a classic example. For each route of the application, there will be a distinct reactive function to compute the reaction to events. In pseudo-code, we would have something of the following kind, for an application with two routes (home and about):

f (cs, es) event = case cs of
      home  -> f_home es event
      about -> f_about es event

Another classic example is an arcade game, in which the player’s inputs will result in different actions depending on what the player character is doing at a specific time:

f (cs, es) event = case cs of
      standing -> f_standing es event
      ducking  -> f_ducking es event
      jumping  -> f_jumping es event
      diving   -> f_diving es event

We called the mode control state as we transfer the control flow of the running application from the reactive function to one of the reactive sub-functions based on the value of the mode. We call the rest of the state extended state, as the reactive function in that form describes a kind of state machine called extended state transducer. An extended state transducer is a state machine with memory that produces an output on processing inputs. The relationship between the state transducer and f is as follows:

  • the states of the machine are the aforementioned modes (control states),
  • the memory of the machine is the extended state,
  • the Actions output by f are the outputs of the machine,
  • the extended state and control state computed by f define the transitions of the state machine

To summarize, the specification for the application behavior is described by a reactive function. That reactive function can be written as a state transducer, which on receiving inputs (events), transitions between computation modes (control states), updates its memory (extended state), and outputs commands to execute. Why is that useful?

Towards a visual formalism

State transducers can be represented intuitively and accurately by a graph linking states of the machine and user scenarios are paths in that graph. The user interface behavior of the Elm application previously taken as an example can be summarized visually as follows:

The visual formalism used here is fairly simple. The two nodes are the two control states of our modified cat gif fetching application. The edges between nodes are labeled with events processed by the application. An edge labeled e/c features an event e and command c and which links a node A to a node B is a visual representation of the following semantics: given that the application is in the control state A, when the event e occurs then the application’s new control state is B and the command c is returned by the reactive function.

Events accepted by the application but which result in no output and no change of control state by the reactive function are not represented. For instance, given that the application is in the Busy control state, when the More Please event occurs, the application should ignore that event, meaning that the reactive function returns an empty output. As such, there is no edge featuring the More Please event and leaving the Busy control state. On the contrary, there is such an edge in the Ready control state.

There are a few important benefits to this visual representation that are already apparent:

  • it is both a concise and accurate representation of the behavior of the application: it answers unequivocally the question What happens when event X occurs?
  • user scenarios can be easily visualized by following a given path of the graph (the BDD terms given/when/then are emphasized above to indicate just that)
  • it is also likely a more readable description of the behavior than the equivalent code, at least for readers who may not be versed in the arcane of programming

Furthermore, and importantly, we previously started from the pseudo-code for the reactive function to reach the visualization. It is often more valuable to do the opposite: draw the visualization first, and write or auto-generate the matching code afterward. To do so, the visualization has to be described with a visual language whose semantics can replicate the necessary code semantics. There are a number of such visual languages, mostly taking inspiration from and improving on the seminal work of David Harel on Statecharts. This article is using the visual language defined in the Kingly state machine library, as Kingly is used for implementing the provided examples.

Having a visual, as-precise-as code, specification language result may considerably help to develop more robust and reliable software. Why is that? Turing Award winner Frederick Brooks, in his influential essay No Silver Bullet – Essence and Accident in Software Engineering, distinguished the essential complexity linked to the problem space from the accidental complexity attached to a given solution space. Brooks further exposed his belief that the exponential growth in the number of states a system goes through, the difficulty of comprehending what the software under development should do – and communicating it to others, and the inherently non-visualizable character of software are essential difficulties of modern software development:

Many of the classic problems of developing software products derive from this essential complexity and its nonlinear increases with size. From the complexity comes the difficulty of communication among team members, which leads to product flaws, cost overruns, schedule delays. From the complexity comes the difficulty of enumerating, much less understanding, all the possible states of the program, and from that comes the unreliability.

(…)

The hard thing about building software is deciding what to say, not saying it.

(…)

In spite of progress in restricting and simplifying the structures of software, they remain inherently unvisualizable, and thus do not permit the mind to use some of its most powerful conceptual tools. This lack not only impedes the process of design within one mind, it severely hinders communication among minds.

Visual specification languages addressing the previously mentioned pain points are good candidates to make a dent in the essential complexity of software. Let’s come back to the arcade game we mentioned earlier. The following graph:

(source here)

visualizes a specific portion of the requirements of the game, in ways that can be quickly understood by game designers, project owners, or game developers alike, while still requiring no code. This visualization can be left as-is while the requirements are still being discussed and explored, then made more precise by labeling the edges of the graph with events, and actions as we saw previously. In fact, the visualization can be made precise enough for code to be automatically generated from it.

Hierarchy and history against non-linear state growth

While the previous visualization addresses the deciding what to say and communication among minds parts of Brooks’ concerns, it does not seem to help with visualizing without clutter a rapidly growing number of states. This is achieved by:

  • only representing transitions that change the machine state (control state, extended state) in some way or produce output,
  • using extended state to capture the pieces of state not involved in control flow,
  • describing control states for the machine as a hierarchy,
  • using that hierarchy to factorize several transitions into one,
  • adding a way to come back to a past configuration of the machine (history mechanism),

Let’s detail the last three items.

Factorizing behavior with hierarchy

Observe that reactive sub-functions have a signature similar to that of the top-level reactive function. This means that the process of separation of the state into control state and extended state can be recursively applied. Let’s consider an application with several routes again. A previous example was that of an application with two routes:

f :: (ControlState, ExtendedState) -> Event -> (Actions, (ControlState, ExtendedState))
f (cs, es) event = case cs of
      home  -> f_home es event
      about -> f_about es event

Assuming the About page has a Team sub-route and a Main index route, we could in turn write:

f_about :: (ControlState, ExtendedState') -> Event -> (Actions, (ControlState, ExtendedState'))
f_about (cs, es) event = case cs of
      index -> f_about_index es event
      team  -> f_about_team es event

As the f_about_team and any reactive sub-function may also be expanded if necessary, a tree of control states naturally appears. That control state tree can be visually represented. Let’s have a look at the more complex example of an application with nested routing:

Observe that the hierarchy of control states is reflected by an inclusion relation. For example, the About detail control state is included in the About control state, and it itself includes the Team control state.

Additionally, and as importantly, the inclusion relation is used to factorize reactions. For instance, at the specification level, whatever state the application is in when the URL gets changed, the application should display the route corresponding to the new URL. At the visualization level, we have five control states (actually seven, the two Routing control states, having no event-triggered transitions, are left as soon as they are entered). This means 5 edges would be necessary to denote the behavior corresponding to a route change. By including all 5 states into a single encompassing state (App), only a single edge between App and the top-level Routing control state is necessary.

History mechanism

It is often necessary for a reactive system to temporarily interrupt a behavior, replace it with another one, and then resume the interrupted behavior.

Referring back to the previous visualization of the routing behavior of an application, when the application changes the URL, the state machine transitions to the Routing control state and determines whether the change of route is allowed. If that is not the case, it is necessary to return to exactly where we were.

This is ensured by memorizing that position on exit and restoring it on entry (transition to the circled H in the App control state).

With all previous conventions, the visualization makes it clear which actions are possible, forbidden, or must occur in given states of the application. Let’s now see a concrete JavaScript example.

Example

Let’s consider the user interface corresponding to a two-player chess game:

The behavior is roughly as follows:

  • the white pieces and black pieces alternate playing
  • the game starts with the white pieces
  • the whites indicate a move by first selecting (clicking on) the piece making the move, and then clicking on the destination of that piece
  • if the destination is a valid one, as per chess game rules, the piece is moved, and the black pieces get to play
  • the same rules apply to the black pieces move (selecting a piece and indicating the destination). After a valid black piece gets moved, it remains the white pieces turn
  • until one of the moves ends the game

We thus have three modes that exhibit different behaviors: white pieces turn, black pieces turn, and game over. Let’s see the mapping event ~ action in those modes. In the first mode (white pieces turn), the interface should not consider the user clicks on black pieces. When a user clicks on a white piece, then that piece should be highlighted. In the second mode, the reverse applies, In the game over mode, no click should be taken into account:

 

CONTROL STATE EVENT ACTION
White pieces turn click on black piece none
White pieces turn click on white piece highlight piece
Black pieces turn click on white piece none
Black pieces turn click on black piece highlight piece
Game over any clicks none

Those modes can further be refined. In the White pieces turn mode, we have again a change of behavior once a white piece is clicked. Concretely, the next clicked square may be either another white piece, or a valid destination for the selected white piece to move to, or an invalid destination. In the first case, the interface should highlight the newly selected white piece, and proceed as if that newly selected piece was the previously selected piece. In the second case, the interface should display the selected piece in its target destination (i.e. execute the move). In the third case, the interface should just ignore the click.

We thus just identified two other sub-modes which belong to the White pieces turn mode: the one the application is in before a piece is selected, and the one after a piece is selected. Detailing the event ~ action mapping for the Whites turn mode goes as follows:

 


CONTROL STATE
EVENT ACTION
White pieces turn.* click on black piece none
White pieces turn.White piece plays click on white piece highlight piece, new mode is Piece selected
White pieces turn.Piece selected clicked square is valid winning move move the piece, new mode is Game over
White pieces turn.Piece selected clicked square is valid non-winning move move the piece, new mode is Black piece plays
White pieces turn.Piece selected clicked square is invalid move none

Once we have identified the totality of our modes (control states), we can link them in a graph, where the nodes are the control states, and the links (also called transitions) reflect the event ~ action relation:

Without going into the details of the visual representation, the notation event [guard] / action is used to label the transitions between control states, and that initial transitions (i.e. transitions with origin control state init) are taken immediately upon entering a hierarchical control state (like White pieces turn).

Let’s now add an Undo functionality. The corresponding modelization is updated by adding two new edges (transitions in red below) corresponding to the Undo button click in the Black pieces turn or White pieces turn state of the application:

Lastly, let’s add yet another feature: a game timer that will count the number of seconds elapsed till the start of the game. The timer additionally will be paused and blink when being clicked on:

The modelization makes use of a timer that produces an event every second. The machine memorizes where it was when processing the timer event (Game on’s history pseudo-state), and resumes the game behavior when done:

There again, the new features produce new edges (in red) in the graph visualization with contained or no modifications to the existing behavior modelization. The two previous modelizations show how hierarchy and history pseudo-state lead to an economical representation of behavior. In a well-designed machine, incremental changes in behavior should correspond to incremental changes in the modelization.

Implementation with JavaScript

The Kingly state machine library may be leveraged to implement the event ~ action relation with a state machine.

Kingly has several tutorials, including the two-player chess game used in this article, and one implementation of the real-world Conduit demo app. Additionally, pre-made integrations with React and Vue are available.

Here is a sample of the machine code for the first chess game iteration. The transition records given to define the Kingly machine map exactly to the transitions appearing in the modelization:

const transitions = [
  { from: OFF, event: START, to: WHITE_TURN, action: ACTION_IDENTITY },
  // Defining the automatic transition to the initial control state for the compound state WHITE_TURN
  { from: WHITE_TURN, event: INIT_EVENT, to: WHITE_PLAYS, action: displayInitScreen },
  {
    from: WHITE_PLAYS, event: BOARD_CLICKED, guards: [
      { predicate: isWhitePieceClicked, to: WHITE_PIECE_SELECTED, action: highlightWhiteSelectedPiece }
    ]
  },
  {
    from: WHITE_PIECE_SELECTED, event: BOARD_CLICKED, guards: [
      { predicate: isWhitePieceClicked, to: WHITE_PIECE_SELECTED, action: highlightWhiteSelectedPiece },
      { predicate: isLegalNonWinningWhiteMove, to: BLACK_PLAYS, action: moveWhitePiece },
      { predicate: isLegalWinningWhiteMove, to: GAME_OVER, action: endWhiteGame },
    ]
  },
  {
    from: BLACK_PLAYS, event: BOARD_CLICKED, guards: [
      { predicate: isBlackPieceClicked, to: BLACK_PIECE_SELECTED, action: highlightBlackSelectedPiece }
    ]
  },
  {
    from: BLACK_PIECE_SELECTED, event: BOARD_CLICKED, guards: [
      { predicate: isBlackPieceClicked, to: BLACK_PIECE_SELECTED, action: highlightBlackSelectedPiece },
      { predicate: isLegalNonWinningBlackMove, to: WHITE_PLAYS, action: moveBlackPiece },
      { predicate: isLegalWinningBlackMove, to: GAME_OVER, action: endBlackGame },
    ]
  },
];

// Events handled by the machine
const events = [BOARD_CLICKED, START];
// JSON is used to express state hierarchy
const states = {
  [OFF]: "",
  [WHITE_TURN]: {
    [WHITE_PLAYS]: "",
    [WHITE_PIECE_SELECTED]: ""
  },
  [BLACK_TURN]: {
    [BLACK_PLAYS]: "",
    [BLACK_PIECE_SELECTED]: ""
  },
  [GAME_OVER]: "",
};
const initialControlState = OFF;
const initialExtendedState = ...

// Definition of the fsm modelizing the game behavior
const gameFsmDef = {
  initialControlState,
  initialExtendedState,
  states,
  events,
  transitions,
  updateState
};

// Create the executable machine from the machine definition
const gameFsm = createStateMachine(gameFsmDef, {
// Injecting necessary dependencies
  eventEmitter,
  chessEngine
});

As the executable state machine already encapsulates its state, it only accepts events (corresponding here to the user clicking on the board). An example of run could be:

// Clicking on a white square holding a pawn
gameFsm({CLICKED: "g2"})
-> [{
 command:  "render"
 // Props for the React component rendering the chessboard
 params:  {
   boardStyle: {borderRadius: "5px", boxShadow: "0 5px 15px rgba(0,0,0,0.5)"},
   draggable:  false
   onSquareClick:  ƒ onSquareClick(square)
   position:  "start"
   squareStyles: {
     g2: {
       // Style for the highlighted piece
       backgroundColor:  "rgba(255, 255, 0, 0.4)"
     }
   },
  width: 320
 }
}]

// Now clicking on a square that is not accessible per chess game rules
// The machine thus computes no commands (`null` return value)
gameFsm({CLICKED: "g5"})
-> null

// Now clicking on g4 (valid move) to have the g2 pawn move there
gameFsm({CLICKED: "g4"})
-> [{
 command:  "render"
 params: ...
}, {
 command:  "MOVE_PIECE"
 params: {from:  "g2", to:  "g4"}
}]

As previously discussed, for each processed event, the machine produces commands to be executed (render and MOVE_PIECE) on the interfaced systems (here the screen and the chess engine).

The machine only implements the control flow of the application: it knows nothing about the game of chess, except that there are two players which take turn to play. Concerns are cleanly separated: rendering the board is done by the <Chessboard /> React component; applying the chess rules and maintaining the chessboard is done by the chess engine. The same machine could apply to the checkers game without modification.

Model-based testing

In the previous Functional UI article, a pure function h, equivalent to the reactive function was presented for testing purposes:

h([event_0]) = [action_0]
h([event_0, event_1]) = [action_0, action_1]
h([event_0, event_1, ..., event_n]) = [action_0, action_1, ..., action_n]

Let’s call h the oracle function. The sequence of input events passed to h are specific user scenarios, the corresponding outputs are the commands computed that should be executed. It is thus possible to unit-test user scenarios with usual assertion-checking techniques. Furthermore, with state machine modelization, it is possible to generate a large number of test sequences, with high flexibility and automaticity. However, the high number of test sequences comes with problems of its own.

Here we review the test space to address, how to use the model to generate test sequences, the two fundamental testing issues developers face, and how a combination of example-based, property-based, and metamorphic testing can help. A full discussion of the subject matter would require a separate article. Only the basics are covered in what follows.

An exponentially growing test space with two challenges

For a user scenario of length n, i.e. consisting of inputs [e_1, ..., e_n], the input test space is the Cartesian product of the event’s test spaces. Returning to the previous chess game example in its first iteration, a user scenario of length 2 would be [{CLICKED: square1}, {CLICKED: square2}]. Because the user can click on any square of the board, the test space for the CLICKED event is 8x8 = 64. Then the size of the test space for user scenarios of length n is 64^n. Commonly enough, developers are faced with huge test spaces, growing exponentially with the length of user scenarios.

For any test in the test space, a method must be devised to validate or invalidate the observed test results. Testers thus face two challenges, which Prof. Tsong Yueh Chen called the reliable test set problem and the oracle problem:

The oracle problem refers to situations where it is extremely difficult, or impossible, to verify the test result of a given test case (…).

The reliable test set problem means that since it is normally not possible to exhaustively execute all possible test cases, it is challenging to effectively select a subset of test cases (the reliable test set) with the ability to determine the correctness of the program.

Let’s see how those two problems can be addressed with a practical example.

Wizard application form

The example consists of a real case of a multi-step workflow (the visual interface, however, has been changed to use an open-source design system, but the behavior is the exact same). A user is applying to a volunteering opportunity, and to do so must navigate through a 5-step process, with a specific screen dedicated to each step. When moving from one step to another, the data entered by the user is validated then saved asynchronously. The user flows are as follows (full-size image):

User flows like the ones presented may often be used as a starting point to be refined iteratively into a precise model of the application.

The first modelization of the core behavior represented by the user flows, and with the error and data fetching flows is as follows (full-size image):

The prototype implemented looks like this:

The model helps address the reliable test set problem

While the test space could be sampled randomly, having a model of the interface behavior allows to pick interesting test sequences whether because:

  • they represent specific user scenarios,
  • or satisfy some coverage criteria,
  • or fulfill specific properties.

To illustrate the first point, the happy path of the application is represented in bold green in the above visualization. That scenario can be extended to include some validation errors along the way by following the error paths (in dotted red).

To generate confidence in the modelization and implementation, it is valuable to select manually a small set of user scenarios that cover key parts of the specifications. The expected outputs of the reactive function should be computed ahead of time for posterior comparison with actual outputs. In other words, a test oracle is needed.

The model can also be used to auto-generate test sequences to fulfill some structural coverage criteria. Data-oriented coverage relates to covering the extended state test space while transition-based coverage relates to covering the transitions between control states. Common transition-based coverage criteria are (in increasing coverage order):

  • All-states-coverage is achieved when the test set reaches every state in the model at least once. This is usually not a sufficient level of coverage, because behavior faults are only accidentally found.
  • All-transitions-coverage is achieved when the test executes every transition in the model at least once. This automatically entails also all states coverage.
  • All-n-transition-coverage, meaning that all possible transition sequences of n or more transitions are included in the test suite.
  • All-paths-coverage is achieved when all possible branches of the underlying model graph are taken (exhaustive test of the control structure). This corresponds to the previous coverage criteria for a high enough n
  • All-one-loop-paths, and All-loop-free-paths are more restrictive criteria focusing on loops in the model.

To illustrate the miscellaneous coverage criteria on a simple model:

Using a dedicated graph traversal library, an abstract test suite for the wizard form application fulfilling the All one-loop path criteria was generated and ended up with around 1500 tests!! Of those tests, 4 were manually selected, for a total of around 50 transitions taken (over 26), fulfilling the All transitions coverage criteria. The control states covered by those four transitions were as follows (nok correspond to the init pseudo control state – in orange on the visualization):

[
 "nok","INIT_S","About","About","Question","Question","Teams",
 "Team_Detail","Team_Detail","Team_Detail","Team_Detail","Teams",
 "Review","Question","Review","About","Review","State_Applied"
],
["nok","INIT_S","Question","Review","State_Applied"],
["nok","INIT_S","Teams","State_Applied"],
["nok","INIT_S","Review","Teams","State_Applied"]

The expected results (oracle) were manually computed for the four test scenarios. However, computing an oracle for the thousands of auto-generated tests is costly or impossible. Property-based testing then becomes more effective than example-based testing to find bugs.

Property-based and metamorphic testing to deal with the oracle problem

One property (here a business rule) of the wizard application helped find a bug in the first prototypal implementation: all subscribed teams must have non-empty answers to the motivational question. One failing sequence showed that the bug happens when there is one subscribed team, with a valid answer, then that answer is deleted, and the user returns back to the Teams screen.

Because that team remains subscribed, the user can proceed to the Review screen with an empty answer, violating the property. Further investigation showed that the root cause is that the Back button registers the empty answer without checking that it is indeed a valid answer.

Metamorphic properties relate test results of different test sequences. Here, for instance, a user subscribing to team A and B in that order (sequence t1) should result in the same application data (produced in the last step of the application flow) as a user with identical data subscribing to team B and A in that order (sequence t2). The oracle function h is thus such that last(h(t1)) = last(h(t2)).

Metamorphic properties are powerful in the absence of an oracle: they do not require testing an actual vs. expected output but a relation between two (or several) outputs. A full and excellent review of metamorphic testing is available in Metamorphic Testing: A Review of Challenges and Opportunities. A major feat of metamorphic testing mentioned in the article is finding 100+ new bugs in two popular C compilers (GCC and LLVM).

Properties (invariants, metamorphic properties, or else) relate to the problem rather than the solution. It is thus possible to modify the implementation as bugs get found while keeping intact the property-based tests. To help identify properties, Scott Wlaschin, senior software architect, has suggested a tentative taxonomy.

Conclusion

User interfaces are reactive systems specified by means of its events/actions interface with the external systems of interest, and a pure reactive function mapping the actions of the user on the user interface to actions on the interfaced system.

A large class of reactive systems, featuring a finite number of modes that dictate their behavior may be advantageously modelized with state machines, expressing the reactive function as a series of reactive sub-functions applying in specific states of the machine.

Model-driven development enables one to visualize behavior economically in a way that is appealing to diverse constituencies (product owners, testers, developers). With functional UI, user scenarios can be unit-tested, thus eschewing sophisticated automation tools and long-running flaky tests. As importantly, with model-based functional UI, both implementation and tests may be automatically generated from the specifications encoded in the model. The high and varied number of tests coupled with the application of property-based techniques result in higher quality software.

On the downside, while modeling is an iterative process, it is a top-down methodology, to which many developers may be reticent. To spend more time thinking about the specifications and properties of the system, i.e. the what instead of the how, the problem instead of the solution, may require a mindset change. Second, statecharts-derived visual formalisms do not effectively represent data flowing between states. Transitions show execution flow but they do not represent data. For the most part, data variables are not visible within the diagram representation. Third, not all interfaces have a limited (or manageable) number of behavioral modes. In some cases, a state-machine modelization may add more complexity than it removes.

Model-based functional UI has been adopted extensively for the prototyping of interfaces for embedded systems in safety-critical industries. Eric Bantégnie, the founder of Esterel Technologies, a company that makes model-based design tools, explained in an interview to James Somers, editor for The Atlantic:

Nobody would build a car by hand. (…) Code is still, in many places, handicraft. When you’re crafting manually 10,000 lines of code, that’s okay. But you have systems that have 30 million lines of code, like an Airbus, or 100 million lines of code, like your Tesla […] — that’s becoming very, very complicated.

References

About the Author

Bruno Couriol holds a Msc in Telecommunications from the French Grandes Ecoles, a BsC in Mathematics and a MBA by INSEAD. Most of his career has been spent as a business consultant, helping large companies addressing their critical strategical, organizational and technical issues. In the last few years, he developed a focus on the intersection of business, technology and entrepreneurship.

Rate this Article

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT