You are on page 1of 11

Best Practices

A guide for programming well.

General

These are not to be blindly followed; strive to understand these and ask
when in doubt.
Don't duplicate the functionality of a built-in library.
Don't swallow exceptions or "fail silently."
Don't write code that guesses at future functionality.
Exceptions should be exceptional.
Keep the code simple.

Object-Oriented Design

Avoid global variables.


Avoid long parameter lists.
Limit collaborators of an object (entities an object depends on).
Limit an object's dependencies (entities that depend on an object).
Prefer composition over inheritance.
Prefer small methods. Between one and five lines is best.
Prefer small classes with a single, well-defined responsibility. When a class
exceeds 100 lines, it may be doing too many things.
Tell, don't ask.

Ruby

Avoid optional parameters. Does the method do too much?


Avoid monkey-patching.
Generate necessary Bundler binstubs for the project, such as rake and rspec,
and add them to version control.
Prefer classes to modules when designing functionality that is shared by
multiple models.
Prefer private when indicating scope. Use protected only with comparison
methods like def ==(other), def <(other), and def >(other).

Ruby Gems

Declare dependencies in the <PROJECT_NAME>.gemspec file.


Reference the gemspec in the Gemfile.
Use Appraisal to test the gem against multiple versions of gem dependencies
(such as Rails in a Rails engine).
Use Bundler to manage the gem's dependencies.

Use Travis CI for Continuous Integration, indicators showing whether GitHub


pull requests can be merged, and to test against multiple Ruby versions.

Rails

Add foreign key constraints in migrations.


Avoid bypassing validations with methods like save(validate: false),
update_attribute, and toggle.
Avoid instantiating more than one object in controllers.
Avoid naming methods after database columns in the same class.
Don't change a migration after it has been merged into master if the desired
change can be solved with another migration.
Don't reference a model class directly from a view.
Don't return false from ActiveModel callbacks, but instead raise an exception.
Don't use instance variables in partials. Pass local variables to partials from
view templates.
Don't use SQL or SQL fragments (where('inviter_id IS NOT NULL')) outside of
models.
Generate necessary Spring binstubs for the project, such as rake and rspec,
and add them to version control.
If there are default values, set them in migrations.
Keep db/schema.rb or db/development_structure.sql under version control.
Use only one instance variable in each view.
Use SQL, not ActiveRecord models, in migrations.
Use the .ruby-version file convention to specify the Ruby version and patch
level for a project.
Use _url suffixes for named routes in mailer views and redirects. Use _path
suffixes for named routes everywhere else.
Use a class constant rather than the stringified class name for class_name
options on ActiveRecord association macros.
Validate the associated belongs_to object (user), not the database column
(user_id).
Use db/seeds.rb for data that is required in all environments.
Use dev:prime rake task for development environment seed data.
Prefer cookies.signed over cookies to prevent tampering.
Prefer Time.current over Time.now
Prefer Date.current over Date.today
Prefer Time.zone.parse("2014-07-04 16:05:37") over Time.parse("2014-07-04

16:05:37")
Use ENV.fetch for environment variables instead of ENV[]so that unset

environment variables are detected on deploy.


Use blocks when declaring date and time attributes in FactoryGirl factories.
Use touch: true when declaring belongs_to relationships.

Testing

Avoid any_instance in rspec-mocks and mocha. Prefer dependency injection.


Avoid its, specify, and before in RSpec.
Avoid let (or let!) in RSpec. Prefer extracting helper methods, but do not reimplement the functionality of let. Example.
Avoid using subject explicitly inside of an RSpec it block. Example.
Avoid using instance variables in tests.
Disable real HTTP requests to external services with
WebMock.disable_net_connect!.
Don't test private methods.
Test background jobs with a Delayed::Job matcher.
Use stubs and spies (not mocks) in isolated tests.
Use a single level of abstraction within scenarios.
Use an it example or test method for each execution path through the
method.
Use assertions about state for incoming messages.
Use stubs and spies to assert you sent outgoing messages.
Use a Fake to stub requests to external services.
Use integration tests to execute the entire app.
Use non-SUT methods in expectations when possible.

Bundler

Specify the Ruby version to be used on the project in the Gemfile.


Use a pessimistic version in the Gemfile for gems that follow semantic
versioning, such as rspec, factory_girl, and capybara.
Use a versionless Gemfile declarations for gems that are safe to update often,
such as pg, thin, and debugger.
Use an exact version in the Gemfile for fragile gems, such as Rails.

Postgres

Avoid multicolumn indexes in Postgres. It combines multiple indexes


efficiently. Optimize later with a compound index if needed.
Consider a partial index for queries on booleans.
Constrain most columns as NOT NULL.
Index foreign keys.
Use an ORDER BY clause on queries where the results will be displayed to a
user, as queries without one may return results in a changing, arbitrary
order.

Background Jobs

Store IDs, not ActiveRecord objects for cleaner serialization, then re-find the

ActiveRecord object in the perform method.

Email

Use SendGrid or Amazon SES to deliver email in staging and production


environments.
Use a tool like ActionMailer Preview to look at each created or updated mailer
view before merging. Use MailView gem unless using Rails version 4.1.0 or
later.

Web

Avoid a Flash of Unstyled Text, even when no cache is available.


Avoid rendering delays caused by synchronous loading.
Use https instead of http when linking to assets.

JavaScript

Use the latest stable JavaScript syntax with a transpiler, such as babel.
Include a to_param or href attribute when serializing ActiveRecord models,
and use that when constructing URLs client side, rather than the ID.

HTML

Don't use a reset button for forms.


Prefer cancel links to cancel buttons.
Use <button> tags over <a> tags for actions.

CSS

Use Sass.
Use Autoprefixer to generate vendor prefixes based on the project-specific
browser support that is needed.

Sass

Prefer overflow: auto to overflow: scroll, because scroll will always display
scrollbars outside of OS X, even when content fits in the container.
Use image-url and font-url, not url, so the asset pipeline will re-write the
correct paths to assets.
Prefer mixins to @extend.

Browsers

Avoid supporting versions of Internet Explorer before IE10.

Objective-C

Setup new projects using Liftof and follow provided directory structure.
Prefer categories on Foundation classes to helper methods.
Prefer string constants to literals when providing keys or key paths to
methods.

Shell

Don't parse the output of ls. See here for details and alternatives.
Don't use cat to provide a file on stdin to a process that accepts file
arguments itself.
Don't use echo with options, escapes, or variables (use printf for those cases).
Don't use a /bin/sh shebang unless you plan to test and run your script on at
least: Actual Sh, Dash in POSIX-compatible mode (as it will be run on
Debian), and Bash in POSIX-compatible mode (as it will be run on OSX).
Don't use any non-POSIX features when using a /bin/sh shebang.
If calling cd, have code to handle a failure to change directories.
If calling rm with a variable, ensure the variable is not empty.
Prefer "$@" over "$*" unless you know exactly what you're doing.
Prefer awk '/re/ { ... }' to grep re | awk '{ ... }'.
Prefer find -exec {} + to find -print0 | xargs -0.
Prefer for loops over while read loops.
Prefer grep -c to grep | wc -l.
Prefer mktemp over using $$ to "uniquely" name a temporary file.
Prefer sed '/re/!d; s//.../' to grep re | sed 's/re/.../'.
Prefer sed 'cmd; cmd' to sed -e 'cmd' -e 'cmd'.
Prefer checking exit statuses over output in if statements (if grep -q ...;, not if [
-n "$(grep ...)" ];).
Prefer reading environment variables over process output ($TTY not $(tty),
$PWD not $(pwd), etc).
Use $( ... ), not backticks for capturing command output.
Use $(( ... )), not expr for executing arithmetic expressions.
Use 1 and 0, not true and false to represent boolean variables.
Use find -print0 | xargs -0, not find | xargs.
Use quotes around every "$variable" and "$( ... )" expression unless you want
them to be word-split and/or interpreted as globs.
Use the local keyword with function-scoped variables.
Identify common problems with shellcheck.

Bash

In addition to Shell best practices,


Prefer ${var,,} and ${var^^} over tr for changing case.
Prefer ${var//from/to} over sed for simple string replacements.
Prefer [[ over test or [.
Prefer process substitution over a pipe in while read loops.
Use (( or let, not $(( when you don't need the result

Haskell

Avoid partial functions (head, read, etc).


Compile code with -Wall -Werror.

Ember

Avoid using $ without scoping to this.$ in views and components.


Prefer to make model lookup calls in routes instead of controllers (find, findAll,
etc.).
Prefer adding properties to controllers instead of models.
Don't use jQuery outside of views and components.
Prefer to use predefined Ember.computed.* functions when possible.
Use href="#" for links that have an action.
Prefer dependency injection through Ember.inject over initializers, globals on
window, or namespaces. (sample)
Prefer sub-routes over maintaining state.
Prefer explicit setting of boolean properties over toggleProperty.
Prefer testing your application with QUnit.
Testing
Prefer findWithAssert over find when fetching an element you expect to exist

Angular

Avoid manual dependency annotations. Disable mangling or use a preprocessor for annotations.
Prefer factory to service. If you desire a singleton, wrap the singleton class in
a factory function and return a new instance of that class from the factory.
Prefer the translate directive to the translate filter for performance reasons.
Don't use the jQuery or $ global. Access jQuery via angular.element.

Ruby JSON APIs

Review the recommended practices outlined in Heroku's HTTP API Design


Guide before designing a new API.
Use a fast JSON parser, e.g. oj

Write integration tests for your API endpoints. When the primary consumer of
the API is a JavaScript client maintained within the same code base as the
provider of the API, write feature specs. Otherwise write request specs.

Code Review
A guide for reviewing code and having your code reviewed.

Everyone

Accept that many programming decisions are opinions. Discuss tradeofs,


which you prefer, and reach a resolution quickly.
Ask questions; don't make demands. ("What do you think about naming this
:user_id?")
Ask for clarification. ("I didn't understand. Can you clarify?")
Avoid selective ownership of code. ("mine", "not mine", "yours")
Avoid using terms that could be seen as referring to personal traits. ("dumb",
"stupid"). Assume everyone is intelligent and well-meaning.
Be explicit. Remember people don't always understand your intentions
online.
Be humble. ("I'm not sure - let's look it up.")
Don't use hyperbole. ("always", "never", "endlessly", "nothing")
Don't use sarcasm.
Keep it real. If emoji, animated gifs, or humor aren't you, don't force them. If
they are, use them with aplomb.
Talk synchronously (e.g. chat, screensharing, in person) if there are too many
"I didn't understand" or "Alternative solution:" comments. Post a follow-up
comment summarizing the discussion.

Having Your Code Reviewed

Be grateful for the reviewer's suggestions. ("Good call. I'll make that
change.")
Don't take it personally. The review is of the code, not you.
Explain why the code exists. ("It's like that because of these reasons. Would
it be more clear if I rename this class/file/method/variable?")
Extract some changes and refactorings into future tickets/stories.
Link to the code review from the ticket/story. ("Ready for review:
https://github.com/organization/project/pull/1")
Push commits based on earlier rounds of feedback as isolated commits to the
branch. Do not squash until the branch is ready to merge. Reviewers should
be able to read individual updates based on their earlier feedback.
Seek to understand the reviewer's perspective.
Try to respond to every comment.
Wait to merge the branch until Continuous Integration (TDDium, TravisCI,

etc.) tells you the test suite is green in the branch.


Merge once you feel confident in the code and its impact on the project.

Reviewing Code

Understand why the change is necessary (fixes a bug, improves the user
experience, refactors the existing code). Then:
Communicate which ideas you feel strongly about and those you don't.
Identify ways to simplify the code while still solving the problem.
If discussions turn too philosophical or academic, move the discussion offline
to a regular Friday afternoon technique discussion. In the meantime, let the
author make the final decision on alternative implementations.
Ofer alternative implementations, but assume the author already considered
them. ("What do you think about using a custom validator here?")
Seek to understand the author's perspective.
Sign of on the pull request with a or "Ready to merge" comment.

Style Comments
Reviewers should comment on missed style guidelines. Example comment:
[Style](../style):

> Order resourceful routes alphabetically by name.

An example response to style comments:

Whoops. Good catch, thanks. Fixed in a4994ec.

If you disagree with a guideline, open an issue on the guides repo rather
than debating it within the code review. In the meantime, apply the
guideline.

How do I ...

Contents
... start a new Rails app?
... feature-test a Rails app's Javascript?

... start a new Rails app?


Use suspenders:
$
$
$
$
$

gem install suspenders


suspenders the-name-of-your-project-here
cd the-name-of-your-project-here/
bin/setup
rake

... feature-test a Rails app's Javascript?


Use capybara-webkit. In your Gemfile:

gem "capybara-webkit"
In spec/support/capybara_webkit.rb (for Rspec):
Capybara.javascript_driver = :webkit
Capybara::Webkit.configure do |config|
config.block_unknown_urls
end

When writing a spec, you must set the :js flag for that test to make use of
capybara-webkit. For example, in spec/features/user_signs_in_spec.rb:
feature "Authentication", :js do
scenario "A user signing in" do
create(:user, email: "me@example.com", password: "sekrit")
sign_in_as email: "me@example.com", password: "sekrit"
expect(page).to have_text("Welcome!")
end
end

Git Protocol
A guide for programming within version control.

Maintain a Repo

Avoid including files in source control that are specific to your development
machine or process.
Delete local and remote feature branches after merging.
Perform work in a feature branch.
Rebase frequently to incorporate upstream changes.
Use a pull request for code reviews.

Write a Feature
Create a local feature branch based of master.
git checkout master
git pull
git checkout -b <branch-name>

Rebase frequently to incorporate upstream changes.


git fetch origin
git rebase origin/master

Resolve conflicts. When feature is complete and tests pass, stage the
changes.
git add --all

When you've staged the changes, commit them.


git status
git commit --verbose

Write a good commit message. Example format:


Present-tense summary under 50 characters

* More information about commit (under 72 characters).


* More information about commit (under 72 characters).
http://project.management-system.com/ticket/123
If you've created more than one commit, use git rebase interactively to

squash them into cohesive commits with good messages:


git rebase -i origin/master

Share your branch.

git push origin <branch-name>

Submit a GitHub pull request.


Ask for a code review in the project's chat room.

Review Code
A team member other than the author reviews the pull request. They follow
Code Review guidelines to avoid miscommunication.
They make comments and ask questions directly on lines of code in the
GitHub web interface or in the project's chat room.
For changes which they can make themselves, they check out the branch.
git checkout <branch-name>
./bin/setup
git dif staging/master..HEAD

They make small changes right in the branch, test the feature on their
machine, run tests, commit, and push.
When satisfied, they comment on the pull request Ready to merge.

Merge
Rebase interactively. Squash commits like "Fix whitespace" into one or a
small number of valuable commit(s). Edit commit messages to reveal intent.
Run tests.
git fetch origin
git rebase -i origin/master

Force push your branch. This allows GitHub to automatically close your pull
request and mark it as merged when your commit(s) are pushed to master. It
also makes it possible to find the pull request that brought in your changes.
git push --force-with-lease origin <branch-name>

View a list of new commits. View changed files. Merge branch into master.
git log origin/master..<branch-name>
git dif --stat origin/master
git checkout master

git merge <branch-name> --f-only


git push

Delete your remote feature branch.

git push origin --delete <branch-name>

Delete your local feature branch.


git branch --delete <branch-name>

You might also like