Do we need constants?

This article by Joey Butler about constants in Ruby got me thinking. How much do we really need constants, anyway?

As Joey points out, constants are an opportunity for implementation details to leak out into other classes. But they complicate things in other ways too.

For instance, I often find myself having to convert a constant to a method when I realize that it needs to be a dynamically calculated value. Why didn’t I just make it a method returning a constant value in the first place?

Constants lead to constant redefinition warnings in dynamically-reloaded code unless you are careful.

Methods are easy to stub out in tests. Constants are a pain to stub out. This becomes an issue if I want to test that a method bases its calculation on the value of a certain constant.

Methods are easy to override on a per-object basis with singleton method definitions or module insertion. Constants are not.

Finally, they are just an extra thing to remember. Was that default value a public constant, or a public class method? I can’t remember, let me go check the docs…

What if we just used methods for everything?

Here’s a module which declares some defaults as methods. Then it uses the little-known #module_function on them. #module_function does two things:

  • Makes the methods private
  • Makes module-level copies of the methods
[gist id=1154789 file=sandwich_defaults.rb]

We can test that #module_function made module-level copies:

[gist id=1154789 file=module_methods.rb]

When we include the module in a class, the class can still use the defaults as normal instance methods:

[gist id=1154789 file=sandwich_shop.rb]

We can selectively override defaults in subclasses:

[gist id=1154789 file=sub_shop.rb]

And we can even selectively override defaults on a per-object basis:

[gist id=1154789 file=special.rb]

Oh, and remember I said that #module_function also makes the methods private? Here’s the proof:

[gist id=1154789 file=private.rb]

All in all, using methods for everything is a lot more flexible, and it’s hard to see the down side. Offhand I can only think of two negatives:

  • It’s a little more verbose than a constant definition.
  • There might be a performance penalty. I haven’t profiled constants vs. methods to find out.

What are your thoughts? Do we need constants?

 

 

30 comments

  1. Something that I always liked about Python and its community was the thinking on this subject: “if you want a constant, make it uppercase and don’t change it”. It’s one of those “we’re all adults here” sentiments, which I generally go for.

    If you were alright assigning the “constants” in the constructor, you could always just do it with an attr_reader, which reads a little cleaner than all those defs.

    1. I suppose it’s a bit snarky, but that contrasts rather sharply with the “all programmers are children and need a strong hand” attitude that I found when I first got into Python 😛

  2. I like the subtle hint I get when I see Uppercase for the constant. Like, “pay attention. It has a meaning, there was a reason it was done that way.”

    I suppose you can def MinLength but then you have to use the parentheses when calling the method, so it looks rather funny to me.

    1. Here’s the thought I keep coming back to… I can’t think of a single time when it mattered how the value was defined. All the method needs to know is that there is a named value there.

      In fact, it’s almost a violation of encapsulation for the method to know how the value was defined. The important thing is that there is a value with a name.  Should the code make any assumptions beyond that?

      What situations can you think of where it was important to know that a value was in a constant, not a method?

      1. I can’t think of a case similar to the one you’ve outlined where I’ll use a constant. Something I’m more apt to do would be like:

          order.payment_type = PaymentTypes::CreditCard 

        Instead of just saying:

        order.payment_type = 0  #with a comment here to remind me that 0 is the magic number for CreditCard.
        In that case, I probably really don’t want it to change. If it does, all the persisted data will be wrong.Of course it doesn’t matter how it’s defined, but I think a quality of good code includes semantics and meaning we impart by how we write it, and I think the capitalization is part of that.I couldn’t care less whether or not it’s actually a constant. It doesn’t bother me if anyone wants to change it. I just like implied extra meaning behind it.

      2. I guess to sum it up: no, I don’t think we need constants to remain constant. But I think we need a way to express our intent behind those kinds of variables, and I like the capital case as a way to do that (mostly because that’s what I’m accustomed to, since it creates a constant — nothing inherent to the capital letter).

  3. This looks like a great alternative. Constants, since they aren’t methods, can’t benefit of ruby’s message passing structure.

    However, its strength is its curse: you can override these constant methods. As you’ve shown here, that can be really useful when the constant is relative to the class (sandwich size). You can override constants in subclasses, though, but you can’t reopen the class and change the constant as you could with a method.

    So, programmer beware. But I think messages are a nicer and more flexible implementation. Nice points!

  4. The performance penalty is negligible on 1.9.2, 1.8.7, and rubinius.  Yes it is slower to use methods but not by much.  JRuby on the other hand it takes almost 6 times as much time to use the methods over the constants :(.

    1. Or, rephrased, it’s currently 6x faster to use constants over method calls in JRuby.  Charles Nutter recently had a post about using invokedynamic to speed up constant lookup/inlining.  I don’t know if method lookup has had the same level of optimization yet (as constant lookup).

  5. I’m a pro constants. 

    One of the main good things about constants => They look awesome on the documentation !Having constants create much cleaner documents. Just take a look at a YARD doc with constants (http://rdoc.info/github/travis-ci/travis-ruby-client/master/Travis/API/Client).  
    * They are not mixed with the methods list. 
    * Their values are displayed in there so anyone can see what they hold without inspect the code. 
    * If you don’t care about them just ignore the constants section 
    * If you are looking for them just go to the constants section.

    Same applies when reading the source code. Is easier to identify them so you can either ignore them or find them easier.

    Recap: 
     * we need them
     * don’t abuse of them
     * use them within your own namespace!!! (be a polite programmer!)

  6. I’ve been going down a similar route where I’ve been using class methods instead of constants – testing (in all forms, stubbing especially) is easier. module_function is something I’d never heard of and is pretty awesome.

    The only thing I struggle with is along the lines of what Nick brought up; not using constants is, in my opinion, not idiomatic. Referring to something that you’re effectively treating as a constant but isn’t actually is somewhat confusing. The closest thing I think I’d consider equivalent would be to prefix a method with an underscore to denote that it’s private without actually changing its access modifier.

    To answer your question, my gut feeling is that Ruby should support them; as to whether I will continue to use them, I’m going with a “most likely not”.

  7. Great article.

    One small point: Instead of  using module_function, you can alternatively extend self in SandwichDefaults. It’s slightly less verbose if you have more than a couple of methods.

    1. Excellent point!

      I’ve just been trying to find an excuse to use #module_function for ages 🙂 Also, FWIW, it does let you draw a line between the stuff you want available at the module level and the stuff you don’t, whereas “extend self” makes all of it available.

        1. Although I think I misunderstood your point: by “make available at the module level” you didn’t mean “private vs public” (which is easily doable with the “extend self”).  You meant M.foo == “bar” vs M.foo => “undefined method” (which requires “module_function).  Oops.  🙂

          1. Yeah, I meant that there may be some methods in module which don’t make sense at the module level, they only make sense included into object instance method collections.

  8. “For instance, I often find myself having to convert a constant to a method when I realize that it needs to be a dynamically calculated value. Why didn’t I just make it a method returning a constant value in the first place?”

    Well, then it’s not a constant.

    You didn’t because you didn’t know, and that’s ok.

    (Say what you will about the c preprocessor, using #define (appropriately scoped, of course) assures one that, constants are indeed, constant.)

    There is an interesting continuum between what should be considered constant, or not, and where it matters. On the one hand, PI anchors a constant that really ought to “leak.” Hard to imagine why one would change the value of PI, anywhere (except perhaps Indiana). On the other hand, perhaps many values for which people initially model as constant turn out not to be constant. Failure of our models, not a failure of Ruby.

    I really like the technique you’re showing here in any case.

  9. I assume you’re only talking about constant references to primitives, as classes are usually referred to by constants as well.

    In the case of primitives, are constants mainly a lazy way to do configuration?

  10. One minor note: constant lookup in ruby behaves differently from method lookup: method lookup follows the ancestor chain while constant lookup also pops the namespace stack and (more significantly) cares about where the code is defined and not where it’s used.  So you can’t necessarily treat it as a drop-in replacement.  Here’s a sloppy demonstration: https://gist.github.com/1157133

  11. this leads directly to the next question: should we store classes and modules in constants, or wouldn’t it be better to assign those to variables/have methods that return classes? classes in constants cause the same problems for mocking in that you can’t easily replace an existing class with something else. keeping them in variables/methods would truly allow us to change everything whenever we want to.

  12. A little while ago, I had a slightly different constants problem.

    Rather than wanting to tweak my constant for a different sub-class as in your example, we wanted to be able to tweak a constant within a path of execution, kind of like using let in Lisp.  The result of it was the DynamicVars library that lets us do stuff like

    DynamicVars.fancy_rate = 0.5 # 0.5 
    class Example; def fancy_calculation(base); base * DynamicVars.fancy_rate; end; end
    Example.new.fancy_calculation(5) # 2.5 
    DynamicVars.let(:fancy_rate=>3){        Example.new.fancy_calculation(5)    } # 15 
    Example.new.fancy_calculation(5) # 2.5It was very useful if a couple of very specific scenarios where we wanted to be able to tweak "constants" during some test scenarios.
    

    For more info, you can see https://github.com/robdimarco/dynamic_vars

    1. It seems like every library I write, I wind up re-implementing dynamically scoped variables… I should really extract out my own version one of these days 🙂

  13. Great points, plus: in languages that demand parentheses, making them methods in the first place means you don’t have to tweak the clients of your class when you convert them from constants to methods later.  🙂  Now THAT’S implementation-hiding!

    PS: I first parsed it as “Do we need consultants?”  Another question that generates flame-wars….

  14. Did constant occupy memory whole time or just when they get called,… I think they occupy whole time as they have to be initialize.. more constant means more memory space reserved by them ? What do you think?

Leave a Reply to Avdi Grimm Cancel reply

Your email address will not be published. Required fields are marked *