Monday, August 13, 2007

Validation Nirvana

I've been researching content management systems, and in the process was reminded of some of the headaches that I've seen in the past when dealing with content on a site. Having just created a test to ensure that my javascript code was well formed, I started to wonder if it might be possible to validate all of my content -- javascript, css and html. The answer turns out to be "yes," and due to some good work done by others, the process turned out to be a lot less painful than I expected.

My goals were that as part of my automated tests, I would be able to ensure that:

  • The site javascript contained no obvious errors
  • The site css was correctly formed, and valid
  • Pages served from the site had valid XHTML

In addition, to avoid some misery I had encountered in earlier projects, I wanted to check a few other things:

  • All link (a) tags had valid href attributes
  • All img tags had valid src attributes
  • All url() attributes in my CSS were valid

I do use the Firefox extensions Firebug and HTMLTidy, but those only operate on the pages loaded in the browser, so they aren't really a substitute for automated validation.

Let's take these in order ...

Javascript

For how javascript checking is done, see my other posting (http://johnwedgwood.blogspot.com/2007/08/javascript-lint-and-assetpackager.html) about using a javascript lint program to automatically check javascript code for the most obvious errors.

CSS

I really wanted to use a command line tool for this, but could not find one that validated the CSS (there are a set of interesting tools for compacting css though). The solution everyone refers to is to use the w3c online validation tool (http://jigsaw.w3.org/css-validator/). It turns out that there is already a terrific plugin for doing exactly this job -- assert_valid_asset (http://www.realityforge.org/articles/2006/03/15/rails-plugin-to-validate-x-html-and-css). It even has a method assert_valid_css_files which is almost perfect. It creates test methods for each CSS file, and these test methods submit the file contents to the w3c site to be checked.

Unfortunately for me I have CSS files located in sub-directories, and this method uses the file name as part of the method name for the test, which doesn't work so well when the file name has a "/" in it. So I took the idea, and rolled my own method that handles this case, producing a file test/unit/css_validation_test.rb which processes each CSS file to create a test method for that CSS file, but which munges the name so that it can handle CSS files located in sub-folders.

And of course it didn't work -- failing to catch the errors that I'd created to test it. After hunting a bit, I noticed that the check that is made for errors from the w3c in assert_valid_asset doesn't appear to work (for me at least). The line #128 in assert_valid_asset.rb was referring to a set of nested div elements that contained the errors. In my testing it appeared as though errors were actually coming back in a table row marked with the error class, so I changed that line to reflect the return value that I was seeing:

1
REXML::XPath.each( REXML::Document.new(response.body).root, "//tr[@class='error']") do |element|

With that done, the code did work for me. All of the errors that I tossed at it were caught, and my CSS was being validated automatically. Here's the contents of test/unit/css_validation_test.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#
# A test for valid css. At the moment the calls to the w3c are failing, so this
# is commented out until I figure out a better approach.
#
require File.dirname(__FILE__) + '/../test_helper'

class CSSValidationTest < Test::Unit::TestCase
# Where our css is located
CSS_PATH = File.dirname(__FILE__) + '/../../public/stylesheets'

# CSS we don't want to process
EXCEPTIONS = ["#{CSS_PATH}/invalid.css"]

# Process the files, generating individual tests for each of them
(Dir::glob("#{CSS_PATH}/**/*.css") - EXCEPTIONS).each do |file_name|
# Create a test name by converting "/.../stylesheets/foo.css" to "foo"
test_name = file_name.scan(/^.*\/public\/stylesheets\/(.*)\.css/)[0][0].gsub("/", "_")

# Create our test case
class_eval <<-EOS
def test_
#{test_name}_valid_css
file_contents = File.open('
#{file_name}','rb').read

# Check the contents as valid css
assert_valid_css file_contents

# Check that the URL links are valid
assert_css_has_valid_urls file_contents, '
#{file_name}'
end

EOS

end

#
# Check the CSS contents for valid links
#
def assert_css_has_valid_urls(css, source)
# Find the contents of all the "url(...)" strings and check them
urls = css.scan(/url\((.*)\)/).flatten.each do |url|
assert_url_valid url, source
end
end
end

Note that there is a set of exceptions listed (files we don't want to check). I moved all my invalid CSS into the file invalid.css and mark this file as one not to be validated. The CSS in there is all related to setting opacity filters for dialog boxes, which is apparently supported, but not valid. If you have a set of CSS files from another source and you can't really change them, the exception list might be the right place to put them if they are generating errors.

There is also a call to assert_url_valid in there, which I'll cover separately under link checking.

HTML

I wanted to be somewhat aggressive -- validating every single response from my code during testing. There is a great slideshow and a small body of test helper code located here -- http://blog.spotstory.com/category/plugin/ -- The test helpers expect that you will have the assert_valid_markup plugin installed, but it turns out to work just fine with the assert_valid_asset plugin as well. What this code does is override the normal "get" and "post" methods used during testing, and performs validation on the results of those calls, checking HTML and RSS responses. It uses the assert_valid_markup method defined in the assert_valid_asset plugin to validate the HTML.

Unfortunately the code in assert_valid_asset package to check HTML sends it off to the w3c, just like it does for CSS, But for some reason I'm less comfortable about sending my HTML off to the w3c. What I wanted instead was to be able to validate my HTML locally, and I wanted to be able to use this tool on every response during testing. To do this I hunted down rails-tidy (a plugin), ruby-tidy (a gem), and the tidy library (a native library).

rails-tidy: http://blog.cosinux.org/pages/rails-tidy
ruby-tidy: http://rubyforge.org/projects/tidy
tidy library: http://tidy.sourceforge.net/

I'm running on Windows, so I downloaded the tidy DLL and put it somewhere that I could find it, and then configured rails-tidy with the path to the tidy library (see the readme file). With these in place, you can run tidy on HTML files if you wish (from the command line). You can also run a rake command to validate each of your views. In addition you get the nice assert_tidy call which you can use to validate your html in your tests. I used that assert_tidy call to override the behavior that was sending my HTML off to the w3c by redefining the assert_html_valid method that was being used by the test helper code:

1
2
3
4
5
6
7
8
9
class Test::Unit::TestCase
#
# Override the test_validation_helper code so it uses tidy instead of a web service
#
def assert_html_valid
# Check for valid XHTML
assert_tidy
end
end

At this point, I was feeling pretty great. I actually found a bunch of errors (including a few in my html-generating helpers) which reminded me that there was actually some value in this. When I was done, all the pages served as part of my functional tests were valid, my CSS was validating correctly, and the javascript was lint free. There was only one more thing I wanted to do.

Links and Images

I wanted to make sure that my links were valid, and that my images were valid. Normally these are a pain to check, since they require checking by hand, or at least loading the page in the browser and looking for errors. On a content rich site, this might be troublesome.

There was one thing working in my favor for links -- I won't have images linking to external sites (so no fully qualified URLs for images, ever). This means that for images, an external link is an error. This accomplishes two things: (1) it simplifies my checking and (2) I don't have to worry about fully qualified URLs producing a "mixed content" warning in IE when the image URL includes "http" but the page is being accessed via https.

There are existing (built-in) rails extensions to Test::Unit::TestCase for finding specific elements in an HTML tree. My code to check links was based on these, and was all placed into a test_asset_validation_helper.rb file. Note that I moved assert_html_valid from test_helper.rb and extended it to provide link checking. The assert_url_valid method is the same one that is called by the CSS checking code.

Summarizing the tests for valid links. A link is valid if any one of these is true:

  • There is a file in public which matches the link
  • It is recognized by the routing engine, and the route refers to a view
  • It is recognized by the routing engine, and the action is a method that the controller responds to

From the file test_asset_validation_helper.rb:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
class Test::Unit::TestCase
#
# Override the test_validation_helper code so it uses tidy instead of a web service
#
def assert_html_valid
# Check for valid XHTML
assert_tidy

# Check for valid a and img tags
assert_a_tags_valid
assert_img_tags_valid
end

#
# Check that the a tags in the response are valid links
#
def assert_a_tags_valid
# Validate a links
find_all_tag( :tag => "a" ).each do |a_tag|
href = a_tag.attributes["href"]
name = a_tag.attributes["name"]
line = a_tag.line

# One of these must exist
flunk("a tag on line #{line} of response must have href or name attribute") unless (href || name)

if href == "#" then
# This is a placeholder -- we allow these
elsif (href =~ /^#.+$/)
# If the href is of the form "#name" then we check that the name exists
assert_select "a[name=#{href[1..-1]}]", {:count => 1}, "Hash link '#{href}' has no anchor on this page"
else
# Assume it's a real link
assert_url_valid(href, "Line #{line} of response")
end
end
end

#
# Check that the img tags in the response are valid
#
def assert_img_tags_valid
# Validate img links
find_all_tag( :tag => "img" ).each do |img_tag|
src = img_tag.attributes["src"]
line = img_tag.line

# There must be a src
flunk("img tag on line #{line} of response must have src attribute") unless src

# We do not allow img tags to point to things off-site
if is_fully_qualified_url? src then
flunk("img tag on line #{line} of response has a fully qualified url")
end

# Assume it's a real link
assert_url_valid(src, "Line #{img_tag.line} of response")
end
end

#
# Check that a URL is valid by seeing if it is a public file, or if our routing
# scheme recognizes it. If our routing scheme does recognize it. If the routing
# scheme does recognize it, then we make sure that the action is handled either
# by a view, or by a method in the controller class.
#
def assert_url_valid(url, source)
# Ignore anything that looks like a fully qualified URL
return if is_fully_qualified_url? url

# Check for empty url
flunk("Empty url in #{source}") if url.empty?

# Strip query parameters
possible_file_name = url.gsub(/\?.*$/, "")

# Check for things in the public folder
file_path = File.expand_path(File.dirname(__FILE__) + "/../public/" + possible_file_name)
return if FileTest::exist? file_path

# Check for a valid route
route_info = ActionController::Routing::Routes.recognize_path url

# If the route parses, then check to make sure that a view exists
# for that controller + action. Assume that any suffix is good enough.
controller_name = route_info[:controller]
action_name = route_info[:action]
controller_class = "#{controller_name.camelize}Controller".constantize

# Construct the path to the view
view_path = "app/views/#{controller_name}/#{action_name}"

# Get the set of possible views
possible_views = Dir::glob(File.dirname(__FILE__) + "/../#{view_path}.*")

# If any view exists, that's good enough
return if possible_views.length > 0

# There are no views ... that means that the controller must respond to the action directly
unless (controller_class.method_defined? action_name)
flunk("Controller #{controller_name} does not respond to #{action_name}, and there is no view either")
end

rescue Exception => ex
# Other failures, probably in mapping the route
flunk("#{source} - Path not recognized: #{url} -- #{ex.message}")
end

#
# Returns true if the passed string represents a fully qualified URL
#
def is_fully_qualified_url?(url)
url =~ /^(http|https|ftp|mailto|callto).*$/
end

end

Thursday, August 2, 2007

Javascript Lint and AssetPackager

The AssetPackager plugin is a configurable tool which allows you to package multiple javascript (or css) files, in whatever order you want, into a single file. This speeds up your page loads by reducing the number of times the users browser needs to fetch files from your server. So instead of a dozen separate javascript and css fetches, you can get down to two if you want. In addition, it includes tools to compact the javascript and css, making the files even smaller.

http://synthesis.sbecker.net/pages/asset_packager

You install it as a plugin, add a capistrano deployment task, and really you're good to go. For my needs, it's perfect.

The only problem that I ran into was something I've seen in other projects that used javascript compactors. If you have things like a missing semi-colon, the compacted code ends up producing errors in the browser, where the uncompacted code does not. This can be a real headache because typically you never actually run the compacted code locally, only when it is finally deployed onto your server.

What I wanted was a way to check for this problem, and ideally check for it as part of my normal deployment process. Since I run all my tests before installing, it seemed natural to include a check of the javascript syntax as part of my tests, so that's what I did.

The first part of this is finding a javascript lint program. I found the "JavaScript Lint" tool:

http://www.javascriptlint.com/

This lint tool was created by Matthias Miller. It's great, and very configurable. It also is available as an executable, which was convenient for automation.

Having downloaded and installed it, I ran into another roadblock. The prototype, scriptaculous and behavior libraries that I was using did not pass lint. Actually, some of my own code didn't pass lint either, but at least there wasn't much of it, so it was easy to fix.

When it comes to lint, you really want to start early, and keep your code clean. If you have a ton of code that is dirty, it tends to just linger because most people don't want to go make 200 changes (even if they are as simple as adding a semicolon or braces), and since hunting for new issues in a sea of old lint errors isn't any fun, the end result is that you just stop using lint entirely. I am lucky enough to be at the start of a project, so fixing the dozen warnings took minutes, and it means that any new warnings are indicators of something that I can (and should) really pay attention to.

With my own code clean, what I wanted was for my tests to run lint on any javascript files which I hadn't specifically exempted, so that new files would be caught in the lint trap, rather than being omitted until I remembered to add them.

That led to the test you see below, which was created in test/unit/javascript_validation_test.rb in my rails application. It runs through every file in the public/javascript folder, removes the ones that I've specifically exempted, removes anything matching a pattern (which was useful for removing files generated by the AssetPackager) and then runs lint on the remaining files, creating output only if the test fails.

The end result is automated javascript lint checking, which runs as part of my standard install process. I'm sure that there will be cases that won't get caught, but at least I feel pretty comfortable that issues like missing semicolons and extra commas in array definitions will be caught and fixed before any code gets installed.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#
# A test for valid javascript. Every javascript file is passed through
# jsl, except for the ones which we know will fail, but we feel good about
# because they come from standard libraries.
#
require File.dirname(__FILE__) + '/../test_helper'

class JavascriptValidationTest < Test::Unit::TestCase
# Where to find the JSL program
JSL_EXECUTABLE = "c:/jsl-0.3.0/jsl"

# Where to find the config file with all our lint settings
JSL_CONFIG_FILE = File.dirname(__FILE__) + '/../jsl.conf'

# Where our javascript is located
JAVASCRIPT_PATH = File.dirname(__FILE__) + '/../../public/javascripts'

# Files we don't want to process because they are either not actually javascript
# files, or because we tested them with the asset packer once, they worked fine,
# and we don't expect them to change.
EXCEPTION_LIST = %w{
.
..
.svn
behaviour.js
builder.js
controls.js
dragdrop.js
effects.js
prototype.js
redbox.js
scriptaculous.js
slider.js
tiny_mce
}


# Pattern for stuff we don't want to process
EXCEPTION_PATTERN = /(^base_\d+\.js$)/

def test_for_valid_javascript
# For each javascript file in the folder
all_files_in_path = Dir::entries(JAVASCRIPT_PATH)

# Figure out which files match our pattern of things not to process
files_matching_exception_pattern = all_files_in_path.grep(EXCEPTION_PATTERN)

# The set of files to process does not include our exceptions
files_to_process = all_files_in_path - (EXCEPTION_LIST + files_matching_exception_pattern)

# Process the files -- checking for exception patterns
files_to_process.each do |file|
# Capture the output
jsl_output = `#{JSL_EXECUTABLE} -conf #{JSL_CONFIG_FILE} -nologo -process "#{JAVASCRIPT_PATH}/#{file}"`

# Printing it only if something went wrong
print jsl_output if $? != 0

# And the obligatory assert, so our tests can fail if need be
assert_equal $?, 0, "#{file} fails to pass lint"
end
end
end

Wednesday, August 1, 2007

My Toolkit

This reflects my current toolkit as of August 1st, 2007.

Ruby and Ruby on Rails

There's enough on the web about these. I'm almost embarrassed by how much I love them.

http://www.ruby-lang.org/en/downloads/
http://www.rubyonrails.org/down

I have ruby installed into C:\ruby, and rails installed as a gem there.

Cygwin

Using cygwin is a blessing and a curse. I love the unix command line tools, but the way that cygwin munges paths makes it sometimes painful to use tools which are expecting DOS paths. Pretty much everything installed in cygwin into /usr/bin works great, but as soon as you attempt to use something outside of the cygwin bubble, things start to break down a bit. For me this has mostly affected ruby-specific tools (e.g. autotest, capistrano, etc) which are installed as gems in the non-cygwin ruby, and fail to run when executed directly from cygwin because the ruby interpreter receives the cygwin path to the script when the script is run from the cygwin command line.

I suppose that I could have made my life a bit easier and done all my work using the cygwin installation of ruby, but I had ruby installed before I decided to do more work inside of cygwin, so as a result when I'm working in cygwin I've had to specifically defeat cygwin's attempts to use its own version of ruby. As a result my .bashrc contains a set of aliases which feel like hacks, but have worked just fine.

# Force the c:/ruby version to be found first
PATH=/cygdrive/c/ruby/bin:$PATH

alias autotest='ruby /ruby/bin/autotest'
alias cap='ruby /ruby/bin/cap'
alias gem='ruby /ruby/bin/gem'
alias rake='ruby /ruby/bin/rake'
alias rcov='ruby /ruby/bin/rcov'
alias ruby='/cygdrive/c/ruby/bin/ruby'
alias specrb='ruby /ruby/bin/specrb'
In the interests of cleanliness I'm sure that I will end up doing something about these, but having set them up, I haven't had to revisit them in a long time.

Gems

Other than the site specific gems, there are a few gems that I have installed which I think are terrific. They are:

Capistrano rocks. Deploying a new build is almost too easy and seems to take about 30 seconds. It really has completely removed all the misery and guesswork from deploying an update to the site.

This fits nicely with the auto-generated rails tests, and allows for some really readable test code:

context "A stream owner" do
...
specify "should be able to create a picture stream" do
...

It's been great. I initially valued the fact that I could add test-spec tests to existing tests and have it all sort of work together, but after a little bit I just went in and replaced all of my old tests with these because I liked the format.

Autotest provides continuous testing of your code. It notices which files have changed, and runs the appropriate tests. If you have snarl configured, when a problem is encountered you get a brief and unobtrusive notification dialog that tells you that some tests have failed, which is a nice clue that you need to go fix something.

Another fantastic tool. I run my tests using rcov, and it tells me how well the tests cover the code. It also produces some phenomenally cool looking output (viewable in your browser) that tells you specifically which lines were covered and weren't, allowing you to browse the code in your browser, spot the areas that were not tested, and write tests for those areas. You really have to see it to believe it. Check the screenshots in the summary section on the eigenclass.org link.

Editors, Source Control, etc...

It's free, and it is really good. There are the cool things I had expected (syntax highlighting, auto completion, etc) and then the occasional nice touch I had not expected, like typing Dir::entries and getting tooltip help for the method.

I am ambivalent about Tortoise. I spent about 2 hours getting it to work nicely with ssh-agent so that I didn't have to type my password over and over again when accessing an svn depot via svn+ssh (that deserves a separate writeup) but other than that little bit of misery, it's been working nicely. I like the fact that it integrates itself as a Windows Explorer extension, allowing me to use the right-click menu in the explorer to do SVN operations. It makes SVN feel well integrated, which is a nice touch.

Getting CodeRay formatting into Blogger

This is the approach I settled on for publishing code snippets in Blogger. If there is a better way, I'd love to hear about it.

First, about CodeRay (http://coderay.rubychan.de/) -- CodeRay is a tool that takes snippets of code, and formats the result as HTML, using CSS styles to create an attractive result. Here's an example.

1
2
3
4
5
6
7
8
9
10
  #
# Handle login requests
#
def login
# Attempt to do the login
self.current_user = User.authenticate(params[:login], params[:password])
if logged_in?
if params[:remember_me] == "1"
self.current_user.remember_me

Some blogging engines have plugins that make it easy to generate results like this, but I haven't found one for Blogger. The approach I chose has two elements:

1) The CSS styles.

There are a lot of CodeRay stylesheets out there. I suggest you find one that you like and start from there. You need to paste the CSS into your template, and from there you can see how it looks on your blog, and then tweak it until you get the result you like.

You will want to save the CSS somewhere (outside of Blogger). I've noticed that when you change templates, you lose the custom CSS that you stuck in the template, so any carefully tweaked values will be lost.

2) Actually generating the formatted code.

This second part turns out to not be terribly difficult, but I wanted a simple tool that would allow me to copy code from my editor, and then produce a formatted result that I can paste into the "Edit Html" tab in Blogger. Here is the script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#!c:/ruby/bin/ruby.exe -rubygems
require 'coderay'
require 'win32/clipboard'
include Win32

# Fix the formatted code to work nicely in blogger.
def format_for_blogger(str)
# Blogger has this quirk where it transforms newlines to <br />
# tags. This is great, except when you don't want it, for instance
# the HTML <tr><br /><td> is illegal. This code finds all of the
# places where things like <tr> exist with a newline after them,
# and removes the newline
blogger_result = str.gsub(/(\s*\n\s*)(<|<\/)(table|tr|td)/i) { |x| x.gsub("\n"," ") }

# CodeRay produces <tt> elements that have nothing but a newline in them.
# Replacing these with simple \n (which blogger transforms to <br />)
# also helps in making things line up nicely. Note the space after the
# newline. That turns out to be important to get nice results in IE.
blogger_result.gsub(/<tt>\s*\n\s*<\/tt>/i, "\n ")
end


# Quit if there's no clipboard
print "The clipboard was empty" and exit unless Clipboard.data

# Format it via CodeRay, and process the result specially for blogger
formatted_code = CodeRay.scan(Clipboard.data, :ruby).div({
:line_numbers => :table,
:bold_every => 5,
:line_number_start => 1
}
)

blogger_code = format_for_blogger(formatted_code)

# Set the clipboard contents and show the user the result
Clipboard.set_data blogger_code

print blogger_code

This relies on two gems, which you can install with:

gem install coderay
gem install win32-clipboard

The script takes the contents of the clipboard, runs it through the CodeRay formatter (passing options to get a table with line numbers) and then puts the result back onto the clipboard. This makes it possible to copy code, run the script and then paste into the "Edit Html" tab of the editor in Blogger.

The Manifesto

Here I am, starting a new company. I have a great partner, but he's not writing code, which leaves me free to try to articulate some of the things I think are important in an engineering culture, and to enforce them ruthlessly ... on myself.

Here is the manifesto. Since we plan on building a site using Ruby on Rails, it has a bit of a focus on those technical underpinnings. I've tried to call out the key items in bullet points.

The big picture

Stay grounded in the core questions:

  • What is the service are we offering?
  • What specifically are we trying to enable?
  • Are we making it easy for the people we serve?
  • Are we making it fun for them?

Why have conventions?

Formal conventions set the expectations that we have for ourselves, and for each other. They make explicit the things that are most important, with the hope that the details will take care of themselves. They provide tools by which we can hold each other accountable, in what is hopefully a positive way, to the shared task of creating great products built on a solid foundation of design and code.

  • Define conventions for things that matter
  • Keep to the conventions
  • Hold each other to high standards
  • Take pride in the work
  • Keep raising the bar

Design

We do our initial designs on paper or using simple design tools. The goal is to turn around designs quickly, and to focus on basic usability questions before focusing on beauty, graphics or color schemes.

We write documentation that describes use cases, starting with the simple and moving to the complex. As much as possible, we do this before we start writing code. The goal is to evaluate the quality of the user experience by attempting to document it up front. We iterate on documentation and design until we feel that we are describing the user experience that we want to offer.

We write code when we think that we have a design and documentation that is mostly complete and which fulfills our goals for the user. We expect to iterate after the code is written. We try to evaluate and iterate quickly, rather than letting mediocre results become accepted fact.

  • Use paper or simple graphics tools
  • Focus on flow and usability
  • Keep the big picture questions in mind
  • Think about error handling or odd use cases
  • Write documentation
  • Expect to make changes after the code is written
  • Evaluate and iterate quickly
  • Don't let mediocre aspects become an accepted fact

Code

The goal is to create a service built on a foundation of an elegant code base that is small and clean. We build tools and libraries. We avoid repetition in the code. The difference between 500 and 1000 lines of code isn't that much, but the difference between 50,000 and 100,000 lines is huge. The path to a bad outcome involves a series of cumulative decisions involving small bodies of code. Paying attention to the small pieces pays off in the aggregate.

We subscribe to the "broken windows" approach to maintaining a clean code base. Once a body of code starts to become "broken", it is easier to add new elements that are broken. By keeping the code clean, it becomes harder to add hacks later.

We write documentation including method and class headers, and we use tools to generate code documentation. We do this from the start, because while it's not very important in a small code base, it is difficult to go back and do later. In addition creating documentation requires some introspection on the part of the engineer, which hopefully leads to better code.

We try to keep controllers and views simple. It is easy to dump code into both places, but it produces code that is harder to understand and maintain. The effort to keep these clean results in helpers, which may not be immediately useful, but the simple act of creating libraries of code at least pushes us toward introspection and reuse.

We think in terms of APIs. When writing our controllers we think in terms of the API that we are providing to outside developers -- is it consistent? is it elegant? is it easy to understand? When we write library and helper methods, we look to achieve the same goals.

  • Write class and method headers suitable for automatically generating code documentation
  • Keep methods short, pulling out code into libraries if needed
  • Keep Controllers and Views as simple as possible
  • Do not repeat code, create libraries or helpers
  • Strive for elegance
  • Always look for simpler ways to do things
  • When you learn something new, look back at old code and apply that knowledge
  • Think in terms of APIs, and imagine other developers needing to understand how to use your code

Testing

We design with testing in mind. Where possible, we design features and write code which can tested using automated testing tools.

We write automated tests, and we write them as part of developing new features. When a feature is installed, it is expected that there is a valid and complete set of automated tests that will test the feature. This is particularly true with unit and functional tests (for models and controllers). For integration testing, we write stories that describe user behavior, and build tests around those stories.

We use these tests to help ensure that new changes haven't broken existing code. Testing by hand is hugely expensive in terms of time and morale. Automated tests don't remove the requirement for testing by hand, but they can dramatically reduce the amount of time required, and dramatically increase confidence in the stability of the code base.

As we find bugs, we decide whether the bug is a simple coding error, or whether it is rooted in a deeper logic or design mistake. If it is not a simple error, then we write a test to detect the error before we fix it. The goal is to ensure that the bug is fixed, but also to ensure that the same sort of bug won't be reintroduced later.

We write test code which is readable and documented. There may eventually be as much test code written as there is code in the site itself, and we should treat the test code as if it were part of the product.

For tools, we use test-spec to help write readable tests. We use the autotest testing library to warn us of tests which are failing due to changes we've made. No code should be installed that does not pass the existing suite of tests.

We use rcov before we install new code to evaluate how completely our tests are covering our code base. We evaluate the results to decide what tests we should add. Our goal is 100% coverage. When we install, we want to know that every line of code at least executes correctly in a basic usage testing model.

  • Design with testing in mind
  • Run autotest when you begin development
  • Write tests as part of developing new features
  • Use test-spec to make tests readable
  • Use RCov to evaluate the effectiveness the tests
  • Do not install code until tests exist, and produce 100% coverage

HTML, CSS and Javascript

We attempt to keep our HTML clean by separating out behavior into javascript files, and binding that behavior to DOM objects via the behavior library. This makes it easier for us to read and maintain HTML code, and easier for designers to work in our templates.

Where possible, we use CSS names that describe what things are, not how they look. So we prefer class names like "emailaddress" over "grayanditalic". We used nested CSS rules to affect differences in appearance in different contexts.

The goal is that the same CSS name will apply to the same type of object, regardless of where that object is placed on the site. This creates simple and self-documentating HTML code that is readable and can be maintained by both engineers and design staff.

We use partials to avoid duplication of HTML. All the same issues which apply to source code apply to HTML, CSS and javascript. It should be clean, elegant, and should not include duplication.

  • Keep the HTML clean
  • Use CSS class names that describe what something is, not how it should look
  • Use the same CSS class name to describe the same thing, no matter where it is
  • Use nested CSS class definitions to alter the look of similar items across the site
  • Where possible, put javascript in javascript files, not in HTML
  • Use behavior to bind javascript to HTML
  • Make it clean, elegant, and avoid duplication

Javascript

We try to minimize the amount of site logic built around javascript. Javascript is hard to test with automated testing tools, which means that to test large bodies of javascript code requires human testing across a broad range of browsers, which is expensive, time consuming and prone to error.

We prefer to have a hit on the server to fetch a small amount of data instead of having a separate body of code that we need to debug in javascript.

This does not mean that we won't render content in javascript (as the result of an ajax request to the server for data), but we avoid writing complex logic in javascript.

  • Keep the javascript as simple as possible
  • Prefer a small hit on the server over complex javascript logic
  • Focus javascript around simple effects or rendering simple UI