Designing and Maintaining Software (DAMS) Louis Rose
Lean software… Has no extra parts Solves the problem at hand and no more Is often easier to change (i.e., is more habitable)
The Advice I Want to Give Your classes should have between no less than X and no more than Y attributes / methods.
The Advice I Can Give Aim for relatively small things. (And then make them a little smaller than that). Start with a larger thing, if you like or must. Then refactor.
Extract Method
Identify extraction class Menu def display puts "****************" puts "** Pizzas **" puts "****************" pizzas.each do |pizza| … end end end
Create new method class Menu def display puts "****************" puts "** Pizzas **" puts "****************" pizzas.each do |pizza| … end end def display_banner end end
Copy method body class Menu def display puts "****************" puts "** Pizzas **" puts "****************" pizzas.each do |pizza| … end end def display_banner puts "****************" puts "** Pizzas **" puts "****************" end end
Use the new method class Menu def display display_banner pizzas.each do |pizza| … end end def display_banner puts "****************" puts "** Pizzas **" puts "****************" end end
Extract Method (with local variable)
Identify extraction class Menu def display sections.each do |section| puts "****************" puts "** #{section} **" puts "****************" pizzas.each do |pizza| … end end end end
Create new method class Menu def display sections.each do |section| puts "****************" puts "** #{section} **" puts "****************" pizzas.each do |pizza| … end end end def display_banner(section) end end
Copy the method body class Menu def display sections.each do |section| puts "****************" puts "** #{section} **" puts "****************" pizzas.each do |pizza| … end end end def display_banner(section) puts "****************" puts "** #{section} **" puts "****************" end end
Use the new method class Menu def display sections.each do |section| display_banner(section) pizzas.each do |pizza| … end end end def display_banner(section) puts "****************" puts "** #{section} **" puts "****************" end end
Extract Method (with a local assignment)
Identify extraction class Menu def display most_popular = pizzas.first pizzas.each do |pizza| most_popular = pizza if pizza.likes > most_popular.likes end puts "Customer favourite: #{most_popular.name}" end end
Create new method class Menu def display most_popular = pizzas.first pizzas.each do |pizza| most_popular = pizza if pizza.likes > most_popular.likes end puts "Customer favourite: #{most_popular.name}" end def most_popular_from(pizzas) end end
Copy method body class Menu def display most_popular = pizzas.first pizzas.each do |pizza| most_popular = pizza if pizza.likes > most_popular.likes end puts "Customer favourite: #{most_popular.name}" end def most_popular_from(pizzas) most_popular = pizzas.first pizzas.each do |pizza| most_popular = pizza if pizza.likes > most_popular.likes end end end
Return the assigned value class Menu def display most_popular = pizzas.first pizzas.each do |pizza| most_popular = pizza if pizza.likes > most_popular.likes end puts "Customer favourite: #{most_popular.name}" end def most_popular_from(pizzas) most_popular = pizzas.first pizzas.each do |pizza| most_popular = pizza if pizza.likes > most_popular.likes end most_popular end end
Use the new method class Menu def display most_popular = most_popular_from(pizzas) puts "Customer favourite: #{most_popular.name}" end def most_popular_from(pizzas) most_popular = pizzas.first pizzas.each do |pizza| most_popular = pizza if pizza.likes > most_popular.likes end most_popular end end
Extract Class
Identify extraction class Menu def display end def display_banner end def display_pizza end def display_pasta end def display_favourite_dishes end def load_from_xml(path) end def add_pizza(pizza) end def add_pasta(pasta) end end
Create new class class Menu def display end def display_banner end def display_pizza end def display_pasta end def display_favourite_dishes end def load_from_xml(path) end def add_pizza(pizza) end def add_pasta(pasta) end end
class MenuDisplayer end
Create link to new class class Menu def display end def display_banner end def display_pizza end def display_pasta end def display_favourite_dishes end private def displayer @d ||= MenuDisplayer.new end … end
class MenuDisplayer end
For each method… class Menu def display end def display_banner end def display_pizza end def display_pasta end def display_favourite_dishes end private def displayer @d ||= MenuDisplayer.new end … end
class MenuDisplayer end
Copy method to new class class Menu def display end def display_banner end def display_pizza end def display_pasta end def display_favourite_dishes end private def displayer @d ||= MenuDisplayer.new end … end
class MenuDisplayer def display end end
Forward original to new class Menu def display displayer.display end def display_banner end def display_pizza end def display_pasta end def display_favourite_dishes end private def displayer @d ||= MenuDisplayer.new end … end
class MenuDisplayer def display end end
Repeat for all methods class Menu def display displayer.display end
class MenuDisplayer def display end
def display_banner displayer.display_banner end
def display_banner end
def display_pizza displayer.display_pizza end
def display_pizza end
def display_pasta displayer.pasta end
def display_pasta end
def display_favourite_dishes displayer.favourite_dishes end private def displayer @d ||= MenuDisplayer.new end … end
def display_favourite_dishes end end
Refactor original class class Menu def display displayer.display end
class MenuDisplayer def display end
def display_banner displayer.display_banner end
def display_banner end
def display_pizza displayer.display_pizza end
def display_pizza end
def display_pasta displayer.pasta end
def display_pasta end
def display_favourite_dishes displayer.favourite_dishes end private def displayer @d ||= MenuDisplayer.new end … end
def display_favourite_dishes end end
Private methods? class Menu def display displayer.display end private def display_banner displayer.display_banner end
class MenuDisplayer def display end private def display_banner end
def display_pizza displayer.display_pizza end
def display_pizza end
def display_pasta displayer.pasta end
def display_pasta end
def display_favourite_dishes displayer.favourite_dishes end def displayer @d ||= MenuDisplayer.new end … end
def display_favourite_dishes end end
Private methods? class Menu def display displayer.display end
class MenuDisplayer def display end private
private def displayer @d ||= MenuDisplayer.new end … end
def display_banner end def display_pizza end def display_pasta end def display_favourite_dishes end end
Must be public? class Menu def display displayer.display end def display_banner displayer.display_banner end def display_pizza displayer.display_pizza end def display_pasta displayer.pasta end def display_favourite_dishes displayer.favourite_dishes end private def displayer @d ||= MenuDisplayer.new end … end
class MenuDisplayer def display end private def display_banner end def display_pizza end def display_pasta end def display_favourite_dishes end end
Use Forwardable class Menu extend Forwardable def_delegators :displayer, :display, :display_banner, :display_pizza, :display_pasta :display_favourite_dishes
class MenuDisplayer def display end private def display_banner end
private def displayer @d ||= MenuDisplayer.new end … end
def display_pizza end def display_pasta end def display_favourite_dishes end end
(Almost) never do this class Menu # Now part of public API def displayer @d ||= MenuDisplayer.new end … end
class MenuDisplayer def display end private def display_banner end def display_pizza end def display_pasta end def display_favourite_dishes end end
Summary There’s no “correct” size for a class (method / project). Aim for small things. Experiment with REALLY small things to see where the boundaries lie. Design or refactor towards smaller things by extracting methods & classes.
Learn Enumerable! class Menu def display most_popular = most_popular_from(pizzas) puts "Customer favourite: #{most_popular.name}" end def most_popular_from(pizzas) most_popular = pizzas.first pizzas.each do |pizza| most_popular = pizza if pizza.likes > most_popular.likes end most_popular end end
Learn Enumerable! class Menu def display most_popular = most_popular_from(pizzas) puts "Customer favourite: #{most_popular.name}" end def most_popular_from(pizzas) pizzas.max_by(&:likes) end end
Designing and Maintaining Software (DAMS) - GitHub
Page 1. Getting Lean. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Lean software⦠Has no extra parts. Solves the problem at hand and no more. Is often easier to change (i.e., is more habitable). Page 3. The Advice I Want to Give. Your classes should have between no less than X and no more than Y.
ASTs are tree data structures that can be analysed for meaning (following JLJ in SYAC 2014/15) ... More Cohesive. Avoids Duplication. Clearer. More Extensible.
Open-source. Influenced by Perl, Smalltalk, Eiffel, Ada and Lisp. Dynamic. Purely object-oriented. Some elements of functional programming. Duck-typed class Numeric def plus(x) self.+(x) end end y = 5.plus(6) https://www.ruby-lang.org/en/about · http
Why not duplicate? Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Habitable Software. Leaner. Less Complex. Loosely Coupled. More Cohesive. Avoids Duplication. Clearer. More Extensible ??? Page 3. Bad Practice. Page 4. Don't Repeat Yo
âWe have tried to demonstrate that it is almost always incorrect to begin the decomposition of a system into modules on the basis of a flowchart. We propose instead that one begins with a list of difficult design decisions or design decisions which
Tools: Vagrant. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Bugs that appear in production and that can't be reproduced by a developer on their machine are really hard to fix. Problem: âIt works on my machineâ. Page 3. Why does
Clear Documentation. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Bad documentation. Misleading or contradictory find_customer(id). CustomerGateway. Used to look up a customer by their customer number. Page 3. Bad documentation. Red
%w.rack tilt date INT TERM..map{|l|trap(l){$r.stop}rescue require l};. $u=Date;$z=($u.new.year + 145).abs;puts "== Almost Sinatra/No Version has taken the stage on #$z for development with backup from Webrick". $n=Module.new{extend. Rack;a,D,S,q=Rack
R&D: sketch habitable solutions on paper, using UML. 4. Evaluate solutions and implement the best, using TDD. Probably start again at 3. 5. Give to the product owner to validate. Probably start again at 1. 6. Put into production for customers to eval
Habitable Software. Leaner. Less Complex. Loosely Coupled. More Cohesive. Avoids Duplication. Clearer. More Extensible ??? Page 3. Lean. âPerfection is finally achieved not when there is no longer anything to add, but when there is no longer anythi
Fixes issue #42. Users were being redirected to the home page after login, which is less useful than redirecting to the page they had originally requested before being redirected to the login form. * Store requested path in a session variable. * Redi
What is it? Several pieces of data are often used together. Why is it problematic? Behaviour that operates on the clump has no home. (and consequently is often duplicated). When does it arise? High cohesion of the clump has not been detected. D
Observers. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Page 3. Delivery people need to know when pizzas are ready class Pizza def initialize(delivery_person). @delivery_person = delivery_person end def bake cook # blocking call. @d
âWe want the reading of code to be easy, even it makes the writing harder. (Of course, there's no way to write code without also reading it, soâ¦)â - Bob Martin. Clean Code. Prentice Hall, 2009. Page 5. Page 6. User Experience. âA person of av
Getting loose coupling. Designing and Maintaining Software (DAMS). Louis Rose ... should not depend on low-level modules. Both should depend on abstractions.â âAbstractions should not depend on details. Details should depend on abstractions.â -
Automatically detect similar fragments of code. class StuffedCrust def title. "Stuffed Crust " +. @toppings.title +. " Pizza" end def cost. @toppings.cost + 6 end end class DeepPan def title. "Deep Pan " +. @ingredients.title +. " Pizza" end def cost
Ruby Testing Frameworks. 3 popular options are: RSpec, Minitest and Test::Unit. We'll use RSpec, as it has the most comprehensive docs. Introductory videos are at: http://rspec.info ...
Clear Names. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Naming is hard. âThere are only two hard things in Computer. Science: cache invalidation and naming things.â - Phil Karlton http://martinfowler.com/bliki/TwoHardThings.ht
Coupling Between Objects. Counts the number of other classes to which a class is coupled (other than via inheritance). CBO(c) = |d â C - (1cl U Ancestors(C))| uses(c, d) V uses(d, c). - Chidamber and Kemerer. A metrics suite for object-oriented des
Reducing duplication. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Tactics. Accentuate similarities to find differences. Favour composition over inheritance. Know when to reach for advanced tools. (metaprogramming, code generation).
Plug-ins. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Problem. Page 3. Current Architecture. Shareable. Likeable. Food. Pizza. Liking and sharing foods are primary business concerns, so shouldn't be implemented as delegators. Page
When we are testing the way that a unit behaves when a condition is met, use a stub to setup the condition. Solution: use stubs for queries class Subscription ... def bill(amount) unless payments.exists(subscription_id: id) payments.charge(subscripti
Getting Cohesion. Designing and Maintaining Software (DAMS). Louis Rose. Page 2. Single Responsibility. Principle. A class should have only one reason to change. - Martin and Martin. Chapter 8, Agile Principles, Patterns and Practices in C#, Prentice
Size != Complexity. âImagine a small (50 line) program comprising. 25 consecutive "IF THEN" constructs. Such a program could have as many as 33.5 million distinct control paths.â - Thomas J. McCabe. IEEE Transactions on Software Engineering, 2:4,