AirJD 焦点
AirJD

没有录音文件
00:00/00:00
加收藏

Lesscode(Ruby on Rails简单吗) by Xuejie Xiao

发布者 ruby
发布于 1447376342606  浏览 6522 关键词 Ruby, Redis, English 
分享到

第1页

Lesscode

Xuejie Xiao @defmacro



第3页

What this talk is about



第4页

Simplicity



第5页

What this talk is NOT about



第6页

Micro services



第7页

Perfomance



第8页

Shitty Rails Clone



第9页

Software is hard

• Daily changed features • Unreasonable Deadlines



第13页

Software is hard to make



第14页

How do we handle complexity?



第15页

What’s the simplest software?



第16页

No software at all!

Zero complexity



第17页

But that’s unrealistic



第18页

What is simple? What is complex?



第19页

Simple vs. Easy



• Simple • One fold/task



Simple!



• No interleaving



• Defined by us



• Easy



• Near our capabilities



• Defined by problems



From Simplicity Matters @ RailsConf 2012 by Rich Hickey, https://www.youtube.com/watch?v=rI8tNMsozo0



第20页

Complexity

• Essential Complexity • Determined by the problem

• Accidental Complexity • Exists because our tools are not perfect



第21页

Easy <=> Essential Complexity Simple <=> Accidental Complexity



第22页

Easy <=> Essential Complexity (Hard) Simple <=> Accidental Complexity (Complex)



第23页

We want to reduce accidental complexity!

I.E., make software less complex



第24页

Accidental Complexity



• Simple • Untwisted

• Complex • Twisted



From Simplicity Matters @ RailsConf 2012 by Rich Hickey, https://www.youtube.com/watch?v=rI8tNMsozo0



第25页

Question: how many moving parts are needed

for a normal site?



第26页

Moving Parts

• PostgreSQL • Cache(Redis) • Ruby App



第27页

Moving Parts



• PostgreSQL • Cache(Redis) • Ruby App



<= Required? <= Optional? <= Required?



第28页

Moving Parts



• PostgreSQL • Cache(Redis) • Ruby App



<= Required! <= Required! <= Required!



第29页

What do you think Redis is?



第31页

Redis as a database? R U kidding me?



第32页

From https://twitter.com/antirez/status/634693291590725632



第33页

So Redis as a database, how does that work?



第34页

Save a record

HMSET User:1 email foo@bar.com name foo



第35页

What about model ID?

INCR User:_id



第36页

Load a record

HGETALL User:1



第37页

Update index

SADD User:emails:foo@bar.com 1



第38页

Find by index

SMEMBERS User:emails:foo@bar.com Then run HGETALL on each ID



第39页

Joint Query

SINTERSTORE User:query:b0fca790 User:emails:foo@bar.com User:name:foo

SMEMBERS User:query:b0fca790

Run whatever processing we need for each id

DEL User:query:b0fca790



第40页

Don’t forget you have Lua at your fingertip!

1 local ctoken = redis.call('HGET', KEYS[1], '_cas') 2 if (not ctoken) or ctoken == ARGV[2] then 3 local ntoken 4 if not ctoken then 5 ntoken = 1 6 else 7 ntoken = tonumber(ctoken) + 1 8 end 9 redis.call('HMSET', KEYS[1], '_sdata', ARGV[1], 10 '_cas', ntoken, '_ndata', ARGV[3]) 11 return ntoken 12 else 13 error('cas_error') 14 end



第41页

Ohm

http://ohm.keyvalue.org/



第42页

1 class Event < Ohm::Model 2 attribute :name 3 reference :venue, :Venue 4 set :participants, :Person 5 counter :votes 6 7 index :name 8 end 9 10 class Venue < Ohm::Model 11 attribute :name 12 collection :events, :Event 13 end 14 15 class Person < Ohm::Model 16 attribute :name 17 end



第43页

1 event = Event.create :name => "Ohm Worldwide Conference 2031" 2 event.id 3 # => 1 4 5 # Find an event by id 6 event == Event[1] 7 # => true 8 9 # Update an event 10 event.update :name => "Ohm Worldwide Conference 2032" 11 # => #<Event:0x007fb4c35e2458 @attributes={:name=>"Ohm Worldwide Conference"}, @_memo={}, @id="1"> 12 13 # Trying to find a non existent event 14 Event[2] 15 # => nil 16 17 # Finding all the events 18 Event.all.to_a 19 # => [<Event:1 name='Ohm Worldwide Conference 2031'>]



第44页

1 event.attendees.add(Person.create(name: "Albert")) 2 3 # And now... 4 event.attendees.each do |person| 5 # ...do what you want with this person. 6 end



第45页

Moving Parts Now

• Redis • Ruby App



第46页

Isn’t that simpler?



第47页

Let’s talk about tests



第50页

Ruby has gone too magical when it comes to

tests



第51页

From https://github.com/rspec/rspec-core/issues/2067



第52页

What does expect(…).to return?



第53页

From https://relishapp.com/rspec/rspec-expectations/docs



第54页

Wait a sec, all you give me is an example?



第55页

This is CDD, not TDD



第56页

Copy(-paste) Driven Development



第57页

More documentation

From https://relishapp.com/rspec/rspec-expectations/docs/compound-expectations



第58页

RubyDoc to the rescue

From http://www.rubydoc.info/gems/rspec-expectations/RSpec/Expectations/ExpectationTarget#to-instance_method



第59页

Huh, Matcher object does what?

From http://www.rubydoc.info/gems/rspec-expectations/RSpec/Matchers/DSL/Matcher



第60页

Complex Behavior

• expect(…).to returns a Matcher object • You don’t know Matcher unless you read RSpec

source code



第61页

MiniTest: better, but still magical

From https://github.com/seattlerb/minitest/blob/master/lib/minitest/spec.rb



第62页

Let’s face it: not everyone likes this syntax

obj.must_equal “foo”

Or

expect(obj).to eq(“foo”)



第63页

What’s wrong with this?

assert_equal obj, “foo”



第64页

Cutest

https://github.com/djanowski/cutest 118 LOC



第65页

Tests cannot be simpler

1 setup do 2 {:a => 23, :b => 43} 3 end 4 5 test "should receive the result of the setup block as a parameter" do |params| 6 assert params == {:a => 23, :b => 43} 7 end 8 9 test "should evaluate the setup block before each test" do | params| 10 params[:a] = nil 11 end 12 13 test "should preserve the original values from the setup" do | params| 14 assert 23 == params[:a] 15 end



第66页

How do we make the whole stack simple at

CitrusByte



第67页

Our choice: Cuba

http://cuba.is/



第68页

Cuba 3.4.0 has only 314 lines of code









0 Cuba 3.4.0



Sinatra 1.4.6



ActionPack 4.2.4



第69页

Gems normally used together with Cuba



Framework



|



LOC



---------------|---------------



mote



|





shield



|





scrivener



|





ohm | 647



protest



|





ost |





malone



|





nobi



|





clap



|





gs | 43



dep | 213



Notice this is never about LOC, it’s about one library fulfills one purpose only



第70页

The whole stack is ~1556 lines of code

• Read the source code! • Simple library, clear boundary • Easy to extend



第71页

AT&T M2X

https://m2x.att.com/



第72页

Redis

http://redis.io/



第73页

ChefsFeed

http://www.chefsfeed.com/



第74页

Red Stamp

https://www.redstamp.com/



第75页

Is Rails simple?



第76页

You might say: Rails feels easy to me!

• Complicated constructs can be:

• Familiar

• Ready to use

• But they are still complex, meaning they are:

• Interleaved

• Brings accidental complexity, which will bite you



第77页

Example time!



第78页

Hound

https://houndci.com/ https://github.com/thoughtbot/hound



第79页

Punchgirls Job Board

https://jobs.punchgirls.com https://github.com/punchgirls/job_board



第80页

Question: how many files are need for one request?



第81页

Route

1 Houndapp::Application.routes.draw do 2 # ... 3 4 resources :repos, only: [:index] do 5 with_options(defaults: { format: :json }) do 6 resource :activation, only: [:create] 7 resource :deactivation, only: [:create] 8 resource :subscription, only: [:create, :destroy] 9 end 10 end 11 12 # ... 13 end



第82页

Controller

1 class ActivationsController < ApplicationController 2 class FailedToActivate < StandardError; end 3 class CannotActivatePaidRepo < StandardError; end 4 5 before_action :check_repo_plan 6 7 def create 8 if activator.activate 9 analytics.track_repo_activated(repo) 10 render json: repo, status: :created 11 else 12 analytics.track_repo_activation_failed(repo) 13 render json: { errors: activator.errors }, status: 502 14 end 15 end 16 17 private



第83页

Controller (cont.)

1 def check_repo_plan 2 if repo.plan_price > 0 3 raise CannotActivatePaidRepo 4 end 5 end 6 7 def activator 8 @activator ||= RepoActivator.new(repo: repo, github_token: github_token) 9 end 10 11 def repo 12 @repo ||= current_user.repos.find(params[:repo_id]) 13 end 14 15 def github_token 16 current_user.token 17 end 18 end



第84页

Related action in ApplicationController

1 class ApplicationController < ActionController::Base 2 protect_from_forgery 3 4 before_action :force_https 5 before_action :capture_campaign_params 6 before_action :authenticate 7 8 helper_method :current_user, :signed_in? 9 10 private 11 # ... 12 13 def analytics 14 @analytics ||= Analytics.new(current_user, session[:campaign_params]) 15 end 16 17 # ... 18 end



第85页

Service Object

1 class RepoActivator 2 attr_reader :errors 3 4 def initialize(github_token:, repo:) 5 @github_token = github_token 6 @repo = repo 7 @errors = [] 8 end 9 10 def activate 11 activated = activate_repo 12 13 if activated 14 enqueue_org_invitation 15 end 16 17 activated 18 end 19 # ... 20 end



第86页

View or Serializer

1 class RepoSerializer < ActiveModel::Serializer 2 attributes( 3 :active, 4 :full_github_name, 5 :full_plan_name, 6 :github_id, 7 :id, 8 :in_organization, 9 :price_in_cents, 10 :private, 11 :stripe_subscription_id, 12 ) 13 # ... 14 end



第87页

How big is your screen?



第88页

I thought I was writing Ruby, not Objective-C?



第89页

Luckily, the action we showed is simple

• No helpers • No so-called presenter or whatever objects • We assume you already know models • before_action is not abused



第90页

What about Cuba?



第91页

Routes that perform actions

1 on "application/:id/contact" do |id| 2 application = Application[id] 3 on application && company.posts.include?(application.post) do 4 on post, param("message") do |params| 5 mail = Contact.new(params) 6 if mail.valid? 7 message = JSON.dump(application_id: id, 8 subject: params["subject"], body: params["body"]) 9 Ost[:contacted_applicant].push(message) 10 res.redirect "/post/#{application.post.id}/applications" 11 else 12 session[:error] = "All fields are required" 13 render("company/post/contact", 14 title: "Contact developer", 15 application: application, message: mail) 16 end 17 end 18 end 19 end



第92页

Filters that validates requests

1 class Contact < Scrivener 2 attr_accessor :subject, :body 3 4 def validate 5 assert_present :subject 6 assert_present :body 7 end 8 end



第93页

Views that queries and assembles data

1 <section id="contact-applicant"> 2 <h2>Contact developer</h2> 3 4 <form action="/application/{{ application.id }}/contact" 5 method="POST"> 6 <!-- ... --> 7 8 <input type="text" name="message[subject]" 9 value="{{ message.subject }}" placeholder="Subject"> 10 11 <textarea name="message[body]" 12 placeholder="This mail will be sent to the developer"> 13 {{ message.body }} 14 </textarea> 15 16 <!-- ... --> 17 </form> 18 </section>



第94页

Is your screen nicer?



第95页

We do use helpers, but it’s more general

1 module DeveloperHelpers 2 # ... 3 4 def mote_vars(content) 5 super.merge(current_developer: current_developer) 6 end 7 8 def notfound(msg) 9 res.status = 404 10 res.write(msg) 11 halt(res.finish) 12 end 13 14 # ... 15 end



第96页

Choices in Rails



第97页

“Rails is omakase.”

–David Heinemeier Hansson



第98页

People love to customize Rails

• ActiveRecord vs. Sequel • Sprockets vs. Webpack/browserify • Disable Turbolinks • Concerns considered harmful • Rails API



第99页

sequel-rails

From https://github.com/TalentBox/sequel-rails#using-sequel-rails



第100页

webpack with Rails

From https://medium.com/brigade-engineering/setting-up-webpack-with-rails-c62aea149679



第101页

Turbolinks

However, keep in mind the code for handling Turbolinks still exists!

From http://blog.steveklabnik.com/posts/2013-06-25-removing-turbolinks-from-rails-4



第102页

We could go on …



第103页

But something feels wrong



第104页

It’s good if you want to JUST use Rails.



第105页

But things become messy when you try to

extend



第106页

Why not aim for something much simpler?



第107页

Don’t worry about bootstrapping speed!

From “Simple Made Easy” at QCon London 2012 by Rich Hickey, http://www.infoq.com/presentations/Simple-Made-Easy-QCon-London-2012



第108页

Easiness doesn’t change, so let’s make our software simpler.



第109页

Lesscode

• http://lesscode.is/ • Less Code track at RubyConf 2015



第110页

“Simplicity is the ultimate sophistication.”

–Leonardo da Vinci



支持文件格式:*.pdf
上传最后阶段需要进行在线转换,可能需要1~2分钟,请耐心等待。