Style Guide

Version 19 (Brian Ford, 03/30/2009 09:30 AM)

1 1
h1. Style Guide
2 1
3 2 Brian Ford
Generally, "RSpec":http://rspec.rubyforge.org specs describe the expected behavior of code. While RSpec is fairly young, there are some conventions for writing specs. The RubySpecs cover a wide variety of components, so we have developed some pragmatic conventions to handle the various situations. As noted below, some conventions are more rigid than others.
4 1
5 1
These conventions apply to all specs. Existing specs that deviate from these conventions need to be fixed. Consistency is the principle that will almost always trump other conventions. Consistency aids understanding and readability. There are many thousands of lines of code in the spec files, so the value of consistency cannot be overstated.
6 1
7 1
The specs uniformly use @describe@ not @context@. The use of @it@ is preferred over @specify@ except in situations when the first word of the string is not a verb. The word "should" is unnecessary noise in the spec description strings and is not used. (The rationale is this: the spec string describes the expected behavior unconditionally. The code examples, on the other hand, set up an expectation that is tested with the call to the _should_ method. The code examples can violate the expectation, but the spec string does not. The value of the spec string is as clearly as possible describing the behavior. Including "should" in that description adds no value.)
8 1
9 1
Whenever possible, the spec strings should be written to conform to very basic English sentence structure: _subject + predicate_. The spec strings also uniformly use double-quotes, not single-quotes. The minimum number of words should be used to describe the behavior. Only make distinctions when they add significant value to understanding the behavior. This is explained further below. The general rule across all the specs is to use the least amount of detail to unambiguously describe behavior. Add to the detail conservatively. This is conceptually consistent with doing the simplest thing that could work.
10 1
11 5 Brian Ford
Ruby is a beautifully expressive language with _optional_ parentheses. There is a distinct preference for omitting parentheses in the specs whenever they are not needed. In other words, parentheses should _not_ be used unless necessary to make an expression syntactically or semantically correct.
12 19 Brian Ford
h2. Basic Principles
13 1
14 19 Brian Ford
h3. Error messages
15 19 Brian Ford
16 19 Brian Ford
Do not check for specific error messages, just the exception type. Implementations should be free to enhance the error message, offer implementation-specific details, or even translate the error messages. The interface is the exception class raised, not the message that is provided for human consumption.
17 19 Brian Ford
18 19 Brian Ford
# The following would be preferred:
19 19 Brian Ford
lambda { 1/0 }.should raise_error(ZeroDivisionError)
20 19 Brian Ford
21 19 Brian Ford
# The following would not be preferred
22 19 Brian Ford
lambda { 1/0 }.should raise_error(ZeroDivisionError, "divided by 0")
23 19 Brian Ford
24 19 Brian Ford
h3. One "should" per example
25 19 Brian Ford
26 19 Brian Ford
This is a guiding principle, not a hard and fast rule. 
27 19 Brian Ford
28 19 Brian Ford
For expressing different aspects of a scenario, you can usually factor out the scenario into a helper method, and then have different examples using the helper method and asserting the specific different aspects. For example, setting up a blocked thread takes a lot of fixture code, and it would be tempting to check different aspects of a blocked thread in a single example. Instead, this principle can be honored by keeping the fixture code in method ThreadSpecs.status_of_blocked_thread in core/thread/fixtures/classes.rb, and with code like this in core/thread/status_spec.rb:
29 19 Brian Ford
30 19 Brian Ford
31 19 Brian Ford
describe "Thread#status" do
32 19 Brian Ford
  it "describes a blocked thread" do
33 19 Brian Ford
    ThreadSpecs.status_of_blocked_thread.status.should == 'sleep'
34 19 Brian Ford
  end
35 19 Brian Ford
end
36 19 Brian Ford
37 19 Brian Ford
38 19 Brian Ford
and the following in core/thread/inspect_spec.rb:
39 19 Brian Ford
40 19 Brian Ford
describe "Thread#inspect" do
41 19 Brian Ford
  it "describes a blocked thread" do
42 19 Brian Ford
    ThreadSpecs.status_of_blocked_thread.inspect.should include('sleep')
43 19 Brian Ford
  end
44 19 Brian Ford
end
45 19 Brian Ford
46 19 Brian Ford
47 19 Brian Ford
Cases where it is OK to break this rule is when the functionality is expressable as a table for different values of the argument. For example, the following in language/regexp_spec.rb. Each "should" expresses the same theme, just different specific data-points. Breaking this up into individual examples would obscure the larger picture, ie. the "table".
48 19 Brian Ford
49 19 Brian Ford
50 19 Brian Ford
  it 'supports escape characters' do
51 19 Brian Ford
    /\t/.match("\t").to_a.should == ["\t"] # horizontal tab
52 19 Brian Ford
    /\v/.match("\v").to_a.should == ["\v"] # vertical tab
53 19 Brian Ford
    /\n/.match("\n").to_a.should == ["\n"] # newline
54 19 Brian Ford
    /\r/.match("\r").to_a.should == ["\r"] # return
55 19 Brian Ford
    /\f/.match("\f").to_a.should == ["\f"] # form feed
56 19 Brian Ford
    /\a/.match("\a").to_a.should == ["\a"] # bell
57 19 Brian Ford
    /\e/.match("\e").to_a.should == ["\e"] # escape
58 19 Brian Ford
  end  
59 19 Brian Ford
60 19 Brian Ford
61 1
h2. 1. Core and Standard Library
62 1
63 1
The specs for the Ruby core and standard libraries use one @describe@ block per method. For particularly complex methods, such as Array#[], more than one @describe@ block may be used according to the nature of arguments the method takes.
64 1
65 1
The @describe@ string should be "Constant.method" for class methods and "Constant#method" for instance methods. "Constant" is either a class or module name. For subclasses or submodules, the "Constant" name should be "Super::Sub". The @describe@ string should not include arguments to the methods unless absolutely necessary to describe the behavior of the method. Keep in mind that in Ruby duck-typing is a deeply embedded concept. Many methods will take any object that responds to a particular method or acts like an instance of a particular class.
66 1
67 6 Brian Ford
Nested @describe@ blocks should not be used. Various automated process scripts depend on the @describe@ string having the format explained above. Also, nested @describe@ blocks complicate the structure of the specs. If a particular situation appears to greatly benefit from nested blocks, open a discussion about it on the "mailing list":http://groups.google.com/group/rubyspec.
68 6 Brian Ford
69 1
70 1
  # This is correct
71 1
  describe "String#eql?" do
72 1
    it "returns true if other has the same length and content" do
73 1
      ...
74 1
    end
75 1
  end
76 1
77 1
  describe "Array#[]= with [index, count]" do
78 1
    it "returns non-array value if non-array value assigned" do
79 1
      ...
80 1
    end
81 1
  end
82 3 Brian Ford
83 1
84 4 Brian Ford
Contrast the _good_ example above with the one below. The following example deviates from the conventions for @describe@ strings and uses "should" and single-quotes for the descriptions.
85 4 Brian Ford
86 3 Brian Ford
87 1
  # This is NOT correct
88 1
  describe "String#eql?(string)" do
89 4 Brian Ford
    it 'should return true if other has the same length and content' do
90 1
      ...
91 1
    end
92 1
  end
93 1
94 4 Brian Ford
  describe 'Array#[]=(index, count)' do
95 4 Brian Ford
    it 'returns non-array value if non-array value assigned' do
96 1
      ...
97 1
    end
98 1
  end
99 1
100 1
101 1
The vast majority of the spec files for the core library have already been created. To create template files for the standard library classes, refer to the "mkspec":/wiki/mspec/Mkspec documentation.
102 1
103 1
h3. 1.1 Utility Classes
104 1
105 1
Many spec code examples refer to a particular class. To prevent name clashes with these different class definitions across all the specs, the classes should be scoped to a module. The convention is as follows:
106 1
107 1
108 1
module ObjectSpecs
109 1
  class SomeClass
110 1
  end
111 1
end
112 1
113 1
114 1
The module is named after the class for which the specs are being written. So, for the specs for _Object_, the module name is ObjectSpecs.
115 1
116 1
These utility classes are also referred to as _fixtures_. In the directory for each class, there is also a @fixtures@ directory. Refer to the existing files for examples.
117 1
118 1
h3. 1.2 Aliased or Identical Methods
119 1
120 1
Ruby has a significant number of aliased methods. True aliases are identical methods, so the specs should be exactly the same for each aliased method. The following illustrates the convention for specs for aliased methods (or just otherwise identical interfaces.)
121 1
122 1
In @rubyspec/1.8/core/array/shared/collect.rb@
123 1
124 1
125 14 Brian Ford
describe :array_collect, :shared => true do
126 14 Brian Ford
  it "returns a copy of array with each element replaced by the value returned by block" do
127 14 Brian Ford
    a = ['a', 'b', 'c', 'd']
128 14 Brian Ford
    b = a.send(@method) { |i| i + '!' }
129 14 Brian Ford
    b.should == ["a!", "b!", "c!", "d!"]
130 14 Brian Ford
    b.object_id.should_not == a.object_id
131 14 Brian Ford
  end
132 1
133 14 Brian Ford
  it "does not return subclass instance" do
134 14 Brian Ford
    ArraySpecs::MyArray[1, 2, 3].send(@method) { |x| x + 1 }.class.should == Array
135 1
  end
136 1
end
137 1
138 1
139 1
In @rubyspec/1.8/core/array/collect_spec.rb@
140 1
141 1
require File.dirname(__FILE__) + '/../../spec_helper'
142 1
require File.dirname(__FILE__) + '/shared/collect.rb'
143 1
144 1
describe "Array#collect" do
145 5 Brian Ford
  it_behaves_like :array_collect, :collect
146 1
end
147 1
148 1
149 1
In @rubyspec/1.8/core/array/map_spec.rb@
150 1
151 1
require File.dirname(__FILE__) + '/../../spec_helper'
152 1
require File.dirname(__FILE__) + '/shared/collect.rb'
153 1
154 9 Brian Ford
describe "Array#map" do
155 11 Brian Ford
  it_behaves_like :array_collect, :map
156 9 Brian Ford
end
157 15 Shri Borde
158 15 Shri Borde
159 15 Shri Borde
h3. 1.3 Floating Point Values
160 15 Shri Borde
161 15 Shri Borde
Writing specs that use floating point values poses a problem because two values that look the same when rendered to a string may not actually be bitwise equal. Also, floating point operations can result in a value that differs based on the way the FPU carried out the operations.
162 15 Shri Borde
163 15 Shri Borde
Specs that compare floating point values should use @#should_be_close@ with the TOLERANCE constant. For floating point values that are exact, but larger than the precision formatted with #to_s (e.g. 1093840198347109283720.00), use the expanded float literal not the truncated precision format that #to_s provides (e.g. don't use 1.09384019834711e+21).
164 15 Shri Borde
165 15 Shri Borde
h3. 1.4 Private methods
166 16 Shri Borde
167 16 Shri Borde
Generally, no specs are written for private methods. A notable exeception are the specs for #initialize on some classes. These specs are primarily written to illustrate the behavior of #initialize for subclasses, where the subclass #initialize behavior is contrasted with the superclass's. Another exeception is #initialize_copy.
168 16 Shri Borde
169 16 Shri Borde
h3. 1.5 Ruby Ducktyping Interface
170 18 Shri Borde
171 16 Shri Borde
Ruby method dispatch behavior calls #method_missing if an instance has no method corresponding to a particular selector.  Ruby also defines a number of methods, for example, #to_ary, #to_int, #to_str, that form an interface to Ruby's ducktyping behavior. String methods, for instance, may call #to_str when passed an argument that is not a String.
172 16 Shri Borde
173 16 Shri Borde
The point of the RubySpecs is to describe behavior in such a way that if two different implementations pass a spec, Ruby code that relies on behavior described by the spec will execute with the same result on either implementation.
174 16 Shri Borde
175 16 Shri Borde
If a spec asserts that a method calls #to_int on an object, it is immaterial to the final outcome whether an implementation calls #to_int and handles the possibility that the method is missing in some way, or first calls #respond_to?(:to_int) and then calls #to_int. There are only two significant aspects to this from the perspective of user code (i.e. code using the interface, not the Ruby implementation code): 1) #to_int is called and performs some action; or 2) #to_int is not called.
176 16 Shri Borde
177 16 Shri Borde
It is conceivable that user code like the following exists:
178 16 Shri Borde
179 1
180 17 Shri Borde
  class Silly
181 16 Shri Borde
    def method_missing(sym, *args)
182 16 Shri Borde
      return 1 if sym == :to_int
183 16 Shri Borde
    end
184 16 Shri Borde
  end
185 16 Shri Borde
186 16 Shri Borde
187 1
In such case, the behavior of the following code would be different:
188 16 Shri Borde
189 18 Shri Borde
190 16 Shri Borde
  # The implementation calls #to_int without checking #respond_to?
191 16 Shri Borde
  [1, 2].at(silly) # => 2
192 16 Shri Borde
193 16 Shri Borde
  # The implementation calls #respond_to? first
194 16 Shri Borde
  [1, 2].at(silly) # => TypeError
195 16 Shri Borde
196 16 Shri Borde
197 16 Shri Borde
In the second case, the expected behavior is restored if the Silly class is modified to implement a #respond_to?(:to_int).
198 16 Shri Borde
199 16 Shri Borde
The point is that it really is not sensible to implement an object that provides an interface but does not let the world know about it by either 1) defining the method properly, or 2) defining #respond_to? to indicate that the object provides the interface.
200 16 Shri Borde
201 16 Shri Borde
If real-world code exists that _depends_ on this silly implementation (i.e. cannot be coded in a more realistic way), then we can revisit the utility of specs that require #respond_to? to be called. Otherwise, these specs are too tied to the implementation and impose an unrealistic burden on implementations that may exhibit perfectly compatible behavior but not call #respond_to?.
202 16 Shri Borde
203 1
h2. 2. Language
204 1
205 1
For the language specs, there is nothing as convenient or as concrete as a particular method to spec. Review the discussion of the "organization":/wiki/rubyspec/Organization of the language specs. The general conventions apply here: use simple English to describe the behavior of the _language entities_ and only add detail as needed. Use a single @describe@ block initially and add distinguishing @describe@ blocks as necessary. Use @it@ rather than @specify@ whenever possible.