ELI5
Building A rails app for 5 year olds
(Not really)
Justin Herrick
Explain
(it)
(to me)
like I'm five
https://www.reddit.com/r/explainlikeimfive/
Reddit 🙄
How do?
-
Simplify
-
Analogize
-
Use Progressive Disclosure
-
Provided Resources
-
Educate
Lets do that!
EVERYONE KNOWS THAT DEBUGGING IS TWICE AS HARD AS WRITING A PROGRAM IN THE FIRST PLACE. SO IF YOU'RE AS CLEVER AS YOU CAN BE WHEN YOU WRITE IT, HOW WILL YOU EVER DEBUG IT?
- Brian Kernighan
the three great virtues of a programmer: laziness, impatience, and hubris
- Larry Wall
So What are we aiming for?
robustness
onboarding
Debugging
Whats going wrong?
Complexity
Bad Abstractions
Bad Code
bad tests
(Or No Abstractions)
(Or No Tests)
(In a rails app? Never!)
We Can do better
Patterns
"Cognitive Offboarding"
A shared language
A common vocabulary
A toolbox full of hammers
Can be used and abused
Design Patterns
So many patterns, so little time
- Factory Method
- Null Object
- Active Record
- Singleton
- Adapter
- Bridge
- Decorator
- Memento
- Controller
- Visitor
- Command
- Guard Clause
Dont just use patterns because they exist
Document when and why you're using a particular pattern
establish system patterns
Patterns are consistent
Patterns are predictable
(The principle of least astonishment)
system patterns establish truth
system patterns establish reasoning
system patterns provoke conversation
How to design a pattern
build for emulation
Restrict the design space
Add Fail-safes and defaults
document
write some tests.
(Please)
Lets go deeper
Prefer small objects over generic data structures
(Avoiding Primitive Obsession)
generic data structures
def fetch_invoices(account_id)
response = Invoice::Fetcher.new(account_id).get_all
if response.status_code == OKAY
{
status: OKAY,
invoices: response.data
}
else
{
status: ERROR,
invoices: nil,
error: "Couldn't fetch"
}
end
endSmall objects
def fetch_invoices(account_id)
response = Invoice::Fetcher.new(account_id).get_all
if response.status_code == OKAY
InvoiceOkayResponse.new(response.data)
else
InvoiceErrorResponse.new(response.error)
end
endSmall objects
def fetch_invoices(account_id)
response = Invoice::Fetcher.new(account_id).get_all
InvoiceResponder.process(response)
endLocalize state
(Avoid shared mutable state)
non-Localize state
def prepare_events
if events_exist?
@presenter_one = @next_event.main.presenter_one.last_name
@presenter_two = @next_event.main.presenter_two.last_name
elsif @next_event.try(:title_tag_line)
presenters = @next_event.tag_line.split(tag_line_del)
@presenter_one = presenters.first
@presenter_two = presenters.last
else
@presenter_one = @prev_event.main.presenter_one.last_name
@presenter_two = @prev_event.main.presenter_two.last_name
end
endLocalized state
def prepare_events(next_event, prev_event = nil)
if events_exist?
[ next_event.main.presenter_one.last_name,
next_event.main.presenter_two.last_name ]
elsif next_event.try(:title_tag_line)
presenters = next_event.tag_line.split(tag_line_del)
[ presenters.first,
presenters.last ]
else
[ prev_event.main.presenter_one.last_name
prev_event.main.presenter_two.last_name ]
end
end
def show
# ... some code
@presenter_one,
@presenter_two = prepare_events(@next_event, @prev_event)
endLocalized state
def event_presenters(next_event, prev_event = nil)
if events_exist?(next_event)
[ next_event.first_presenter, next_event.second_presenter ]
elsif next_event.try(:title_tag_line)
presenters = next_event.tag_line.split(tag_line_del)
[ presenters.first, presenters.last ]
elsif prev_event
[ prev_event.first_presenter, prev_event.second_presenter ]
end
endmake order-dependence explicit
Unexplicit order dependence
before_filter :run_before
before_filter :prepare_event_page, only: [:show_without_cache, :show]
before_filter :set_events, only: [:show_without_cache, :show]
around_filter :set_visitor, only: [:show_without_cache, :show]
after_filter :run_analytics_job, if: Proc.new{|c| !@next_event.present? }
before_filter :parse_query
def show
if !@query.nil?
#...
else
#...
end
end
def show_without_cache
if @event.nil?
render text: "Not Found", status: 404
end
end
explicit order dependence
def show
public_event_display do |events, visitor, query|
if query
#...
else
#...
end
end
end
def show_without_cache
public_event_display do |events, _, _|
if events.empty?
render text: "Not Found", status: 404
end
end
end
private
def public_event_display
run_before
prepare_evnet_page
events = get_events
visitor = get_visitor
query = parse_query(params)
yield(events, visitor, query)
AnalyticsJob.perform(visitor, query) if has_next_event?(events)
endexplicit order dependence
def show
public_event_display do |events, visitor, query|
if query
#...
else
#...
end
end
end
def show_without_cache
public_event_display do |events, _, _|
if events.empty?
render text: "Not Found", status: 404
end
end
end
private
def public_event_display
run_before
prepare_evnet_page
events = get_events
visitor = get_visitor
query = parse_query(params)
yield(events, visitor, query)
AnalyticsJob.perform(visitor, query) if has_next_event?(events)
endRemove base assumptions
Review base assumptions
Remove Bad assumptions
Validate
Repeat
Questions?
Thanks!
ELI5: Write code for those who don't
By Justin Herrick
ELI5: Write code for those who don't
- 160