167 Software design basics [4d]

Posted Over 8 years ago. Visible to the public.

Reading

Read the following chapters from The Pragmatic Programmer, anniversary edition (in our library):

  • Chapter 1, Topic 3: Software Entropy
  • Chapter 2, Topic 9: The Evils of Duplication
  • Chapter 2, Topic 10: Orthogonality
  • Chapter 5, Topic 28: Decoupling (and the Law of Demeter)

Read the following chapters from Clean Code (in our library):

  • Chapter 1: Clean Code
  • Chapter 2: Meaningful Names
  • Chapter 3: Functions
  • Chapter 4: Comments
  • Chapter 5: Formatting
  • Chapter 8: Boundaries
  • Chapter 10: Classes
  • Chapter 12: Emergence
  • Chapter 17: Smells and Heuristics

Also read:

Read the following chapters from our book Growing Rails Application in Practice:

    1. Dealing with fat models
    1. Extracting service objects

Discuss with your mentor what you took away from each topic.

Rules of thumb

Apart from the concepts mentioned above, there are a many more rules that are good to follow most of the time. For example:

  • Embrace Locality: Avoid cluttering one concept across the entire code base. It should be encapsulated in single files or folders.
  • Be easy to call: A service should always try to have a simple public interface, even if that causes additional complexity within the class. For example:
    • Interacting with the model should be easy for controllers and views
    • Interacting with helpers and routes should be easy for views
  • Avoid long instruction manuals: If you need to write down an A4-letter for your colleagues on how to use your service, there might be a way to refactor it to a simpler public interface. A post-it sized note should be enough, if anything!

Exercise

We're building an e-commerce app where users can create and view invoices.

This is our current model:

class Invoice < ApplicationRecord
  has_many :items
  validates_presence_of :recipient_address, :number
end

class Invoice::Item < ApplicationRecord
  belongs_to :invoice
  belongs_to :product
  validates_numericality_of :units
end  

class Product < ApplicationRecord
  validates_presence_of :description
  validates_numericality_of :unit_price
end

This is a view that shows an invoice:

%h1
  Invoice
  = @invoice.number

%h2 Recipient
= @invoice.recipient_address

%h2 Items

%table
  %tr
    %th Description
    %th Quantity
    %th Item total

  - @invoice.items.each do |item|
    %td= item.product.description
    %td= item.units
    %td= item.units * item.product.unit_price
    
  %tr
    %th
      Invoice total
    %td(colspan=3)
      = @invoice.items.sum { |item| item.units * item.product.unit_price } * 1.19

How would you judge the quality of this code?
Try to apply what you learned with a refactoring of the model and the view. What are the advantages of your solution?

Exercise: MovieSearch vs. Authorization

Let's say there is the a MovieSearch class in your project with the following public API:

class MovieSearch
  def initialize(query)
    @query = query
  end
  
  def results
    Movie.where('title LIKE ?', @query)
  end
end
search = MovieSearch.new('Interstellar')
search.results.each do |movie|
  puts movie.title
end

Now the current user should not be allowed to search all movies, but only a subset based on their role and the movie's state. A naive inline authorization could look like this:

class MovieSearch
  def initialize(query, current_user)
    @query = query
    @current_user = current_user
  end
  
  def results
    scope = Movie.where('title LIKE ?', @query)
    unless current_user.moderator?
      scope = scope.where('state = "approved" OR user_id = ?', @current_user.id)
    end
    scope
  end
end

Try to come up with an alternative implementation where the two concepts (movie search and authorization) are less coupled.

Henning Koch
Last edit
5 months ago
Nico Winkler
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra Curriculum (2015-07-07 15:22)