The art of
explicit and consistent
user interfaces
Farzad Yousef Zadeh
@farzad_yz
Farzad Yousef Zadeh
✈ Aerospace engineer 🔭 Astrophysicist
Senior Software Engineer @
CurrentState
PreviousState
github.com/farskid
twitter.com/farzad_yz
🇫🇮
I've seen A lot!
jQuery
AngularJS
BackboneJS
Phonegap / Cordova
React
React Native
Redux
Dojo Toolkit
Vue
User interface development
is
challenging
User interface used to be only text based!
TUI
aka
Command line
USSD
Today, they've grown graphics!
GUI
aka
Alan Kay made the term Windowing UI popular
Direct manipulation
ui
source: constructing the user interface with Statecharts by Ian Horrocks
Too many platforms
Browsers
Mobile Phones
Reader devices
Smart Gadgets
Smart TV
My fridge at home
+ IE
Platforms
are
different
(WAY TOO MUCH)
Partial standards
IE 6,7,8,9
Mobile Safari
JS Engines
Styling challenges
Layouting
Responsiveness
Color systems
😱 @media foldable
Many external players
Network Connection
Ad blockers
user permissions
user prefrences
Accessibility
Usability
Visually impaired
Color blindness
🍺 Drunk
in a hurry!
internationalization
Vertical Typogrpahy
RTL layouts
RTL typography
Locale numbers
justify-content: flex-start
Unpredictable consumers
Human being
Another UI (embedded)
Browser extensions
Crawlers / Bots
To survive the complexities, we can
AT LEAST MODEL BETTER
What is behaviour really?
How the software is supposed to work
The form is supposed to be submitted
The input is supposed to be validated
The button is supposed to be clicked
The button is supposed to be enabled when the user hasn't searched yet
The button is supposed to be disabled when the search is happening
The button is supposed to be enabled when the search is done
A is supposed to happen while...
A is supposed to happen after...
A is supposed to happen before...
➡️ Booleans
Binary
isHappening: true
isHappening: false
isHappening: false
Only model with boolean when
the state is binary
isFetching: false
isFetching: true
isFetching: false
isFetching: true
isFetching: false
fetching Remote data
should not be modeled by a binary state
Promise based problems
should not be modeled by a binary state
fetching Remote data
is an extended promise based problem
Pending
Fulfilled
Rejected
Fulfilled with valid data
Fulfilled with corrupted data
Timed out!
Token expired
Server went down
1
2
3
4
5
6
7
8
state: idle
state: pending
state: pending
state: success | failure
state: success | failure
failureState: offline | timeout | unauthorized | ...
successState: valid | corrupted
Be aware of
Mutual exclusivity
Things that can not happen at the same time
Modeling a <form />
isValidating: boolean
isSubmitting: boolean
isValidating: true, isSubmitting: true
isValidating: true, isSubmitting: false
isValidating: false, isSubmitting: true
isValidating: false, isSubmitting: false
Form is idle
Form is being submitted
Form is being validated
WTF ???
Impossible state
formState: | idle | validating | validated_success | validated_failure | submitting | submitted_success | submitted_failure| ...
EXPLICITLY Modeling the <form /> example
Impossible state is gone!
Modeling mutually exclusive behaviour
WILL MAKE IMPOSSIBLE STATE, POSSIBLE
Modeling mutually exclusive behaviour
Will force you to test something that shouldn't have existed in the first place.
problems of poorly modeled booleans
They do not carry an inherent meaning.
They do not let the complexity grow linearly (2^N)
They do not let the complexity grow linearly (2^N)
By using Conflicting booleans (dependants), the order matters!
Conflicting properties
modeled as Booleans
Modeling a <Layout />
header
main
side
isHeader: boolean isMain: boolean isSide: boolean
DOESN'T MAKE SENSE?
Optional types ~= Boolean types
Now it's familiar?
type LayoutProps = { main?: boolean, header?: boolean, side?: boolean }
<Layout main />
<Layout header />
<Layout side />
3😊
2^3 = 8😱
Poorly typed optionals are as bad as
poorly modeled booleans
Consider implicit states too!
Imagine a list of items
items.length === 0
Empty State
items.length > 0
Happy State
Modeling Events and Side effects
UI is event-driven
Being event-driven means hundreds of objects passing messages to each other
when software grows, integration between event handlers gets lost!
There are many ways that user can get from A to B!
we assume the user will take the happiest path!
A
B
To avoid unhappy states, we guard!
Guard in event handler
Guard down the view layer
if (authState.token == null) { authService.refreshToken(); }
if (!formState.isSubmitting) { formService.submit(); }
Business logic spread everywhere
Increase
cyclomatic complexiy
Dependant on
current State
<Button disabled={isSubmitting} />
Technically Fragile
exposed to race conditions
INvalid events are allowed in the code but not allowed by the UI
we need a solid way to model the behaviour
LOts of challenges
modeling manually is impractical
Human brain comes short
Finite state machines
State
State
State
Event
Event
Finite state machines eliminate impossible states
Event-derivn follows
event=>Action
if (action.type === "LOGOUT") { authService.logout(...) }
Event
Action
FSM follows (state,event)=>Action
states: { loggedout: { on: {LOGIN: {actions: "login"}} }, loggedin: { on: {LOGOUT: {actions: "logout"}} } }
Event
Action
state
Finite state machines eliminate race conditions
states: { idle: {on: {SEARCH: "searching"}}, searching: {on: {SUCCESS: "render", ERROR: "error"}}, render: {on: {SEARCH: "searching"}} }
NO race condition!
searching state won't acecept search event
Finite state machines are firewalls
Finite state machines force you to model errors
Finite state machines force you to model implicit states
Finite state machines GIve your states a proper structure
Finite state machines have first class support for
side effects
Fire and forget
Sending local Notifications
Analytics
Crash Reporting
Activities
Kept alive running actions in a certain state
(side effects over time)
Beeping
flashing icon next to the live events in the social events feed
Stream callbacks
Activities with impact
(their result matters)
Data polling from server
Websockets
Geolocation changes
Invoked Services
Asynchronous side effects fired once
Detecting the geolocation support
Fetching data from server
Promises
Finite state machines are serializable
Portable behaviour
Share logic between platforms and languages
Decouple Presentation from logic
states: {
location_find: {
invoke: {
src: "getCurrentLocation",
// web
initMachine(machine).withConfig({
services: {
getCurrentLocation: navigator.geolocation...
}
})
// mobile
initMachine(machine).withConfig({
services: {
getCurrentLocation: ReactNative.geolocation...
}
})
// test environment
initMachine(machine).withConfig({
services: {
getCurrentLocation: Promise.resolve(setTimeout(...))
}
})
with Finite state machines you only test your integrations
Finite state machines are Predicatble
next events
possible transitions
Travel to future
Finite state machines
can be visualized
Take these away
from this talk
Writing software is easy, maintaining it is hard.
with Finite state machines Adding features feels safer
Software
Test
Document
Auto generated
Abstract modeling
If you don't model the behaviour of your software,
it will shape IMPLICITLY around the code you write
Conference Wifi is slow...
Thank You!
Farzad Yousef Zadeh
@farzad_yz