RDRC 2014: Safety Nets: Learn to code with confidence

RDRC 2014: Safety Nets: Learn to code with confidence

Ruby gives you a great power, such as easy Duck Typing. As the saying goes, "With great power there must also comes great responsibility!" It comes at a price. 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 the cruft long term. Like a safety net, they allow you to go forward with more confidence.

presented at RedDotRubyConference 2014 http://www.reddotrubyconf.com/

Code available at https://github.com/8thcolor/rdrc2014-safetynets
Video available at http://www.confreaks.com/videos/4106-rdrc2014-safety-nets-learn-to-code-with-confidence

3f8fcddf7ab5d1bd90b0a0a9adfd6527?s=128

Christophe Philemotte

June 27, 2014
Tweet

Transcript

  1. Safety Nets: Learn to code with confidence

  2. _toch toch Hi, I'm Christophe

  3. Belgium

  4. Belgium

  5. Co-founder of

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

  8. My wife's job:

  9. My wife's job: curing cancer

  10. None
  11. With Ionizing Radiation

  12. Side Effects BUT

  13. BUT =

  14. BUT Complex

  15. BUT Long

  16. BUT

  17. None
  18. Ineffective

  19. Casualties Ineffective

  20. Casualties Ineffective Costly Time consuming

  21. Casualties Ineffective Broken Equipment Costly Time consuming

  22. Casualties Ineffective Broken Equipment Non Availability Costly Time consuming

  23. As an Engineer What CAN she do?

  24. Catch Errors & Mitigate Risks

  25. How?

  26. Is it Working?

  27. Test Before & During

  28. Is it Correct?

  29. Peer-Review & Cross-Check

  30. Have the Requirements been met?

  31. Peer-Review & Process

  32. Sounds Familiar?

  33. We're Human

  34. We make mistakes

  35. None
  36. We can be helped

  37. Radiotherapy, Civil Engineering, Manufacturing, … Software Development

  38. Safety Nets

  39. None
  40. None
  41. None
  42. Be Informed Then Decide

  43. Tests Static Analysis Code Review

  44. Tests Static Analysis Code Review

  45. None
  46. 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
  47. 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
  48. 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
  49. class Invoice def initialize @items = [] end def add_item(item)

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

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

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

    item.quantity end @total end end
  54. None
  55. #... def total @total = 0 @items.each do |item| @total

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

  57. BUT What are we Testing?

  58. SimpleCov

  59. BUT Has someone Launched the Tests

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

  61. CI & Close to Prod

  62. BUT "Who tests the tests?"

  63. Mutant

  64. Tests Static Analysis Code Review

  65. None
  66. class Invoice #... def total @total = 0 @items.each do

    |item| @total += item.price * item.quantity end
  67. 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
  68. @total += vat @total end #... end

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

    Invoice#total lib/invoice.rb:13
  70. None
  71. 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
  72. else vat = 0 end @total + vat end #...

    end
  73. $ 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
  74. class Invoice #... def total @total = 0 @items.each do

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

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

    += item.price * item.quantity end end
  77. None
  78. $ 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
  79. $ rubocop lib/invoice.rb Inspecting 1 file W Offenses: ------8<------ lib/invoice.rb:40:5:

    W: Useless assignment to variable - total. total = 0 ^^^^^
  80. 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
  81. def calculate_subtotal @items.reduce(0) do |total, item| total + item.price *

    item.quantity end end
  82. Check Flaws & Smells

  83. BUT You could Forget to run

  84. CI + devtool/rake

  85. BUT False Positives

  86. 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
  87. Tests Static Analysis Code Review

  88. None
  89. 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
  90. else return 0 end end #... end

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

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

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

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

    def europe? %w(IT FR NL LU DE).include? @code end end
  95. 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
  96. 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
  97. 4.8: Vat::rate lib/vat.rb:2 3.3: branch 1.2: is_valid? 1.1: europe? 1.0:

    belgium? 1.0: assignment
  98. Spreading Knowledge

  99. Check Anything

  100. BUT Cannot Check Everything Everytime

  101. Test + Static Analysis

  102. What now?

  103. Cost & Risk

  104. Don't wait

  105. None
  106. We're only Human

  107. None
  108. None
  109. None
  110. None
  111. ? https://github.com/ 8thcolor/rdrc2014-safetynets _toch toch