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

Спасибо!

 

The art of explicit and consistent user interfaces (404 Fest Samara 2019)

By Farzad Yousefzadeh

The art of explicit and consistent user interfaces (404 Fest Samara 2019)

The user interface is challenging to the core. There are many UI components that carry a huge deal of complexity. Usually, we deal with complexity in UI without noticing it. As truly said, the devil is in the detail. We usually don't notice this until it's too late! That's why no matter how clean our UI and their API surface looks when the first attempt is out, many bugs pop out soon and adding new features gives us a hard time! As humans, we might not have a great tool (sarcasm towards our own brain) to think, consider and compute. we tend to forget. we can't consider everything. we are unable to foresee and predict software. we carry assumptions that aren't correct all the time. This might be a reason why our software starts as an explicit, great looking codebase until the moment reaches when this so-called software transitions from evergreen to ever failing and legacy. By thinking about the history of UI development and trying to explore different complexities, we might be able to find better ways to develop UI.

  • 1,068