You know what you want to do, but your code just isn’t cooperating. Maybe it has a few too many levels of indentation, or chains a half dozen methods, or looks asymmetrical. Whatever it is, something just feels off. You could ignore it – I mean, you have a backlog full of features you still want to write, and it’s really not that bad. But that would be a mistake: Your code is trying to tell you something, and you don’t want to miss it.

If you can learn to tell when your code feels strange, you will quickly and drastically improve your software design skills. This intuition is hard to build, since it comes from experience, mentorship, and code reviews. But you can get some help from libraries that use syntactic vinegar to make bad code feel bad.

What does syntactic vinegar look like?

Here’s an example of syntactic vinegar using minitest/mock, a small mocking and stubbing library that ships with Ruby:

1
2
3
4
5
6
7
8
9
10
11
require 'minitest/mock'

class CartTest < MiniTest::Test
  def test_error_message_set_on_charge_failure
    cart = Cart.new(items)
    cart.stub(:charge!, false) do
      cart.checkout!
      assert_equal "The credit card could not be charged", cart.credit_card_error
    end
  end
end

When you run the test, the charge! method on Cart is stubbed, so the test won’t hit the payment processor. The block syntax is nice for making sure you only stub exactly when you want to. But what happens when you want to stub a bunch of methods?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
require 'minitest/mock'

class CartTest < MiniTest::Test
  def test_error_message_set_on_charge_failure
    payment_processor = PaymentProcessor.new
    cart = Cart.new(items, processor: payment_processor)

    payment_processor.stub(:charge!, false) do
      payment_processor.stub(:login!, true) do
        payment_processor.stub(:logout!, true) do
          cart.checkout!
          assert_equal "The credit card could not be charged", cart.credit_card_error
        end
      end
    end
  end
end

Ew. That’s a lot of indentation. And that’s just in a single test – you can imagine this code being repeated in a lot of other tests.

You could wrap all this nesting into a test helper method. But if you’re really listening to your code, it’s telling you that you should find a better way. It might be time to look into using a Test Double instead:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class TestPaymentProcessor < PaymentProcessor
  def login!(account_id, key)
    true
  end

  def charge!(amount, credit_card)
    credit_card.can_be_charged?
  end

  def logout!
    true
  end
end

class CartTest < MiniTest::Test
  def test_error_message_set_on_charge_failure
    test_payment_processor = TestPaymentProcessor.new
    cart = Cart.new(items, processor: test_payment_processor)

    cart.credit_card = failing_credit_card
    cart.checkout!
    assert_equal "The credit card could not be charged", cart.credit_card_error
  end
end

Now your test is more readable. Plus, you have a TestPaymentProcessor that can be used in a lot of other places. You could even use it in development mode, if you don’t want to hit a real server!

Bad code should feel bad

By using opinionated libraries that make bad code obvious, you’ll start to notice bad code much faster and more reliably. And that’ll make your future code cleaner, easier to read, and less painful to work with.

What are your favorite opinionated libraries, and how do they help you find and fix bad code? Let me know in the comments below!

Did you like this post? You should read these:

Finished another Rails tutorial and still don’t know how to start?

Have you slogged through the same guide three times and still can't retain enough to write apps on your own?

In my free 7-part course, you’ll discover the fastest way to learn and remember new Rails ideas, so you can use them when you need them. And you'll learn to use what you already know to build your own Rails project.



Comments