Slide 1

Slide 1 text

THE DESIGN OF EVERYDAY RUBY

Slide 2

Slide 2 text

! Hi! I'm Ju, I really love Ruby, I work at AlphaSights, You can find me as @arkh4m.

Slide 3

Slide 3 text

THE DESIGN OF EVERYDAY THINGS

Slide 4

Slide 4 text

CATALOGUE OF UNFINDABLE OBJECTS

Slide 5

Slide 5 text

COFFEEPOT FOR MASOCHISTS

Slide 6

Slide 6 text

KNIFE FOR PEARS

Slide 7

Slide 7 text

KANGAROO RIFLE

Slide 8

Slide 8 text

SNOW BYCICLE

Slide 9

Slide 9 text

ALL IN ONE

Slide 10

Slide 10 text

THE DESIGN PSYCHOLOGY OF EVERYDAY THINGS

Slide 11

Slide 11 text

YOU'LL NEVER SEE A DOOR IN THE SAME WAY !

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

PLATE FOR PUSHING, HANDLE FOR PULLING.

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

HANDLE FOR PUSHING? OK...

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

WAT

Slide 18

Slide 18 text

MENTAL MODELS

Slide 19

Slide 19 text

THE DESIGNER

Slide 20

Slide 20 text

The designer is given a problem. He thinks of a solution, a conceptual model. Then he implements this model and creates an object.

Slide 21

Slide 21 text

THE USER

Slide 22

Slide 22 text

The user is given an object. He looks at it and tries to use it. He builds a mental model of how the object works.

Slide 23

Slide 23 text

DESIGNER MODEL != USER MODEL

Slide 24

Slide 24 text

THE DESIGNER CANNOT EXPLAIN TO THE USER HOW THE OBJECT WORKS

Slide 25

Slide 25 text

Design is an act of communication between the designer and the user, except that all the communication has to come from the device itself. — Don Norman

Slide 26

Slide 26 text

A device which speaks clearly makes the difference between a great user experience and a horrible one.

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

WHAT DOES THIS HAVE TO DO WITH ME?

Slide 30

Slide 30 text

I'm not a designer.

Slide 31

Slide 31 text

FALSE

Slide 32

Slide 32 text

We spend our days reading, writing and obsessing over software.

Slide 33

Slide 33 text

WRITING SOFTWARE IS AN ACT OF COMMUNICATION

Slide 34

Slide 34 text

When we read software, we're Users.

Slide 35

Slide 35 text

When we write software, we're Designers.

Slide 36

Slide 36 text

When we hate software, we're Angry Users.

Slide 37

Slide 37 text

DESIGN PRINCIPLES APPLIED TO SOFTWARE WRITING

Slide 38

Slide 38 text

VISIBILITY AFFORDANCES CONSTRAINTS NATURAL MAPPINGS

Slide 39

Slide 39 text

VISIBILITY

Slide 40

Slide 40 text

A device’s functions should be visible.

Slide 41

Slide 41 text

Less frequently used functions should be hidden to reduce the apparent complexity of the device.

Slide 42

Slide 42 text

At all times, it should be evident to the user what actions are available and how to perform them.

Slide 43

Slide 43 text

AFFORDANCES

Slide 44

Slide 44 text

Affordances are visual clues that suggest how to manipulate an object.

Slide 45

Slide 45 text

Plates on doors afford pushing, Handles on doors afford pulling.

Slide 46

Slide 46 text

LET'S SEE SOME CODE!

Slide 47

Slide 47 text

No wait, let's try the Squint Test..

Slide 48

Slide 48 text

class Anagram attr_reader :word def initialize(word) @word = word.downcase end def matches(candidates) candidates.select{ |candidate| is_anagram?(candidate) } end def is_anagram?(candidate) word != candidate.downcase && word_distribution == distribution_for(candidate) end def word_distribution @word_distribution ||= distribution_for(word) end def distribution_for(word) word.downcase.chars.each_with_object(Hash.new(0)) do |c, result| result[c] += 1 end end end

Slide 49

Slide 49 text

class Anagram attr_reader :word def initialize(word) @word = word.downcase end def matches(candidates) candidates.select{ |candidate| is_anagram?(candidate) } end private def is_anagram?(candidate) word != candidate.downcase && word_distribution == distribution_for(candidate) end def word_distribution @word_distribution ||= distribution_for(word) end def distribution_for(word) word.downcase.chars.each_with_object(Hash.new(0)) do |c, result| result[c] += 1 end end end

Slide 50

Slide 50 text

USE PUBLIC METHODS TO MAKE THE API VISIBLE

Slide 51

Slide 51 text

USE PRIVATE METHODS TO HIDE IMPLEMENTATION

Slide 52

Slide 52 text

CONSTRAINTS

Slide 53

Slide 53 text

Affordances suggest the range of possibilities, Constraints limit the number of alternatives.

Slide 54

Slide 54 text

The best way of making something easy to use is to restrict the possible choices.

Slide 55

Slide 55 text

Make it impossible to do wrong.

Slide 56

Slide 56 text

module RasmusString def strpos(haystack, needle, offset = 0) end def stristr(haystack, needle, before_needle = false) end end module RasmusArray def in_array(needle, haystack, strict = nil) end def array_search(needle, haystack, strict = nil) end end

Slide 57

Slide 57 text

module RasmusString def strpos(haystack:, needle:, offset: 0) end def stristr(haystack:, needle:, before_needle: false) end end module RasmusArray def in_array(haystack:, needle:, strict: nil) end def array_search(haystack:, needle:, strict: nil) end end

Slide 58

Slide 58 text

NATURAL MAPPINGS

Slide 59

Slide 59 text

Your design should take advantadge of physical analogies and cultural standards.

Slide 60

Slide 60 text

A natural mapping leads to a clear conceptual model and is much easier to understand.

Slide 61

Slide 61 text

Any design that requires labels, diagrams or instructions lacks a good natural mapping.

Slide 62

Slide 62 text

class User < ActiveRecord::Base before_save :geocode def geocode Geocoder.new(self).process # if Geocoder returns false, it will halt the # callback chain and Rails won't save true end end

Slide 63

Slide 63 text

ActiveRecord::Base.class_eval do def without_halting_save yield true end end class User < ActiveRecord::Base before_save :geocode def geocode without_halting_save { Geocoder.new(self).process } end end

Slide 64

Slide 64 text

COMMENTS SHOULD BE YOUR VERY LAST RESORT

Slide 65

Slide 65 text

TRY TO WRITE TRUTHFUL METHODS NAMES INSTEAD

Slide 66

Slide 66 text

So, what's the point of it all?

Slide 67

Slide 67 text

CLARITY

Slide 68

Slide 68 text

I concentrate on one component: making things that are understandable and usable. — Don Norman

Slide 69

Slide 69 text

CONCENTRATE ON ONE COMPONENT

Slide 70

Slide 70 text

WRITE SOFTWARE THAT IS UNDERSTANDABLE AND USABLE

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

Where are we going as an industry?

Slide 73

Slide 73 text

THE HOLY GRAIL

Slide 74

Slide 74 text

If you want to be a pro, all you have to do is just follow these set of rules...

Slide 75

Slide 75 text

LOD LAW OF DEMETER

Slide 76

Slide 76 text

class Sport < ActiveRecord::Base has_many :leagues end class League < ActiveRecord::Base belongs_to :sport has_many :teams end class Team < ActiveRecord::Base belongs_to :league end

Slide 77

Slide 77 text

class Sport < ActiveRecord::Base has_many :leagues end class League < ActiveRecord::Base belongs_to :sport has_many :teams end class Team < ActiveRecord::Base belongs_to :league def sport_name league.sport.name end end

Slide 78

Slide 78 text

class Sport < ActiveRecord::Base has_many :leagues end class League < ActiveRecord::Base belongs_to :sport has_many :teams def sport_name sport.name end end class Team < ActiveRecord::Base belongs_to :league has_many :players def sport_name league.sport_name end end

Slide 79

Slide 79 text

TDD TEST DRIVEN DEVELOPMENT

Slide 80

Slide 80 text

require 'ostruct' class Person < OpenStruct def age Date.today.year - birthday.year end end describe Person do let(:ju) { Person.new(birthday: Date.new(1986)) } context "#age" do it "returns its age" do Timecop.travel(Date.new(2014)) do expect(ju.age).to eq(28) end end end end

Slide 81

Slide 81 text

require 'ostruct' class Person < OpenStruct def age(now = Date.today) now.year - birthday.year end end describe Person do let(:ju) { Person.new(birthday: Date.new(1986)) } context "#age" do it "returns its age" do expect(ju.age(Date.new(2014))).to eq(28) end end end

Slide 82

Slide 82 text

class Author < ActiveRecord::Base has_many :posts end class AuthorPresenter < BasePresenter def posts_previews posts.map(&:preview) end end

Slide 83

Slide 83 text

describe AuthorPresenter do let(:post) { Post.new(preview: "I love Ruby") } let(:ju) { double(posts: [post]) } let(:presenter) { AuthorPresenter.new(ju) } context "#posts_previews" do it "returns posts previews" do expect(presenter.posts_previews).to eq(["I love Ruby"]) end end end

Slide 84

Slide 84 text

class Author < ActiveRecord::Base has_many :posts end class AuthorPresenter < BasePresenter def posts_previews posts.published.latest_first.map(&:preview) end end

Slide 85

Slide 85 text

describe AuthorPresenter do let(:post) { Post.new(preview: "I love Ruby") } let(:ju) do double( posts: double( published: double( latest_first: [post] ) ) ) end let(:presenter) { AuthorPresenter.new(ju) } context "#posts_previews" do it "returns posts previews" do expect(presenter.posts_previews).to eq(["I love Ruby"]) end end end

Slide 86

Slide 86 text

DRY DON'T REPEAT YOURSELF

Slide 87

Slide 87 text

require 'ostruct' class Person < OpenStruct def can_drink?(now = Date.today) age(now) >= 21 end def age(now = Date.today) now.year - birthday.year end end

Slide 88

Slide 88 text

require 'ostruct' class Person < OpenStruct def can_drink?(now = date_today) age(now) >= 21 end def age(now = date_today) now.year - birthday.year end private def date_today Date.today end end

Slide 89

Slide 89 text

require 'ostruct' class Person < OpenStruct def can_drink? age >= 21 end def age Date.today.year - birthday.year end end

Slide 90

Slide 90 text

SOLID SRP, OPEN-CLOSED, LISKOV, IS, DIP

Slide 91

Slide 91 text

class EmployeesController < ApplicationController def create @employee = Employee.new(params[:employee]) if @employee.save redirect_to @employee, notice: "Employee #{@employee.name} created" else render :new end end end

Slide 92

Slide 92 text

class EmployeesController < ApplicationController def create CreateRunner.new(self, EmployeesRepository.new).run(params[:employee]) end def create_succeeded(employee, message) redirect_to employee, notice: message end def create_failed(employee) @employee = employee render :new end end

Slide 93

Slide 93 text

class CreateRunner attr_reader :context, :repo def initialize(context, repo) @context = context @repo = repo end def run(employee_attrs) @employee = repo.new_employee(employee_attrs) if repo.save_employee context.create_succeeded(employee, "Employee #{employee.name} created") else context.create_failed(employee) end end end

Slide 94

Slide 94 text

class EmployeesRepository def new_employee(*args) Biz::Employee.new(Employee.new(*args)) end def save_employee(employee) employee.save end end

Slide 95

Slide 95 text

require 'delegate' module Biz class Employee < SimpleDelegator def self.wrap(employees) employees.wrap { |e| new(e) } end def class __getobj__.class end # Biz logic ... AR is only a data-access object end end

Slide 96

Slide 96 text

class EmployeesController < ApplicationController def create @employee = Employee.new(employee_params) if @employee.save redirect_to @employee, notice: "Employee #{@employee.name} created" else render :new end end end

Slide 97

Slide 97 text

class EmployeesController < ApplicationController def create CreateRunner.new(self, EmployeesRepository.new).run(params[:employee]) end def create_succeeded(employee, message) redirect_to employee, notice: message end def create_failed(employee) @employee = employee render :new end end class CreateRunner attr_reader :context, :repo def initialize(context, repo) @context = context @repo = repo end def run(employee_attrs) @employee = repo.new_employee(employee_attrs) if repo.save_employee context.create_succeeded(employee, "Employee #{employee.name} created") else context.create_failed(employee) end end end class EmployeesRepository def new_employee(*args) Biz::Employee.new(Employee.new(*args)) end def save_employee(employee) employee.save end end require 'delegate' module Biz class Employee < SimpleDelegator def self.wrap(employees) employees.wrap { |e| new(e) } end def class __getobj__.class end # Biz logic ... AR is only a data-access object end end

Slide 98

Slide 98 text

No content

Slide 99

Slide 99 text

Following all the rules all the time...

Slide 100

Slide 100 text

No content

Slide 101

Slide 101 text

THERE ARE NO RULES WITHOUT DRAWBACKS

Slide 102

Slide 102 text

THINK ABOUT THE TRADEOFFS

Slide 103

Slide 103 text

THINK ABOUT CLARITY

Slide 104

Slide 104 text

INSTEAD OF SOLID, TRY

Slide 105

Slide 105 text

AULIC

Slide 106

Slide 106 text

AVOID

Slide 107

Slide 107 text

USELESS

Slide 108

Slide 108 text

LEVELS OF

Slide 109

Slide 109 text

INDIRECTION

Slide 110

Slide 110 text

COURAGEOUSLY

Slide 111

Slide 111 text

AVOID USELESS LEVELS OF INDIRECTION COURAGEOUSLY

Slide 112

Slide 112 text

THANKS, DISCUSSION TIME! ⚡ ALPHASIGHTS.COM/RUBY