Safety Nets: How to check your code? (in Ruby)

Safety Nets: How to check your code? (in Ruby)

Programming gives you a great power...

But as the saying goes, "With great power comes great responsibility!"

As software developers, we cannot afford to blow off everything when shipping. That's why it's important to put in place different strategies to help us to catch errors asap, but also to avoid cruft in the long term. Come and discover those tools & techniques (Tests, Code Review, Static Analysis) and how to put them in place in your code base. Like safety nets, they will allow you to go forward with more confidence.

http://www.meetup.com/Le-Wagon-Brussels-Coding-Station/events/222354764/

3f8fcddf7ab5d1bd90b0a0a9adfd6527?s=128

Christophe Philemotte

June 23, 2015
Tweet

Transcript

  1. Safety Nets How to check your code?

  2. _toch toch Hi, I'm Christophe

  3. Belgium

  4. Belgium

  5. Founder of

  6. None
  7. Once upon a time...

  8. None
  9. 40% of fatalities = no air

  10. Peer Review BWRAF

  11. 15% of fatalities = equipment

  12. Make them Test

  13. 1st cause = Human error

  14. Be Equipped Be Assisted

  15. We're Human

  16. We make mistakes

  17. None
  18. We can be helped

  19. Scuba Diving, Civil Engineering, Manufacturing, … Software Development

  20. Safety Nets How to check your code?

  21. None
  22. None
  23. None
  24. Be Informed Then Decide

  25. Tests Code Review Static Analysis

  26. Tests Static Analysis Code Review

  27. None
  28. None
  29. None
  30. Tests Static Analysis Code Review

  31. None
  32. Tests Static Analysis Code Review

  33. ❏ tires on? ❏ painted? ❏ tank full? ❏ labelled?

    ❏ checked on bench?
  34. Tests Static Analysis Code Review

  35. None
  36. describe Invoice do let(:item1) { Item.new(20.0, 3) } let(:item2) {

    Item.new(15.0, 2) } let(:empty_invoice) { Invoice.new } let(:invoice) do tmp = Invoice.new tmp.add_item(item1) tmp.add_item(item2) tmp end
  37. it 'returns a zero total when empty' do assert_equal 0,

    empty_invoice.total end it 'returns a correct total' do assert_equal 90.0, invoice.total end
  38. it 'returns a correct total even after updating a previously

    added item' do assert_equal 90.0, invoice.total item2.quantity = 3 assert_equal 105, invoice.total end end
  39. class Invoice def initialize @items = [] end def add_item(item)

    @items << item end #...
  40. #... def total total = 0 @items.each do |item| total

    += item.price * item.quantity end total end end
  41. None
  42. class Invoice def initialize @items = [] @total = 0

    end def add_item(item) @items << item end #...
  43. #... def total @items.each do |item| @total += item.price *

    item.quantity end @total end end
  44. None
  45. #... def total @total = 0 @items.each do |item| @total

    += item.price * item.quantity end @total end end
  46. Check Functionality & Regression

  47. BUT What are we Testing?

  48. SimpleCov

  49. BUT Has someone Launched the Tests

  50. BUT "We’re not shipping your machine!"

  51. CI & Close to Prod

  52. BUT "Who tests the tests?"

  53. Mutant

  54. Tests Static Analysis Code Review

  55. None
  56. class Invoice #... def total @total = 0 @items.each do

    |item| @total += item.price * item.quantity end
  57. if @country_code == 'BE' vat = 0.21 * @total elsif

    ['IT','FR','NL','LU','DE'].include?(@country_code) if valid_vat_number? vat = 0 else vat = 0.21 * @total end else vat = 0 end
  58. @total += vat @total end #... end

  59. $ flog lib/invoice.rb 27.4: flog total 5.5: flog/method average 17.2:

    Invoice#total lib/invoice.rb:13
  60. None
  61. class Quote #... def total if @country_code == 'BE' vat

    = 0.21 * @total elsif ['IT','FR','NL','LU','DE'].include?(@country_code) if valid_vat_number? vat = 0 else vat = 0.21 * @total end
  62. else vat = 0 end @total + vat end #...

    end
  63. $ flay lib Total score (lower is better) = 116

    1) IDENTICAL code found in :if (mass*2 = 116) lib/invoice.rb:21 lib/quote.rb:10
  64. class Invoice #... def total @total = 0 @items.each do

    |item| @total += item.price * item.quantity end
  65. class Invoice #... def total @total = calculate_subtotal

  66. def calculate_subtotal total = 0 @items.reduce(0) do |total, item| total

    += item.price * item.quantity end end
  67. None
  68. $ ruby -w lib/invoice.rb lib/invoice.rb:41: warning: shadowing outer local variable

    - total lib/invoice.rb:40: warning: assigned but unused variable - total
  69. $ rubocop lib/invoice.rb Inspecting 1 file W Offenses: ------8<------ lib/invoice.rb:40:5:

    W: Useless assignment to variable - total. total = 0 ^^^^^
  70. lib/invoice.rb:41:26: W: Shadowing outer local variable - total. @items.reduce(0) do

    |total, item| ^^^^^ lib/invoice.rb:42:7: W: Useless assignment to variable - total. Use just operator +. total += item.price * item.quantity ^^^^^ 1 file inspected, 10 offenses detected
  71. def calculate_subtotal @items.reduce(0) do |total, item| total + item.price *

    item.quantity end end
  72. Check Flaws & Smells

  73. BUT You could Forget to run

  74. CI + devtool/rake

  75. BUT False Positives

  76. It is common to take a sort of smug satisfaction

    in reports of colossal failures of automatic systems, but for every failure of automation, the failures of humans are legion. Exhortations to “write better code” plans for more code reviews, pair programming, and so on just don’t cut it, especially in an environment with dozens of programmers under a lot of time pressure. The value in catching even the small subset of errors that are tractable to static analysis every single time is huge. John Carmack
  77. Tests Static Analysis Code Review

  78. None
  79. module Vat def self.rate(country_code = '', vat_number = '') if

    country_code == 'BE' return 0.21 elsif %w(IT FR NL LU DE).include?(country_code) if is_valid?(vat_number) return 0 else return 0.21 end
  80. else return 0 end end #... end

  81. class Invoice # ... def total @total = calculate_subtotal vat

    = Vat::rate(@country_code, @vat_number) * @total @total += vat end # ... end
  82. class Quote # ... def total vat = Vat::rate(@country_code, @vat_number)

    * @total @total + vat end # ... end
  83. $ flay lib/ Total score (lower is better) = 0

  84. class Country # ... def belgium? @code == 'BE' end

    def europe? %w(IT FR NL LU DE).include? @code end end
  85. module Vat def self.rate(country, vat_number = '') if country.belgium? return

    0.21 elsif country.europe? if is_valid?(vat_number) return 0 else return 0.21 end # ... end
  86. 34.7: flog total 2.9: flog/method average 7.0: Invoice#calculate_subtotal lib/invoice.rb:28 6.7:

    Invoice#initialize lib/invoice.rb:5 5.7: Invoice#total lib/invoice.rb:16 4.8: Vat::rate lib/vat.rb:2
  87. 4.8: Vat::rate lib/vat.rb:2 3.3: branch 1.2: is_valid? 1.1: europe? 1.0:

    belgium? 1.0: assignment
  88. Spreading Knowledge

  89. Check Anything

  90. BUT Cannot Check Everything Everytime

  91. Test + Static Analysis

  92. What now?

  93. Cost & Risk

  94. Don't wait

  95. None
  96. We're only Human

  97. None
  98. None
  99. None
  100. None
  101. ? https://github.com/ 8thcolor/rdrc2014-safetynets _toch toch