Una Critica a Rails by Luca Guidi

Post on 12-Sep-2014

1058 Views

Category:

Technology

1 Downloads

Preview:

Click to see full reader

DESCRIPTION

Un profondo punto di vista sul perché Ruby on Rails ha rivoluzionato lo sviluppo web. Questo talk focalizzerà la sua attenzione sul “Golden Path” di Rails, sui motivi del suo successo, sui problemi più comuni, e su come le sue API possano essere migliorate. Impareremo a trarre beneficio da uno strumento tanto potente quanto pericoloso, di come mitigare le implicazioni architetturali, di design e testabilità delle vostre applicazioni, migliorando la qualità del codice..

Transcript

Luca Guidi

March 22nd, 2013

A Rails Criticismhow i learned to stop worrying about the Golden Path

AGENDA

intro

Luca GuidiSenior Developer at Litmus

@jodosha - http//lucaguidi.com

litmusBeautiful Email previews

Campaign analytics

Spam filter tests

HTML code analysis

And many other..

Why Railshas revolutionized web

development?(IMHO)

Why Railshas revolutionized web

development?

(IMHO)

Convention Over Configuration

:)

Why Railshas revolutionized web

development?

(IMHO)

Convention Over Configuration

Dynamic and innovativeecosystem

:)

Why Railshas revolutionized web

development?

(IMHO)

Convention Over Configuration

Dynamic and innovativeecosystem

Productivity andDeveloper Happiness

:)

..but

The Framework is almost ten years old

..but

The Framework is almost ten years old

A Lot of Legacy Codeis around

..but

The Framework is almost ten years old

A Lot of Legacy Codeis around

Upgrades to a major release are *painful*

AGENDA

intro problems

ActiveRecord

Active

Record

Models != Records

Active

Record

Encapsulation violations

Active

Record

1 if article.state != 'published'2 article.update_attribute(state: 'published')3 end

1 unless article.published?2 article.publish!3 end4 # not completely right...

Encapsulation violations

Active

Record

1 if article.state != 'published'2 article.update_attribute(state: 'published')3 end

1 unless article.published?2 article.publish!3 end4 # not completely right...

Encapsulation violations

Active

Record

1 if article.state != 'published'2 article.update_attribute(state: 'published')3 end

1 unless article.published?2 article.publish!3 end4 # not completely right...

Tell, Don’t Askviolations

Active

Record

1 unless article.published?2 article.publish!3 end4 # not completely right...

1 article.publish!2 # push the implementation3 # into the model

Tell, Don’t Askviolations

Active

Record

1 unless article.published?2 article.publish!3 end4 # not completely right...

1 article.publish!2 # push the implementation3 # into the model

Tell, Don’t Askviolations

Active

Record

1 unless article.published?2 article.publish!3 end4 # not completely right...

1 article.publish!2 # push the implementation3 # into the model

Implicit vs Explicit API

Active

Record

1 Post.where(state:'published').2 order('created_at DESC').3 limit(5)

1 Post.most_recent_published1 class Post < ActiveRecord::Base2 def self.most_recent_published(limit = 5)3 published.recent(limit).order('created_at DESC')4 end5 6 private7 scope :published, ->() { where(state: 'published') }8 scope :recent, ->(n) { limit(n) }9 end

Implicit vs Explicit API

Active

Record

1 Post.where(state:'published').2 order('created_at DESC').3 limit(5)

1 Post.most_recent_published1 class Post < ActiveRecord::Base2 def self.most_recent_published(limit = 5)3 published.recent(limit).order('created_at DESC')4 end5 6 private7 scope :published, ->() { where(state: 'published') }8 scope :recent, ->(n) { limit(n) }9 end

Implicit vs Explicit API

Active

Record

1 Post.where(state:'published').2 order('created_at DESC').3 limit(5)

1 Post.most_recent_published1 class Post < ActiveRecord::Base2 def self.most_recent_published(limit = 5)3 published.recent(limit).order('created_at DESC')4 end5 6 private7 scope :published, ->() { where(state: 'published') }8 scope :recent, ->(n) { limit(n) }9 end

Implicit vs Explicit API

Active

Record

1 Post.where(state:'published').2 order('created_at DESC').3 limit(5)

1 Post.most_recent_published1 class Post < ActiveRecord::Base2 def self.most_recent_published(limit = 5)3 published.recent(limit).order('created_at DESC')4 end5 6 private7 scope :published, ->() { where(state: 'published') }8 scope :recent, ->(n) { limit(n) }9 end

Callbacks abuse

Active

Record

Non-persistence logic is tight to the persistence life cycle.

Eg. Sending emails

Testability issues

Active

Record

Micheal Feathers

Testability issues

Active

Record

A test is not a unit test if it talks to a database.

Micheal Feathers

ActionController

Action

Controller

It doesn’t affect too much your architecture, but it has strange OOP design.

Frankenstein Controllers

Action

Controller

1 class PostsController < ApplicationController2 before_filter :authenticate3 4 def new5 end6 7 def create8 @post = Post.new(params[:post])9 10 if @post.save11 redirect_to post_url(@post), notice: 'Yay!'12 else13 render :new14 end15 end16 end

�!& ��������� !��!��

�!& �����"!��

�!& �����������

����!%���������!�������#����!� ���������"������ �

�%�����"�!�

Frankenstein ControllersAction

Controller

1 class PostsController < ApplicationController2 before_filter :authenticate3 4 def new5 end6 7 def create8 @post = Post.new(params[:post])9 10 if @post.save11 redirect_to post_url(@post), notice: 'Yay!'12 else13 render :new14 end15 end16 end

�!& ��������� !��!��

�!& �����"!��

�!& �����������

����!%���������!�������#����!� ���������"������ �

�%�����"�!�

Frankenstein ControllersAction

Controller

1 class PostsController < ApplicationController2 before_filter :authenticate3 4 def new5 end6 7 def create8 @post = Post.new(params[:post])9 10 if @post.save11 redirect_to post_url(@post), notice: 'Yay!'12 else13 render :new14 end15 end16 end

�!& ��������� !��!��

�!& �����"!��

�!& �����������

����!%���������!�������#����!� ���������"������ �

�%�����"�!�

Frankenstein ControllersAction

Controller

1 class PostsController < ApplicationController2 before_filter :authenticate3 4 def new5 end6 7 def create8 @post = Post.new(params[:post])9 10 if @post.save11 redirect_to post_url(@post), notice: 'Yay!'12 else13 render :new14 end15 end16 end

�!& ��������� !��!��

�!& �����"!��

�!& �����������

����!%���������!�������#����!� ���������"������ �

�%�����"�!�

Frankenstein ControllersAction

Controller

1 class PostsController < ApplicationController2 before_filter :authenticate3 4 def new5 end6 7 def create8 @post = Post.new(params[:post])9 10 if @post.save11 redirect_to post_url(@post), notice: 'Yay!'12 else13 render :new14 end15 end16 end

�!& ��������� !��!��

�!& �����"!��

�!& �����������

����!%���������!�������#����!� ���������"������ �

�%�����"�!�

Frankenstein ControllersAction

Controller

1 class PostsController < ApplicationController2 before_filter :authenticate3 4 def new5 end6 7 def create8 @post = Post.new(params[:post])9 10 if @post.save11 redirect_to post_url(@post), notice: 'Yay!'12 else13 render :new14 end15 end16 end

�!& ��������� !��!��

�!& �����"!��

�!& �����������

����!%���������!�������#����!� ���������"������ �

�%�����"�!�

1 class PostsController < ApplicationController2 # ...34 def create5 @post = Post.new(params[:post])6 7 if @post.save8 # ...9 else10 # ...11 end12 end13 end

Odd classes

Action

Controller

��$�!����������!�� ������!������

��"����&!�����" ��%�"����&!������!�%��� !��!��!�������!���������$�!�������!�!�� ����������%�

1 class PostsController < ApplicationController2 # ...34 def create5 @post = Post.new(params[:post])6 7 if @post.save8 # ...9 else10 # ...11 end12 end13 end

Odd classesAction

Controller

��$�!����������!�� ������!������

��"����&!�����" ��%�"����&!������!�%��� !��!��!�������!���������$�!�������!�!�� ����������%�

1 class PostsController < ApplicationController2 # ...34 def create5 @post = Post.new(params[:post])6 7 if @post.save8 # ...9 else10 # ...11 end12 end13 end

Odd classesAction

Controller

��$�!����������!�� ������!������

��"����&!�����" ��%�"����&!������!�%��� !��!��!�������!���������$�!�������!�!�� ����������%�

1 class PostsController < ApplicationController2 # ...34 def create5 @post = Post.new(params[:post])6 7 if @post.save8 # ...9 else10 # ...11 end12 end13 end

Odd classesAction

Controller

��$�!����������!�� ������!������

��"����&!�����" ��%�"����&!������!�%��� !��!��!�������!���������$�!�������!�!�� ����������%�

1 class PostsController < ApplicationController2 # ...34 def create5 @post = Post.new(params[:post])6 # ...7 end8 end

Encapsulation violations

Action

Controller

��$�!����������!�� ������!��������� ��#���$��������������!��!� ! �����#��$ ��

1 class PostsController < ApplicationController2 # ...34 def create5 @post = Post.new(params[:post])6 # ...7 end8 end

Encapsulation violationsAction

Controller

��$�!����������!�� ������!��������� ��#���$��������������!��!� ! �����#��$ ��

1 describe PostsController do 2 it 'assigns @posts' do 3 Post.should_receive(:most_recent_published). 4 and_return(posts = [mock]) 5 get :index 6 7 expect(assigns(:posts)).to eq(posts) 8 end 9 end

Testability issues

Action

Controller

��� �� ������!����!����!� ! �����&!������!�!�� ����������%� ��$������������� !"�����!����!��

��!������������!�!���

��� �� �����������!��!�' !��� (!��� !�!�������!������!�������

1 describe PostsController do 2 it 'assigns @posts' do 3 Post.should_receive(:most_recent_published). 4 and_return(posts = [mock]) 5 get :index 6 7 expect(assigns(:posts)).to eq(posts) 8 end 9 end

Testability issuesAction

Controller

��� �� ������!����!����!� ! �����&!������!�!�� ����������%� ��$������������� !"�����!����!��

��!������������!�!���

��� �� �����������!��!�' !��� (!��� !�!�������!������!�������

1 describe PostsController do 2 it 'assigns @posts' do 3 Post.should_receive(:most_recent_published). 4 and_return(posts = [mock]) 5 get :index 6 7 expect(assigns(:posts)).to eq(posts) 8 end 9 end

Testability issuesAction

Controller

��� �� ������!����!����!� ! �����&!������!�!�� ����������%� ��$������������� !"�����!����!��

��!������������!�!���

��� �� �����������!��!�' !��� (!��� !�!�������!������!�������

1 describe PostsController do 2 it 'assigns @posts' do 3 Post.should_receive(:most_recent_published). 4 and_return(posts = [mock]) 5 get :index 6 7 expect(assigns(:posts)).to eq(posts) 8 end 9 end

Testability issuesAction

Controller

��� �� ������!����!����!� ! �����&!������!�!�� ����������%� ��$������������� !"�����!����!��

��!������������!�!���

��� �� �����������!��!�' !��� (!��� !�!�������!������!�������

1 describe PostsController do 2 it 'assigns @posts' do 3 Post.should_receive(:most_recent_published). 4 and_return(posts = [mock]) 5 get :index 6 7 expect(assigns(:posts)).to eq(posts) 8 end 9 end

Testability issuesAction

Controller

��� �� ������!����!����!� ! �����&!������!�!�� ����������%� ��$������������� !"�����!����!��

��!������������!�!���

��� �� �����������!��!�' !��� (!��� !�!�������!������!�������

1 describe PostsController do 2 it 'assigns @posts' do 3 Post.should_receive(:most_recent_published). 4 and_return(posts = [mock]) 5 get :index 6 7 expect(assigns(:posts)).to eq(posts) 8 end 9 end

Testability issuesAction

Controller

��� �� ������!����!����!� ! �����&!������!�!�� ����������%� ��$������������� !"�����!����!��

��!������������!�!���

��� �� �����������!��!�' !��� (!��� !�!�������!������!�������

ActionView

Action

View

Views aren’t views

Action

View

(but templates with logic)

Without “real” views (or presenters), we’re tempted to push presentational methods into the models.

In an ideal world we shouldn’t test our templates.

Helpers arefunctional programming

Action

View

1 def user_full_name(user)2 [ user.first_name, user.last_name ].join(' ')3 end4 5 url_for6 # vs7 Url.for8 9 posts_url

Half-assed way to solvepresentational problems in ActionView.

Helpers arefunctional programming

Action

View

1 def user_full_name(user)2 [ user.first_name, user.last_name ].join(' ')3 end4 5 url_for6 # vs7 Url.for8 9 posts_url

Half-assed way to solvepresentational problems in ActionView.

Ruby onRails

Ruby on

Rails

Rails isn’t a framework,but an application

template

Ruby on

Rails

Rails is multi-paradigmas Ruby is.

Ruby on

Rails

AGENDA

intro problems solutions

Decouple your logic fromActiveRecord as much as

possible.

Solutions

AR is focused on data, but OOP is about behavior.

Don’t think in ActiveRecord terms.

Solutions

Database is a detail, forget about associations,scopes, validations..

Let your public APIto declare intents.

Solutions

Let your design to emerge via TDD.

Keep methods and accessors private as

much as possible.

Solutions

Public APIs are hard to maintain as the codebase and the team grows.

Skinny controllersand

skinny models.

Solutions

Use service objects or DCI, they are easier and faster to test.

Don’t be afraid to extract ad-hoc classes for

specific responsibilities.

Solutions

Inner classes are your friends, they help you with details, and aren’t part of your public API.

Use DIY presenters

Solutions

They will help you to keep your models clean from presentational logic.

Refactor, refactor, refactor.

Solutions

AGENDA

intro problems solutions conclusion

Q&A

me@lucaguidi.com@jodosha

https://speakerdeck.com/jodosha/a-rails-criticism

Except where otherwise noted, this work is licensed under:http://creativecommons.org/licenses/by-nc-sa/3.0/

��������

top related