Skip to content
/ assume Public

Be honest about your (code's) assumptions

License

Notifications You must be signed in to change notification settings

odinhb/assume

Repository files navigation

Assume

Assume is a tool that helps you record your assumptions about code directly in the code you're writing. It is similiar in functionality to and directly inspired by solid_assert.

As opposed to solid_assert, this gem has you write your assumptions in ruby code blocks. The idea behind this is that the assumption does not need to be evaluated in your production/release environment, reserving expensive checks for development/test environments which only affect developers.

What?

We make lots of assumptions when we write code. Programming with assertions is a technique that helps you catch problems with your assumptions earlier in the development process.

Assertions live in a subtle space between testing your code explicitly and raising exceptions to enforce your api behaviour or handle edge cases. It is not a replacement for either.

This gem is an attempt to make the concept less computer-sciency and more english, encouraging you to think about and record your assumptions as you go and leave them in the codebase for your colleagues to find, so they may understand your thinking, and thus your code, better.

Why tho?

You might have found yourself writing something like this:

# i is the value of some enum 0-3
if i == 0
  # do something cool
elsif i == 1
  # do something cooler
elsif i == 2
  # do something so cool the sun freezes
else # we know i == 3
  # do something awesome
end

We know we don't need to perform another check on i, as we've exhausted all other options, but what happens when 'Dave' comes along next month and extends the enum to allow a 4?

By recording our assumption in code, 'Dave' will (hopefully) test his new enum value and quickly discover that he broke our previous assumption.

If he happens to read our code, it will say right there in the code that he's broken it, and he'll know to fix it.

# i is now the value of some enum 0-4
if i == 0
  # do something cool
elsif i == 1
  # do something cooler
elsif i == 2
  # do something so cool the sun freezes
else
  assume { i == 3 } # BadAssumption!
end

If you had written this only as a comment, 'Dave' might've merged a bug because he had no idea this code even existed. And if you didn't even write a comment, 'Dave' might not have noticed, even if he read your code. (Although in this example he probably would)

More examples

You're writing a method and assuming it gets called after a certain point. You're actually assuming that there is a certain state available to you.

class ThingDoer
  # ...

  private def do_the_thing
    assume { @thingies.size > 0 }
  end

  # ...
end

You're doing some control flow you know will never happen

  def something(arg)
    case arg
    when 1
      :option1
    when 2
      :option2
    when 3
      :option3
    else
      # instead of raise "this should never happen"
      # maybe real users don't need to notice, since this is not actually dangerous
      assume { false }

      :option1 # .. as long as we return the default option
    end
  end

Installation

Open assume/lib/assume.rb and copy/paste it into your project.

The code is trivial, and you may want to customize it to behave differently or have a different interface.

Since making this gem I have done this myself, and recommend it.

Using rubygems

Add this line to your application's Gemfile:

gem "assume"

And then execute:

$ bundle

Or install it yourself as:

$ gem install assume

Setup

This:

require "assume"

assume { "this" } # NoMethodError!
assumption { "gravity still works" } # NoMethodError!

Does not allow you to use the methods alone. You have three options:

require "assume/core_ext" # monkey-patches Object to provide the methods everywhere
require "assume/refine" # Loads Assumptions, a module containing a refinement

class Whatever
  using Assumptions # assume's methods are now available in this class

  # ...

  def do_the_thing
    assume { @true == false }

    @true = true
    # ...
  end
end

assume { "yes" == "no" } # NoMethodError!

or you can just do it yourself:

include Assume

# or

class Whatever
  include Assume
end

# etc...

Usage

require "assume"
require "assume/core_ext"

# assume does nothing by default
assume { true == false } # => nil

# it is common only to run assertions in development/test environments
# prod users get to be blissfully unaware, and the application does less work in prod
# Assume.enabled = Whatever.env != "prod"
Assume.enabled = true

assume { "yes" == "no" } # Assume::BadAssumption!

Assume.handler = proc { |result, block|
  $stderr.puts "oh noes"
}

assume { true } # => nil
assumption { false } # => nil, (on stderr) => "oh noes\n"
assert { true } # NoMethodError!

alias assert assume # (if you're old-school like that)

assert { true } # => nil 

Don'ts

Don't use assume and rescue BadAssumption as an error handling mechanism. Make your own error class and raise/rescue it.
Don't use assume to enforce the public api of a class/method. raise ArgumentError instead.

You should probably not use assume directly in a test case (you've already got expect/assert/whatever) but if the code under test uses assume and assume is enabled in your test suite, bonus!

Advanced

This gem has been developed on ruby 2.6.5 and 2.7.2, but it should work on basically all versions. If it doesn't work for whatever reason, it is probably due to the default broken assumption handler. Consider replacing it with something simpler:

Assume.handler = proc { |result, block|
  raise ::Assume::BadAssumption
}

Or using Proc#to_source from sourcify

Assume.handler = proc { |result, block|
  raise ::Assume::BadAssumption, "failed assumption:\n\n#{block.to_source}\n"
}

etc...

The default handler will only print the first line of source code which the block's #source_location method points it at. E.g. it will not print any source code in irb, for example.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

Use rake test to run the tests. rake and rake spec also works.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/odinhb/assume.

About

Be honest about your (code's) assumptions

Resources

License

Stars

Watchers

Forks