/todo_or_die

Write TODOs in code that ensure you actually do them

Primary LanguageRubyMIT LicenseMIT

TODO or Die!

TODO or Die NES cart

CircleCI

Usage

Stick this in your Gemfile and bundle it:

gem "todo_or_die"

Once required, you can mark TODOs for yourself anywhere you like:

TodoOrDie("Update after APIv2 goes live", by: Date.civil(2019, 2, 4))

To understand why you would ever call a method to write a comment, read on.

The awful way you used to procrastinate

In the Bad Old Days™, if you had a bit of code you knew you needed to change later, you might leave yourself a code comment to remind yourself to change it. For example, here's the real world code comment that inspired this gem:

class UsersController < ApiController
  # TODO: remember to delete after JS app has propagated
  def show
    redirect_to root_path
  end
end

This was bad. The comment did nothing to remind myself or anyone else to actually delete the code. Because no one was working on this part of the system for a while, the continued existence of the redirect eventually resulted in an actual support incident (long story).

The cool new way you put off coding now

So I did what any programmer would do in the face of an intractable social problem: I wrote code in the vain hope of solving things without needing to talk to anyone. And now this gem exists.

To use it, try replacing one of your TODO comments with something like this:

class UsersController < ApiController
  TodoOrDie("delete after JS app has propagated", by: "2019-02-04")
  def show
    redirect_to root_path
  end
end

Nothing will happen at all until February 4th, at which point the gem will raise an error whenever this class is loaded until someone deals with it.

You may also pass a condition, either as a callable (e.g. proc/lambda/method) or a boolean test:

class User < ApplicationRecord
  TodoOrDie("delete after someone wins", if: User.count > 1000000)
  def is_one_millionth_user?
    id == 1000000
  end
end

You can also pass both by and if (where both must be met for an error to be raised) or, I guess, neither (where an error will be raised as soon as TodoOrDie is invoked).

What kind of error?

It depends on whether you're using Rails or not.

When you're writing Real Ruby

If you're not using Rails (i.e. defined?(Rails) is false), then the gem will raise a TodoOrDie::OverdueError whenever a TODO is overdue. The message looks like this:

TODO: "Visit Wisconsin" came due on 2016-11-09. Do it!

When Rails is a thing

If TodoOrDie sees that Rails is defined, it'll assume you probably don't want this tool to run outside development and test, so it'll log the error message to Rails.logger.warn in production (while still raising the error in development and test).

Wait, won't sprinkling time bombs throughout my app ruin my weekend?

Sure will! It's "TODO or Die", not "TODO and Remember to Pace Yourself".

Still, someone will probably get mad if you break production because you forgot to follow through on removing an A/B test, so I'd strongly recommend you read what the default hook actually does before this gem leads to you losing your job. (Speaking of, please note the lack of any warranty in todo_or_die's license.)

To appease your boss, you may customize the gem's behavior by passing in your own call'able lambda/proc/dingus like this:

TodoOrDie.config(
  die: ->(message, due_at) {
    if message.include?("Karen")
      raise "Hey Karen your code's broke"
    end
  }
)

Now, any TodoOrDie() invocations in your codebase (other than Karen's) will be ignored. (You can restore the default hook with TodoOrDie.reset).

When is this useful?

This gem may come in handy whenever you know the code will need to change, but it can't be changed just yet, and you lack some other reliable means of ensuring yourself (or your team) will actually follow through on making the change later.

This is a good time to recall LeBlanc's Law, which states that Later == Never. Countless proofs of this theorem have been reproduced by software teams around the world. Some common examples:

  • A feature flag was added to the app a long time ago, but the old code path is still present, even after the flag had been enabled for everyone. Except now there's also a useless TODO: delete comment to keep it company
  • A failing test was blocking the build and someone felt an urgent pressure to deploy the app anyway. So, rather than fix the test, Bill commented it out "for now"
  • You're a real funny guy and you think it'd be hilarious to make a bunch of Aaron's tests start failing on Christmas morning

Pro-tip

Cute Rails date helpers are awesome, but don't think you're going to be able to do this and actually accomplish anything:

TodoOrDie("Update after APIv2 goes live", 2.weeks.from_now)

It will never be two weeks from now.