Skip to content

Instantly share code, notes, and snippets.

@davidfstr
Last active December 26, 2015 09:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save davidfstr/7127276 to your computer and use it in GitHub Desktop.
Save davidfstr/7127276 to your computer and use it in GitHub Desktop.
JavaScript Code Conventions
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<style>
h1,
h2,
h3,
h4,
h5,
h6,
p,
blockquote {
margin: 0;
padding: 0;
}
body {
font-family: "Helvetica Neue", Helvetica, "Hiragino Sans GB", Arial, sans-serif;
font-size: 13px;
line-height: 18px;
color: #737373;
margin: 10px 13px 10px 13px;
}
a {
color: #0069d6;
}
a:hover {
color: #0050a3;
text-decoration: none;
}
a img {
border: none;
}
p {
margin-bottom: 9px;
}
h1,
h2,
h3,
h4,
h5,
h6 {
color: #404040;
line-height: 36px;
}
h1 {
margin-bottom: 18px;
font-size: 30px;
}
h2 {
font-size: 24px;
}
h3 {
font-size: 18px;
}
h4 {
font-size: 16px;
}
h5 {
font-size: 14px;
}
h6 {
font-size: 13px;
}
hr {
margin: 0 0 19px;
border: 0;
border-bottom: 1px solid #ccc;
}
blockquote {
padding: 13px 13px 21px 15px;
margin-bottom: 18px;
font-family:georgia,serif;
font-style: italic;
}
blockquote:before {
content:"\201C";
font-size:40px;
margin-left:-10px;
font-family:georgia,serif;
color:#eee;
}
blockquote p {
font-size: 14px;
font-weight: 300;
line-height: 18px;
margin-bottom: 0;
font-style: italic;
}
code, pre {
font-family: Monaco, Andale Mono, Courier New, monospace;
}
code {
background-color: #fee9cc;
color: rgba(0, 0, 0, 0.75);
padding: 1px 3px;
font-size: 12px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
pre {
display: block;
padding: 14px;
margin: 0 0 18px;
line-height: 16px;
font-size: 11px;
border: 1px solid #d9d9d9;
white-space: pre-wrap;
word-wrap: break-word;
}
pre code {
background-color: #fff;
color:#737373;
font-size: 11px;
padding: 0;
}
@media screen and (min-width: 768px) {
body {
width: 748px;
margin:10px auto;
}
}
</style>
<title>JavaScript Code Conventions - 0.3.1</title>
</head>
<body>
<h1>JavaScript Code Conventions - 0.3.1</h1>
<p>This document describes the code conventions used in JavaScript code written by the Splunk Seattle office, notably in the Web Framework and JavaScript SDK projects. It is intended to guide developers to a common style and reduce the number of style-related nits received in code review comments.</p>
<p>Although this is a JavaScript guide, a number of recommendations here are language independent.</p>
<h2>Meta</h2>
<h3>Be consistent</h3>
<p>When working in an existing source file that is consistently styled in a particular way, <em>be consistent with local style</em>. This takes precedence over all other guidelines.</p>
<p>If local style is inconsistent but a dominant local style prevails, use that style. If there is no clear dominant style then insert new code using the style of this guide.</p>
<h3>Guidelines, not strict rules</h3>
<p>This set of conventions is a set of guidelines, not absolute rules.</p>
<p>For example if a guideline exists for a particular rationale that does not apply in a particular situation, the guideline may not apply.</p>
<p>Other guidelines with no particular rationale exist mainly for consistency (such as the choice of 4-space vs. 2-space indentation). By analogy with the real world, there's no particular reason why the cold water faucet is on the right in the USA, although it is very useful that it is consistently there. Otherwise you might burn yourself should a rouge sink-maker put the hot water on the right instead.</p>
<p>If a guideline needs to be ignored in a particular case, consider adding a comment to explain why, usually in the form of a <code>NOTE</code> flag comment. (Flag comments are described below.)</p>
<h2>Conceptual Layout</h2>
<h3>Top-to-bottom; Logical Grouping</h3>
<ul>
<li><p>Code is easiest to read in a top-to-bottom fashion, as one would read a book. Therefore top-level functions should be arranged before lower-level functions. <strong>Functions should call downward.</strong></p>
<p>Additionally <strong>logically related methods should be grouped together</strong>. By contrast the use of unmeaningful orderings like a strict alphabetical ordering is discouraged.</p>
<p>Yes:</p>
<pre><code>var BaseSplunkView = Backbone.View.extend({
constructor: function(options, settingsOptions) {
...
this.configure();
this.initialize();
...
},
configure: function() { ... },
initialize: function() { ... },
bindToComponentSetting: function(settingName, fn, fnContext) { ... },
bindToComponent: function(id, fn, fnContext) { ... },
unbindFromComponent: function(id, fn, fnContext) { ... },
...
});
</code></pre>
<p>Above, the methods are conceptually bound together in the structure:</p>
<ul>
<li>constructor
<ul>
<li>configure</li>
<li>initialize</li>
</ul>
</li>
<li>(binding methods, commonly called in initialize)
<ul>
<li>bindToComponentSetting</li>
<li>bindToComponent</li>
<li>unbindFromComponent</li>
</ul>
</li>
<li>(other methods that apply after initialization)
<ul>
<li><p>...</p></li>
</ul>
</li>
</ul>
</li>
<li><p>Exception: There are some technical circumstances where calling upward is required, such as the final return statement in an AMD-style define clause. These circumstances should be considered the exception rather than the norm.</p>
<p>Yes:</p>
<pre><code>define(function(require, exports, module) {
var _ = require('underscore');
...
var BaseSplunkView = Backbone.View.extend({
...
});
return BaseSplunkView;
});
</code></pre></li>
<li><p>Exception: Many developers are not familar with the details of <em>hoisting</em> in JavaScript, so it should be considered a technical limitation that nested local functions must call up.</p>
<p>Yes:</p>
<pre><code>function formatMessageElement(message) {
// NOTE: Does not handle characters outside the Basic Multilingual Plane.
var isValidChar = function(charCode) {
return charCode &gt;= 32;
};
var isValidString = function(s) {
for (var i = 0; i &lt; s.length; i++) {
if (!isValidChar(s.charCodeAt(i))) {
return false;
}
}
return true;
};
return (isValidString(message))
? "&lt;message&gt;" + message + "&lt;/message&gt;"
: "&lt;message/&gt;";
}
</code></pre>
<p>No:</p>
<pre><code>function formatMessageElement(message) {
return (isValidString(message))
? "&lt;message&gt;" + message + "&lt;/message&gt;"
: "&lt;message/&gt;";
function isValidString(s) {
for (var i = 0; i &lt; s.length; i++) {
if (!isValidChar(s.charCodeAt(i))) {
return false;
}
}
return true;
};
// NOTE: Does not handle characters outside the Basic Multilingual Plane.
function isValidChar(charCode) {
return charCode &gt;= 32;
};
}
</code></pre></li>
<li><p>However if you were to pull the nested local functions out to be siblings instead, top-to-bottom ordering still applies:</p>
<p>Yes:</p>
<pre><code>function formatMessageElement(message) {
return (isValidString(message))
? "&lt;message&gt;" + message + "&lt;/message&gt;"
: "&lt;message/&gt;";
}
function isValidString(s) {
for (var i = 0; i &lt; s.length; i++) {
if (!isValidChar(s.charCodeAt(i))) {
return false;
}
}
return true;
};
// NOTE: Does not handle characters outside the Basic Multilingual Plane.
function isValidChar(charCode) {
return charCode &gt;= 32;
};
</code></pre></li>
</ul>
<h3>Use paragraphs to group common functionality</h3>
<p>Individual methods and functions should be organized such that each does one thing and does it well.</p>
<p>Nevertheless a method may perform multiple discrete low-level actions when performing a higher-level task. Actions that takes a few lines of code are good to visually separate from adjacent actions with a empty line. One-line actions do not benefit from separation.</p>
<p>Example:</p>
<pre><code>_configureBindingForPush: function(propName) {
var binding = this._bindings[propName];
if (!TokenUtils.isToken(binding.template)) {
// This property's template is not presently bound to a
// single token. Therefore there is no token that can
// be pushed to yet.
return;
}
// Forward value changes to solitary token in template
var that = this;
var listener = function(model, newValue, options) {
that._pushPropertyValue(propName);
};
this.listenTo(this, 'change:' + propName, listener);
// Save listener for later removal
listener.dispose = function() {
that.stopListening(that, 'change:' + propName, listener);
};
binding._listeners.push(listener);
},
</code></pre>
<p>Many of the paragraphs in this example also have leading comments, which are useful.</p>
<p>Paragraphs that grow too large are good to extract to private methods.</p>
<h3>Line Spacing</h3>
<h4>Methods separated by one blank line</h4>
<p>Adjacent methods, classes, and paragraphs should be separated by one blank line to make them easy to distinguish.</p>
<p>Yes:</p>
<pre><code>var TextInputView = BaseInputView.extend({
initialize: function() {
...
},
getValue: function() {
...
},
setValue: function(value) {
...
}
});
</code></pre>
<p>No:</p>
<pre><code>var TextInputView = BaseInputView.extend({
initialize: function() {
...
},
getValue: function() {
...
},
setValue: function(value) {
...
}
});
</code></pre>
<h4>Minimize extra blank lines</h4>
<ul>
<li><p>There should not be blank lines at the beginning or end of a block.</p>
<p>Yes:</p>
<pre><code>var TextInputView = BaseInputView.extend({
initialize: function() {
...
}
});
</code></pre>
<p>No:</p>
<pre><code>var TextInputView = BaseInputView.extend({
initialize: function() {
...
}
});
</code></pre></li>
<li><p>There should never be two blank lines in a row.</p></li>
<li><p>There should not be extra blank lines at the beginning of a file. An extra blank line may exist at the end of a file to accomodate environments that expect a trailing newline for every file.</p></li>
</ul>
<h2>Physical Layout</h2>
<h3>Indentation</h3>
<ul>
<li><p>Spaces only. No tabs.</p></li>
<li><p>JavaScript files use 4-column indentation.</p></li>
<li><p>JSON files use 4-column indentation.</p></li>
<li><p>HTML may use either 4-column or 2-column indentation. Prefer 4-column indentation if there is no local preference to allow easy mixing of standalone JS with HTML that includes embedded JS.</p></li>
</ul>
<p>Tabs are avoided because they are interpreted differently by lots of different tools. In particular some tools may assume a tab width of 8 while others may assume a width of 4. For consistent behavior everywhere, only spaces are a safe bet.</p>
<p>To make this guideline easy to follow, please configure your editor to automatically convert inserted tabs to spaces. But preserve the tab-ness of any tabs that already exist in the file. For Sublime:</p>
<pre><code>// When tab key is pressed, insert spaces. Leave preexisting tabs alone.
"translate_tabs_to_spaces": true
</code></pre>
<p>Note that a few file types require real tabs in certain circumstances. Makefiles in particular.</p>
<h3>Line Length</h3>
<ul>
<li><p>Avoid lines longer than 80 columns.</p></li>
<li><p>Strongly avoid lines longer than 100 columns unless there is a specific technical reason.</p></li>
</ul>
<p>Long lines make it difficult to have many documents onscreen at once. Typographically they are hard to read. They also interfere with Crucible side-by-side diffs because each side of the diff has its min-width set to the length of the longest line in the file under review.</p>
<p>To make this guideline easy to follow, please configure your editor to display a vertical rule at the 80 and 100 column positions. For Sublime:</p>
<pre><code>"rulers": [80, 100]
</code></pre>
<h3>Breaking Long Lines</h3>
<ul>
<li><p>When breaking a statement on multiple physical lines, the second and subsequent
lines should be indented a single level greater than the first line.</p></li>
<li><p>Break lines after operators, not before.</p>
<p>Yes:</p>
<pre><code>complexical = super­cali­fragi­listic­expi­ali­docious +
flocci­naucini­hilipil­ification +
anti­dis­establish­ment­arian­ism;
</code></pre>
<p>No:</p>
<pre><code>complexical = super­cali­fragi­listic­expi­ali­docious
+ flocci­naucini­hilipil­ification
+ anti­dis­establish­ment­arian­ism;
</code></pre></li>
<li><p>Exception: The ternary operator (<code>? :</code>), when broken on multiple lines, should break before each operator. The conditional expression should also be parenthesized:</p>
<pre><code>complexical = (boris === natasha)
? flocci­naucini­hilipil­ification
: anti­dis­establish­ment­arian­ism;
</code></pre></li>
<li><p>Multi-line expressions may optionally be surrounded by parentheses to emphasize the unity of the expression.</p>
<p>Okay:</p>
<pre><code>complexical = (super­cali­fragi­listic­expi­ali­docious +
flocci­naucini­hilipil­ification +
anti­dis­establish­ment­arian­ism);
</code></pre></li>
<li><p>Break lines after spaces when breaking string literals.</p>
<p>Yes:</p>
<pre><code>complexical = 'Lorem ipsum dolor sit amet, consectetur adipiscing ' +
'elit. Sed et auctor magna. Nullam interdum lobortis lectus eu ' +
'tempus. Etiam suscipit vulputate sollicitudin.'
</code></pre>
<p>No:</p>
<pre><code>complexical = 'Lorem ipsum dolor sit amet, consectetur adipiscing' +
' elit. Sed et auctor magna. Nullam interdum lobortis lectus eu' +
' tempus. Etiam suscipit vulputate sollicitudin.'
</code></pre></li>
</ul>
<h3>Brace Positioning</h3>
<p>In general:</p>
<ul>
<li><p>For multi-line blocks, the opening brace should hang off the end of the line.</p></li>
<li><p>The closing brace should be indented to the same level as the line on which the open brace appears.</p></li>
<li><p>Statements that continue after a closing brace should preferably continue on the same line as the closing brace.</p>
<p>Preferred:</p>
<pre><code>if (foo === bar) {
...
} else {
...
}
</code></pre>
<p>Okay:</p>
<pre><code>if (foo === bar) {
...
}
else {
...
}
</code></pre>
<p>No:</p>
<pre><code>if (foo === bar)
{
...
}
else
{
...
}
</code></pre></li>
</ul>
<p>Exception:</p>
<ul>
<li><p>Block statements whose header needs to be broken on multiple lines should move the open brace to the next line, at the same indentation level as the header.</p>
<p>Yes:</p>
<pre><code>if (flocci­naucini­hilipil­ification &amp;&amp;
anti­dis­establish­ment­arian­ism)
{
findDictionary();
}
</code></pre>
<p>No:</p>
<pre><code>if (flocci­naucini­hilipil­ification &amp;&amp;
anti­dis­establish­ment­arian­ism) {
findDictionary();
}
</code></pre>
<p>This creates additional vertical space that visually separates the multi-line header from the first statement in the block. Without this space there would be a false visual grouping between the header lines and the block lines.</p></li>
</ul>
<h3>Whitespace in Expressions and Statements</h3>
<p><em>(Much of this section is shamelessly ripped from <a href="http://www.python.org/dev/peps/pep-0008/">PEP 8</a> and adapted for JavaScript.)</em></p>
<p>Avoid extraneous whitespace in the following situations:</p>
<ul>
<li><p>Immediately inside parentheses and brackets.</p>
<pre><code>Yes: spam(ham[1]);
No: spam( ham[ 1 ] );
</code></pre></li>
<li><p>Immediately before a comma, semicolon, or colon:</p>
<pre><code>Yes: if (x === 4) { console.log(x, y); }
No: if (x === 4) { console.log(x , y) ; }
Yes: spam({ eggs: 2 });
No: spam({ eggs : 2 });
</code></pre></li>
<li><p>Immediately before the open parenthesis that starts the argument list of a function call or function literal:</p>
<pre><code>Yes: spam(1);
No: spam (1);
Yes: exports.createServer = function(port, host) { ... }
No: exports.createServer = function (port, host) { ... }
</code></pre></li>
<li><p>Immediately before the open parenthesis that starts an indexing:</p>
<pre><code>Yes: dict['key'] = list[index];
No: dict ['key'] = list [index];
</code></pre></li>
<li><p>More than one space around an assignment (or other) operator to align it with another.</p>
<p>Alignment is difficult to maintain, particularly in the presence of rename refactorings, and therefore should be avoided in general.</p>
<p>Preferred:</p>
<pre><code>x = 1
y = 2
long_variable = 3
</code></pre>
<p>Avoid:</p>
<pre><code>x = 1
y = 2
long_variable = 3
</code></pre></li>
<li><p>Exception: If there is duplication on each side of multiple assignment operators in a paragraph, alignment may be used to emphasize this.</p>
<p>Okay:</p>
<pre><code>this.specialize = utils.bind(this, this.specialize);
this.apps = utils.bind(this, this.apps);
this.configurations = utils.bind(this, this.configurations);
this.indexes = utils.bind(this, this.indexes);
</code></pre>
<p>Okay:</p>
<pre><code>var Context = require('./context');
var Http = require('./http');
var Async = require('./async');
</code></pre></li>
</ul>
<p>Use single spaces in the following situations:</p>
<ul>
<li><p>After keywords that begin a statement.</p>
<pre><code>Yes: if (i &gt; 0) { ... }
No: if(i &gt; 0) { ... }
Yes: return (i == 42);
No: return(i == 42);
</code></pre></li>
<li><p>Immediately inside braces.</p>
<pre><code>Yes: if (x === 4) { spam({ eggs: 2 }); }
No: if (x === 4) {spam({eggs: 2});}
</code></pre></li>
<li><p>Immediately before the open brace of a function literal:</p>
<pre><code>Yes: var isChoiceView = function(v) {
return v instanceof BaseChoiceView;
};
No: var isChoiceView = function(v){
return v instanceof BaseChoiceView;
};
</code></pre></li>
<li><p>Immediately after commas and colons:</p>
<pre><code>Yes: chart.set({ seriesColors: [0xBF3030, 0xFFE800] });
No: chart.set({ seriesColors:[0xBF3030,0xFFE800] });
</code></pre></li>
<li><p>Around binary operators:</p>
<pre><code>Yes: for (int i = 0; i &lt; cars.length; i++) { ... }
No: for (int i=0; i&lt;cars.length; i++) { ... }
</code></pre>
<p>However if operators with different priorities are used, consider removing whitespace around the operators with the highest precedence. Use your own judgment; however always have the same amount of whitespace on both sides of a binary operator.</p>
<p>Okay:</p>
<pre><code>i = i + 1;
submitted += 1;
x = x*2 - 1;
hypot2 = x*x + y*y;
c = (a+b) * (a-b);
</code></pre></li>
</ul>
<p>Other recommendations:</p>
<ul>
<li><p>Prefer one statement per line.</p>
<p>Yes:</p>
<pre><code>if (foo === 'blah') {
do_blah_thing();
}
do_one();
do_two();
do_three();
</code></pre>
<p>Avoid:</p>
<pre><code>if (foo === 'blah') do_blah_thing();
do_one(); do_two(); do_three();
</code></pre></li>
<li><p>Avoid using parentheses with <code>return</code> statements if the meaning is clear without them:</p>
<pre><code>Yes: return x;
No: return (x);
Okay: return (x == 1); // parens here avoid an initial misreading as "return x"
</code></pre></li>
</ul>
<h3>Trailing Whitespace</h3>
<p>Trailing whitespace on non-blank lines should be avoided. For blank lines it is preferable to leave whitespace up to the logical indentation level. For example:</p>
<pre><code>/**
.*.Marks the specified property as being push-enabled.
.*.
.*.Due to this definition, [...]
.*/
enablePush: function(propName) {
....if (this._isPushEnabled(propName)) {
........// Already push-enabled
........return;
....}
....
....this._pushed_properties.push(propName);
....
....// If binding already exists, push-enable it
....if (this._bindings[propName] !== undefined) {
........this._configureBindingForPush(propName);
....}
},
</code></pre>
<p>Leaving whitespace up to the indentation level makes it easier to insert new code around a blank line and easier to navigate.</p>
<p>Sublime's default settings automatically remove spaces on blank lines that are newly inserted. To avoid this use the setting:</p>
<pre><code>// Do not remove whitespace on newly inserted blank lines when cursor is moved away.
"trim_automatic_white_space": false
</code></pre>
<h3>Avoid autoformatting</h3>
<p>Some editors can be configured to autoformat documents upon save. For example newlines can be automatically appended to the end of files that lack a trailing newline. Or trailing whitespace on lines can automatically be deleted.</p>
<p>Such autoformatting should be avoided. It generates unnecessary extra diffs, making code review more difficult. And the autoformat algorithm may clobber intentional elements of the local style, such as certain uses of trailing whitespace.</p>
<h3>Prefer ' over " for string literals</h3>
<p>String literals should prefer single quotes (') over double quotes ("). It is easier to type and encourages the use of double quotes in output messages which matches English typographical convention. It also makes it easy to use double quotes in SPL searches, which is required by the search language.</p>
<pre><code>Yes: console.warn('SelectView declares "value" property with undefined value.');
No: console.warn("SelectView declares 'value' property with undefined value.");
Yes: context.on('search:start', function(job) { ... });
No: context.on("search:start", function(job) { ... });
</code></pre>
<p>However if you need to use single quotes in a string you can still use double-quoted strings in that case.</p>
<h3>Prefer ===, !== over ==, !=</h3>
<p>The former operators make a strong type comparison, which is the equality semantic you usually want.</p>
<p>Yes:</p>
<pre><code>var a = 0;
if (a === '') {
console.log('winning');
}
</code></pre>
<p>No:</p>
<pre><code>var a = 0;
if (a == '') {
console.log('losing');
}
</code></pre>
<p>Unfortunately there are no strong equivalents for the comparison operators &lt;, &lt;=, >, >=.</p>
<h3>Prefer unquoted keys for object/dictionary literals</h3>
<p>Only quote keys when your interpreter complains. In particular keys that are not alphanumeric or those that collide with JavaScript keywords (ex: <code>default</code>). This makes brevity the default.</p>
<p>Later versions of JavaScript allow keywords to be used directly as unquoted keys but many older browsers will choke.</p>
<p>Yes:</p>
<pre><code>var foobar = {
foo: 1,
bar: 2
}
</code></pre>
<p>No:</p>
<pre><code>var foobar = {
'foo': 1,
'bar': 2
}
</code></pre>
<p>Yes:</p>
<pre><code>var options = {
'charting.legend.placement': 'none', // contains .
disabled: false
'default': undefined, // keyword
seed: undefined,
'type': 'text', // keyword
value: undefined,
};
</code></pre>
<h2>Comments</h2>
<p>Comments that contradict the code are worse than no comments. Always make a priority of keeping the comments up-to-date when the code changes!</p>
<p>Comments should be complete sentences. If a comment is a phrase or sentence, its first word should be capitalized, unless it is an identifier that begins with a lower case letter (never alter the case of identifiers!).</p>
<p>If a comment is short, the period at the end can be omitted. Block comments generally consist of one or more paragraphs built out of complete sentences, and each sentence should end in a period.</p>
<h3>Normal Comments</h3>
<ul>
<li><p>A space should occur between the comment marker and first letter. Capitalize the first letter.</p>
<pre><code>Yes: // Compensate for border
No: //compensate for border
</code></pre></li>
<li><p>Most comments apply to apply to some line, paragraph, method, or other code element that directly follows them. There should be no intervening blank line. <strong>Comments hug elements.</strong></p>
<p>Example:</p>
<pre><code>// Collect changes to be made
var bulkTemplateSets = {};
var bulkTemplateUnsets = [];
var bulkSelfSets = {};
var queueTemplateSet = function(propName, propTemplate) {
bulkTemplateSets[propName] = propTemplate;
};
var queueLiteralSet = function(propName, propValue) {
if (!that._isPushEnabled(propName)) {
// Blank out any preexisting template unless this
// is a pushed property
bulkTemplateUnsets.push(propName);
}
bulkSelfSets[propName] = propValue;
};
_.each(attrs, function(propValue, propName) {
if (propValue instanceof TokenSafeString) {
// Interpret as template.
queueTemplateSet(propName, propValue.value);
} else if (propValue instanceof TokenEscapeString) {
// Interpret as literal value.
queueLiteralSet(propName, propValue.value);
} else if (_.isString(propValue) &amp;&amp; options.tokens) {
// Interpret as template.
queueTemplateSet(propName, propValue);
} else {
// Otherwise interpret as a literal value.
queueLiteralSet(propName, propValue);
}
});
</code></pre>
<p>The first comment in the above example applies to the entire paragraph. The other comments apply to individual lines.</p></li>
<li><p>The trailing period for short single-line comments is optional and often omitted.</p>
<pre><code>// This is a short comment
&lt;code element&gt;
</code></pre></li>
<li><p>Any comment with multiple sentences should have trailing periods.</p>
<pre><code>// This is a longer comment that requires multiple lines.
// And perhaps multiple sentences.
//
// Or even multiple paragraphs.
&lt;code element&gt;
</code></pre>
<p>Or alternatively:</p>
<pre><code>/*
* This is a longer comment that requires multiple lines.
* And perhaps multiple sentences.
*
* Or even multiple paragraphs.
*/
&lt;code element&gt;
</code></pre></li>
<li><p>Comments should be indented to the same level as the element they describe.</p>
<p>Yes:</p>
<pre><code> // Lorem ipsum sig dilor amit
makeItSo();
</code></pre>
<p>No:</p>
<pre><code>// Lorem ipsum sig dilor amit
makeItSo();
</code></pre></li>
</ul>
<h3>Flag Comments</h3>
<p>Some comments are designed to be easily identifiable by global searches and tools. These take the format:</p>
<pre><code>// FLAGNAME: This is a short comment
&lt;code element&gt;
</code></pre>
<p>Or in longer form:</p>
<pre><code>// FLAGNAME: This is a longer comment that requires multiple lines.
// And perhaps multiple sentences.
&lt;code element&gt;
</code></pre>
<p>Flag comments don't generally use the <code>/*</code> variety of long comments.</p>
<h4>TODO Comments</h4>
<p>A TODO is a flag comment that indicates further action is needed on a set of code.</p>
<p>These comments should generally reference a JIRA that tracks the work identified by the TODO. That way the work isn't forgotten.</p>
<p>For example:</p>
<pre><code>// TODO: Write test that checks whether get() is symmetric to set(). (DVPL-1610)
</code></pre>
<p>Code reviewers are encouraged to nag for the creation of JIRAs to track unmarked TODOs.</p>
<h4>Other Flag Comments</h4>
<p>There are a few other flag comments that are occasionally used:</p>
<ul>
<li><code>FIXME</code> - Flags code that shouldn't be committed. Recognized by some people's local checkin guards.</li>
<li><code>HACK</code> - Warns maintainers of known brittleness.</li>
<li><code>NOTE</code> - Warns maintainers of other issues.</li>
<li><code>XXX</code> - Same as <code>HACK</code>. Not as clear. Less politically correct. Avoid.</li>
</ul>
<h3>Documentation Comments</h3>
<h4>Syntax</h4>
<p><span style="color: red;">
<strong>Documentation comments have not been fully standardized</strong> since no API documentation generator is used presently. If such a generator was used, its accepted syntax would dictate the syntax used by doc comments.
</span></p>
<p>In general:</p>
<ul>
<li><p>Use JavaDoc style comments, as this is the syntax that has inspired the syntax of most documentation generator tools, and thus will likely to be supported by whatever tool is chosen in the future.</p></li>
<li><p>All public classes, methods, and other members of the public API should have a documentation comment.</p></li>
<li><p>Documentation comments are marked with <code>/**</code> and <code>*/</code>. Notice the extra leading <code>*</code> that distinguishes doc comments from regular multi-line comments.</p></li>
<li><p>File-level comments, such as a comment block at the top of a file, are not generally used. Class comments are typically used instead.</p></li>
</ul>
<p>When documenting a class or method:</p>
<ul>
<li><p>The leading sentence or paragraph should serve as the primary summary.</p></li>
<li><p>Additional paragraphs may be added to provide more detail. Notice that <code>&lt;p&gt;</code> isn't used between different paragraphs here.</p></li>
</ul>
<p>When documenting a class:</p>
<ul>
<li><p>A class declaration should always assign to a new variable whose name matches the class name.</p>
<pre><code>/**
* A model with built-in data binding support.
*
* The set() and get() methods support a `tokens` boolean option.
* When true, the set is interpreted as a template
* that may contain token escapes such as "$indexName$".
*
* If a property is set to a template, it is defined as a computed property
* based on the referenced tokens and kept up-to-date when those token
* values change.
*/
var TokenAwareModel = BaseModel.extend({
});
</code></pre></li>
</ul>
<p>When documenting a method:</p>
<ul>
<li><p>The <code>@param</code> block tag is used to describe method parameters.</p></li>
<li><p>The <code>@return</code> block tag (not shown here) is used to describe the return value.</p></li>
<li><p>Dictionary-valued parameters used to pass keyword arguments may document the specific keywords as if it were a <code>dictParamName.keywordArgName</code> parameter.</p>
<pre><code>/**
* Creates a new token-aware model.
*
* @param attributes Initial attributes of this model.
* @param options.tokenNamespace
* Name of the namespace to use when resolving
* unqualified token references such as $token$.
* Defaults to 'default'.
* @param options.retainUnmatchedTokens
* If true, returns the computed value of
* the specified property, but with unresolved
* tokens retained in the output string.
* For example the template '$first$ $last$'
* would resolve to 'Bob $last$' if only
* the $first$ token was defined
* (and it was 'Bob').
* @param options.tokenEscaper
* Escaping function that will be used to escape
* all expanded token values.
* @param options.* Interpreted the same way as in TokenAwareModel.set().
*/
constructor: function(attributes, options) {
...
},
</code></pre></li>
</ul>
<p>Private API may be documented in a separate multi-line comment directly below the public doc comment.</p>
<p>If there are also implementation comments that apply to the same element, the implementation comments appear last.</p>
<pre><code>/**
* Creates a new token-aware model.
*
* @param attributes Initial attributes of this model.
* ... ...
*/
/*
* Private API:
*
* @param options._tokenRegistry
* An alternate token registry to use other than
* `mvc.Components`. For use by tests only.
*/
// NOTE: Must override constructor() and not initialize()
// because this._templates and listeners need to be
// in place before the first (non-empty) call to set(),
// which the default constructor() does by default.
constructor: function(attributes, options) {
...
}
</code></pre>
<h4>Style</h4>
<p><em>(Much of this is adapted from the <a href="http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html#styleguide">JavaDoc style guide</a>.)</em></p>
<ul>
<li><p>OK to use phrases instead of complete sentences, in the interests of brevity.</p>
<p>This holds especially in the initial summary and in @param tag descriptions.</p></li>
<li><p>Use 3rd person (descriptive) not 2nd person (prescriptive).</p>
<pre><code>Yes: Gets the label.
No: Get the label.
</code></pre></li>
<li><p>Method descriptions begin with a verb phrase.</p>
<pre><code>Yes: Gets the label of this button.
No: This method gets the label of this button.
</code></pre></li>
<li><p>Class and field descriptions can omit the subject and simply state the object.</p>
<pre><code>Yes: A button label.
No: This field is a button label.
</code></pre></li>
<li><p>Use "this" instead of "the" when referring to an object created from the current class.</p>
<pre><code>Yes: Gets the toolkit for this component.
No: Gets the toolkit for the component.
</code></pre></li>
<li><p>Add description beyond the API name.</p>
<p>If the doc comment merely repeats the API name in sentence form, it is not providing more information. The ideal comment goes beyond those words and should always reward you with some bit of information that was not immediately obvious from the API name.</p>
<p>Any documentation that doesn't provide additional information should be omitted,
particularly for @param and @return block tags.</p>
<p>Yes:</p>
<pre><code>/**
* Sets the tool tip text, which displays when the cursor
* lingers over this component.
*/
setToolTipText: function(text) { ... },
</code></pre>
<p>No:</p>
<pre><code>/**
* Sets the tool tip text.
*
* @param text The text of the tool tip.
*/
setToolTipText: function(text) { ... },
</code></pre></li>
</ul>
<p>Block tags should be used in the following order:</p>
<ul>
<li>@param (methods and constructors only)</li>
<li>@return (methods only)</li>
<li>@deprecated</li>
</ul>
<h3>Non-Element Comments</h3>
<p>There are a few cases where a comment doesn't apply directly to an element:</p>
<h4>Empty Block Explanations</h4>
<p>In cases where an empty block would be unusual, a comment can be inserted to explain why the block is blank.</p>
<p>For example, in Java:</p>
<pre><code>FileReader fileIn = new FileReader("input.txt", "UTF-8");
try {
...
} finally {
try {
fileIn.close();
} catch (IOException e) {
// ignore
}
}
</code></pre>
<h4>Block Termination Explanations</h4>
<pre><code>switch (command) {
case 1:
...
// fallthrough
case 2:
...
break;
default:
break;
}
</code></pre>
<h4>Section Dividers</h4>
<p>Sometimes it is useful to divide the methods in a large class into sections.</p>
<pre><code>var MainWindow = function() {
this.initialize.apply(this, arguments);
};
_.extend(MainWindow, {
// === Init ===
initialize: function() {
...
},
_createContent: function() {
...
},
_createHeader: function() {
...
},
// === Events ===
_onButtonClick: function() {
...
},
_onDispose: function() {
...
}
});
</code></pre>
<p>Most JavaScript files we currently write aren't long enough to justify this kind of sectioning.</p>
<h3>Avoid Commenting Out Code</h3>
<p>Commented out code should usually not be committed. Rather it should be deleted. It can always be restored later through version control if needed.</p>
<p>This guideline avoids leaving in code that becomes out of date, and leaving in temporary code that shouldn't be committed at all.</p>
<p>If you need to commit commented out code, it is recommended that there be no space between the comment marker and the related code. This gives it a distinct appearance from normal comments which use a single intervening space.</p>
<p>It is also recommended that an additional comment be provided that explains why the commented out code shouldn't be removed.</p>
<pre><code>new TextInputView({
id: "rightField",
el: $("#rightField"),
// NOTE: If this default is specified, it will override the
// default defined in the view above.
//default: "$1.00 $2.00", // literal value
value: mvc.tokenSafe("$fullName$")
}).render();
</code></pre>
<p>In this example the commented out code is left in as an explanation for readers. Since this example comes from example code shipped with the Web Framework, there definitely will be future readers.</p>
<h2>Naming Conventions</h2>
<p>In general:</p>
<ul>
<li>Files: <code>myclass.js</code>
<ul>
<li>All lower case.</li>
</ul>
</li>
<li>Packages: <code>splunkjs/mvc/myclass.js</code>
<ul>
<li>Short. All lower case.</li>
</ul>
</li>
<li>Classes: <code>MyClass</code>
<ul>
<li>Upper camel case.</li>
</ul>
</li>
<li>Methods, Fields, Variables: <code>myVar</code>
<ul>
<li>Lower camel case.</li>
</ul>
</li>
<li>Constants: <code>MY_CONSTANT</code>
<ul>
<li>Loud case.</li>
</ul>
</li>
</ul>
<p>Additionally:</p>
<ul>
<li><p>A public class should be declared in a file whose name matches the class name, converted to all lowercase to match file naming conventions. So a class called <code>MyClass</code> should reside in file called <code>myclass.js</code>.</p></li>
<li><p>Acronyms are considered words for the purposes of the above conventions. Including 2-letter acronyms.</p>
<pre><code>Yes: HttpUrlConnection
No: HTTPURLConnection
</code></pre>
<pre><code>Yes: getId
No: getID
</code></pre></li>
<li><p>Members that are not part of the public API should be named with a leading underscore. Especially when naming methods and fields.</p>
<p>When in doubt, a method should be considered private API and therefore named with a leading underscore.</p>
<p>A class's private methods should not be invoked externally unless there is comment in the class describing the exception. For example unit tests occasionally require access to private API, although this is discouraged when possible.</p></li>
</ul>
<h2>Classes</h2>
<p>When a file consists of a single require-compatible class it should be defined with imports at the top, followed by the exported class, optional private classes, and top-level code at the end.</p>
<pre><code>define(function(require, exports, module) {
var $ = require('jquery'); // (1) imports
var _ = require('underscore');
var Backbone = require('backbone');
var console = require('util/console');
var mvc = require('./mvc');
var Settings = require('./settings');
var BaseSplunkView = Backbone.View.extend({ // (2) top-level class
...
});
return BaseSplunkView; // (3) top-level code
});
</code></pre>
<ul>
<li><p>Imports should be sorted alphabetically by the variable name, case-insensitive.</p>
<p>Thus symbolically-named imports like <code>$</code> and <code>_</code> usually precede alphabetically-named imports. And <code>$</code> occurs before <code>_</code> when being pedantic.</p>
<p>This makes it easy to see whether a prospective import has already been inserted. And alphabetical ordering is also easy to reformat with tools if needed.</p></li>
<li><p>Classes may be defined in many ways depending on what object-orientation library or manual approach is being used.</p>
<p>Here are a few examples if your environment is not opinionated:</p>
<p><strong>Normal Class:</strong></p>
<p>Underscore:</p>
<pre><code>var _ = require('underscore');
var BaseSplunkView = function() {
BaseSplunkView.prototype.initialize.apply(this, arguments);
};
_.extend(BaseSplunkView.prototype, {
initialize: function(...) { ... },
... methods ...
});
</code></pre>
<p>Vanilla JS:</p>
<pre><code>var BaseSplunkView = function() {
BaseSplunkView.prototype.initialize.apply(this, arguments);
};
BaseSplunkView.prototype.initialize = function(...) { ... };
... methods ...
</code></pre>
<p><strong>Inheriting from a Normal Class:</strong></p>
<p>Underscore:</p>
<pre><code>var _ = require('underscore');
// NOTE: Cannot be detected as instanceof BaseSplunkView
// using this implementation strategy.
var DerivedSplunkView = function() {
DerivedSplunkView.prototype.initialize.apply(this, arguments);
};
_.extend(DerivedSplunkView.prototype, BaseSplunkView.prototype, {
initialize: function(...) {
BaseSplunkView.prototype.initialize.apply(this, arguments);
... constructor body ...
},
... methods ...
});
</code></pre>
<p>Vanilla JS:</p>
<p><em>Please don't implement inheritance manually. Use a library.</em></p>
<p><strong>Inheriting from a Backbone Class:</strong></p>
<pre><code>var Backbone = require('backbone');
var DerivedSplunkView = Backbone.View.extend({
initialize: function(...) { ... },
... methods ...
});
</code></pre>
<p>Many other class libraries have a similar syntax, perhaps varying the name of the constructor method.</p>
<p><strong>Static Class:</strong></p>
<pre><code>var TokenUtils = {
... methods ...
};
</code></pre></li>
</ul>
<h2>Other Recommendations</h2>
<ul>
<li><p>Only one var should be declared per var statement.</p>
<p>This makes new vars easier to recognize and insert.</p>
<p>Yes:</p>
<pre><code>var ichi = 1;
var ni = 2;
var san = 3;
</code></pre>
<p>No:</p>
<pre><code>var ichi = 1,
ni = 2,
san = 3;
</code></pre>
<p>No:</p>
<pre><code>var ichi = 1, ni = 2, san = 3;
</code></pre>
<p>Definitely not:</p>
<pre><code>var ichi = 1; var ni = 2; var san = 3;
</code></pre></li>
<li><p>Variables should have minimal (logical) scope. Therefore they should be declared as close to their first use as possible.</p>
<p>Thus variables used in a single paragraph should be declared in that paragraph.
Variables used in multiple paragraphs should be declared in a (potentially separate)
paragraph immediately preceding the first reading paragraph.</p>
<p>Yes:</p>
<pre><code>var ship = dock.get('USS Enterprise');
ship.setRedAlert(true);
var desiredEtaInSeconds = 15 * 60; // 15 min
var distanceToTargetInLightSeconds = 1000;
ship.setWarpFactor(distanceToTargetInLightSeconds / desiredEtaInSeconds);
</code></pre></li>
<li><p>Trailing commas break IE. Don't use them.</p>
<pre><code>Yes: var items = [
item1,
item2
];
No: var items = [
item1,
item2,
];
</code></pre>
<p>(If they didn't break IE, I'd recommend their use because it makes inserting new elements easier.)</p></li>
<li><p>Avoid using block statements (such as <code>if</code> or <code>while</code>) without braces:</p>
<p>Yes:</p>
<pre><code>if (!this.data) {
return;
}
</code></pre>
<p>No:</p>
<pre><code>if (!this.data)
return;
</code></pre></li>
<li><p>Avoid having multiple statements on the same line.</p>
<p>Yes:</p>
<pre><code>if (!this.data) {
return;
}
</code></pre>
<p>No:</p>
<pre><code>if (!this.data) { return; }
</code></pre></li>
<li><p>Avoid monkeypatching classes or individual objects by altering prototypes. Especially not native classes. Use the Foreign Method pattern instead.</p>
<p>Monkeypatching introduces ordering dependencies, makes it difficult to determine where custom grafted functions came from, and can cause conflicts with other JavaScript libraries.</p>
<p>Yes:</p>
<pre><code>var empty = function(self) {
return (self.length === 0);
}
var a = [];
if (empty(a)) {
console.log('winning');
}
</code></pre>
<p>No:</p>
<pre><code>Array.prototype.empty = function() {
return (this.length === 0);
}
var a = [];
if (a.empty()) {
console.log('losing');
}
</code></pre></li>
<li><p>Return function values as early as possible to avoid deep nesting of if-statements.</p>
<p>And do not contort code to follow the old "single function, single return" guideline from C.</p>
<p>Yes:</p>
<pre><code>var isPercentage = function(val) {
if (val &lt; 0) {
return false;
}
if (val &gt; 100) {
return false;
}
return true;
}
</code></pre>
<p>No:</p>
<pre><code>var isPercentage = function(val) {
if (val &lt; 0) {
return false;
} else {
if (val &gt; 100) {
return false;
} else {
return true;
}
}
}
</code></pre>
<p>Definitely not:</p>
<pre><code>var isPercentage = function(val) {
var result;
if (val &lt; 0) {
result = false;
} else {
if (val &gt; 100) {
result = false;
} else {
result = true;
}
}
return result;
}
</code></pre></li>
<li><p>Prefer <code>undefined</code> over <code>void(0)</code> or <code>_.isUndefined(...)</code>. It is more straightforward.</p>
<p>Preferred:</p>
<pre><code>if (x === undefined) { ... }
</code></pre>
<p>Avoid:</p>
<pre><code>if (_.isUndefined(x)) { ... }
</code></pre>
<p>Definitely not:</p>
<pre><code>if (x === void(0)) { ... }
</code></pre></li>
<li><p>Do not use <code>eval</code>.</p></li>
</ul>
</body>
</html>

JavaScript Code Conventions - 0.3.1

This document describes the code conventions used in JavaScript code written by the Splunk Seattle office, notably in the Web Framework and JavaScript SDK projects. It is intended to guide developers to a common style and reduce the number of style-related nits received in code review comments.

Although this is a JavaScript guide, a number of recommendations here are language independent.

Meta

Be consistent

When working in an existing source file that is consistently styled in a particular way, be consistent with local style. This takes precedence over all other guidelines.

If local style is inconsistent but a dominant local style prevails, use that style. If there is no clear dominant style then insert new code using the style of this guide.

Guidelines, not strict rules

This set of conventions is a set of guidelines, not absolute rules.

For example if a guideline exists for a particular rationale that does not apply in a particular situation, the guideline may not apply.

Other guidelines with no particular rationale exist mainly for consistency (such as the choice of 4-space vs. 2-space indentation). By analogy with the real world, there's no particular reason why the cold water faucet is on the right in the USA, although it is very useful that it is consistently there. Otherwise you might burn yourself should a rouge sink-maker put the hot water on the right instead.

If a guideline needs to be ignored in a particular case, consider adding a comment to explain why, usually in the form of a NOTE flag comment. (Flag comments are described below.)

Conceptual Layout

Top-to-bottom; Logical Grouping

  • Code is easiest to read in a top-to-bottom fashion, as one would read a book. Therefore top-level functions should be arranged before lower-level functions. Functions should call downward.

    Additionally logically related methods should be grouped together. By contrast the use of unmeaningful orderings like a strict alphabetical ordering is discouraged.

    Yes:

    var BaseSplunkView = Backbone.View.extend({
        constructor: function(options, settingsOptions) {
            ...
            this.configure();
            this.initialize();
            ...
        },
        
        configure: function() { ... },
        
        initialize: function() { ... },
        
        bindToComponentSetting: function(settingName, fn, fnContext) { ... },
    
        bindToComponent: function(id, fn, fnContext) { ... },
    
        unbindFromComponent: function(id, fn, fnContext) { ... },
        
        ...
    });
    

    Above, the methods are conceptually bound together in the structure:

    • constructor
      • configure
      • initialize
    • (binding methods, commonly called in initialize)
      • bindToComponentSetting
      • bindToComponent
      • unbindFromComponent
    • (other methods that apply after initialization)
      • ...

  • Exception: There are some technical circumstances where calling upward is required, such as the final return statement in an AMD-style define clause. These circumstances should be considered the exception rather than the norm.

    Yes:

    define(function(require, exports, module) {
        var _ = require('underscore');
        ...
    
        var BaseSplunkView = Backbone.View.extend({
            ...
        });
        
        return BaseSplunkView;
    });
    
    
  • Exception: Many developers are not familar with the details of hoisting in JavaScript, so it should be considered a technical limitation that nested local functions must call up.

    Yes:

    function formatMessageElement(message) {
        // NOTE: Does not handle characters outside the Basic Multilingual Plane.
        var isValidChar = function(charCode) {
            return charCode >= 32;
        };
        
        var isValidString = function(s) {
            for (var i = 0; i < s.length; i++) {
                if (!isValidChar(s.charCodeAt(i))) {
                    return false;
                }
            }
            return true;
        };
        
        return (isValidString(message))
            ? "<message>" + message + "</message>"
            : "<message/>";
    }
    

    No:

    function formatMessageElement(message) {
        return (isValidString(message))
            ? "<message>" + message + "</message>"
            : "<message/>";
        
        function isValidString(s) {
            for (var i = 0; i < s.length; i++) {
                if (!isValidChar(s.charCodeAt(i))) {
                    return false;
                }
            }
            return true;
        };
        
        // NOTE: Does not handle characters outside the Basic Multilingual Plane.
        function isValidChar(charCode) {
            return charCode >= 32;
        };
    }
    
  • However if you were to pull the nested local functions out to be siblings instead, top-to-bottom ordering still applies:

    Yes:

    function formatMessageElement(message) {
        return (isValidString(message))
            ? "<message>" + message + "</message>"
            : "<message/>";
    }
    
    function isValidString(s) {
        for (var i = 0; i < s.length; i++) {
            if (!isValidChar(s.charCodeAt(i))) {
                return false;
            }
        }
        return true;
    };
    
    // NOTE: Does not handle characters outside the Basic Multilingual Plane.
    function isValidChar(charCode) {
        return charCode >= 32;
    };
    

Use paragraphs to group common functionality

Individual methods and functions should be organized such that each does one thing and does it well.

Nevertheless a method may perform multiple discrete low-level actions when performing a higher-level task. Actions that takes a few lines of code are good to visually separate from adjacent actions with a empty line. One-line actions do not benefit from separation.

Example:

_configureBindingForPush: function(propName) {
    var binding = this._bindings[propName];
    if (!TokenUtils.isToken(binding.template)) {
        // This property's template is not presently bound to a
        // single token. Therefore there is no token that can
        // be pushed to yet.
        return;
    }
    
    // Forward value changes to solitary token in template
    var that = this;
    var listener = function(model, newValue, options) {
        that._pushPropertyValue(propName);
    };
    this.listenTo(this, 'change:' + propName, listener);
    
    // Save listener for later removal
    listener.dispose = function() {
        that.stopListening(that, 'change:' + propName, listener);
    };
    binding._listeners.push(listener);
},

Many of the paragraphs in this example also have leading comments, which are useful.

Paragraphs that grow too large are good to extract to private methods.

Line Spacing

Methods separated by one blank line

Adjacent methods, classes, and paragraphs should be separated by one blank line to make them easy to distinguish.

Yes:

var TextInputView = BaseInputView.extend({
    initialize: function() {
        ...
    },
    
    getValue: function() {
        ...
    },
    
    setValue: function(value) {
        ...
    }
});

No:

var TextInputView = BaseInputView.extend({
    initialize: function() {
        ...
    },
    getValue: function() {
        ...
    },
    setValue: function(value) {
        ...
    }
});

Minimize extra blank lines

  • There should not be blank lines at the beginning or end of a block.

    Yes:

    var TextInputView = BaseInputView.extend({
        initialize: function() {
            ...
        }
    });
    

    No:

    var TextInputView = BaseInputView.extend({
        
        initialize: function() {
            ...
        }
        
    });
    
  • There should never be two blank lines in a row.

  • There should not be extra blank lines at the beginning of a file. An extra blank line may exist at the end of a file to accomodate environments that expect a trailing newline for every file.

Physical Layout

Indentation

  • Spaces only. No tabs.

  • JavaScript files use 4-column indentation.

  • JSON files use 4-column indentation.

  • HTML may use either 4-column or 2-column indentation. Prefer 4-column indentation if there is no local preference to allow easy mixing of standalone JS with HTML that includes embedded JS.

Tabs are avoided because they are interpreted differently by lots of different tools. In particular some tools may assume a tab width of 8 while others may assume a width of 4. For consistent behavior everywhere, only spaces are a safe bet.

To make this guideline easy to follow, please configure your editor to automatically convert inserted tabs to spaces. But preserve the tab-ness of any tabs that already exist in the file. For Sublime:

// When tab key is pressed, insert spaces. Leave preexisting tabs alone.
"translate_tabs_to_spaces": true

Note that a few file types require real tabs in certain circumstances. Makefiles in particular.

Line Length

  • Avoid lines longer than 80 columns.

  • Strongly avoid lines longer than 100 columns unless there is a specific technical reason.

Long lines make it difficult to have many documents onscreen at once. Typographically they are hard to read. They also interfere with Crucible side-by-side diffs because each side of the diff has its min-width set to the length of the longest line in the file under review.

To make this guideline easy to follow, please configure your editor to display a vertical rule at the 80 and 100 column positions. For Sublime:

"rulers": [80, 100]

Breaking Long Lines

  • When breaking a statement on multiple physical lines, the second and subsequent lines should be indented a single level greater than the first line.

  • Break lines after operators, not before.

    Yes:

    complexical = super­cali­fragi­listic­expi­ali­docious + 
        flocci­naucini­hilipil­ification +
        anti­dis­establish­ment­arian­ism;
    

    No:

    complexical = super­cali­fragi­listic­expi­ali­docious
        + flocci­naucini­hilipil­ification
        + anti­dis­establish­ment­arian­ism;
    
  • Exception: The ternary operator (? :), when broken on multiple lines, should break before each operator. The conditional expression should also be parenthesized:

    complexical = (boris === natasha)
        ? flocci­naucini­hilipil­ification
        : anti­dis­establish­ment­arian­ism;
    
  • Multi-line expressions may optionally be surrounded by parentheses to emphasize the unity of the expression.

    Okay:

    complexical = (super­cali­fragi­listic­expi­ali­docious + 
        flocci­naucini­hilipil­ification +
        anti­dis­establish­ment­arian­ism);
    
  • Break lines after spaces when breaking string literals.

    Yes:

    complexical = 'Lorem ipsum dolor sit amet, consectetur adipiscing ' +
        'elit. Sed et auctor magna. Nullam interdum lobortis lectus eu ' +
        'tempus. Etiam suscipit vulputate sollicitudin.'
    

    No:

    complexical = 'Lorem ipsum dolor sit amet, consectetur adipiscing' +
        ' elit. Sed et auctor magna. Nullam interdum lobortis lectus eu' +
        ' tempus. Etiam suscipit vulputate sollicitudin.'
    

Brace Positioning

In general:

  • For multi-line blocks, the opening brace should hang off the end of the line.

  • The closing brace should be indented to the same level as the line on which the open brace appears.

  • Statements that continue after a closing brace should preferably continue on the same line as the closing brace.

    Preferred:

    if (foo === bar) {
        ...
    } else {
        ...
    }
    

    Okay:

    if (foo === bar) {
        ...
    }
    else {
        ...
    }
    

    No:

    if (foo === bar)
    {
        ...
    }
    else
    {
        ...
    }
    

Exception:

  • Block statements whose header needs to be broken on multiple lines should move the open brace to the next line, at the same indentation level as the header.

    Yes:

    if (flocci­naucini­hilipil­ification &&
        anti­dis­establish­ment­arian­ism)
    {
        findDictionary();
    }
    

    No:

    if (flocci­naucini­hilipil­ification &&
        anti­dis­establish­ment­arian­ism) {
        findDictionary();
    }
    

    This creates additional vertical space that visually separates the multi-line header from the first statement in the block. Without this space there would be a false visual grouping between the header lines and the block lines.

Whitespace in Expressions and Statements

(Much of this section is shamelessly ripped from PEP 8 and adapted for JavaScript.)

Avoid extraneous whitespace in the following situations:

  • Immediately inside parentheses and brackets.

    Yes: spam(ham[1]);
    No:  spam( ham[ 1 ] );
    
  • Immediately before a comma, semicolon, or colon:

    Yes: if (x === 4) { console.log(x, y); }
    No:  if (x === 4) { console.log(x , y) ; }
    
    Yes: spam({ eggs: 2 });
    No:  spam({ eggs : 2 });
    
  • Immediately before the open parenthesis that starts the argument list of a function call or function literal:

    Yes: spam(1);
    No:  spam (1);
    
    Yes: exports.createServer = function(port, host) { ... }
    No:  exports.createServer = function (port, host) { ... }
    
  • Immediately before the open parenthesis that starts an indexing:

    Yes: dict['key'] = list[index];
    No:  dict ['key'] = list [index];
    
  • More than one space around an assignment (or other) operator to align it with another.

    Alignment is difficult to maintain, particularly in the presence of rename refactorings, and therefore should be avoided in general.

    Preferred:

    x = 1
    y = 2
    long_variable = 3
    

    Avoid:

    x             = 1
    y             = 2
    long_variable = 3
    
  • Exception: If there is duplication on each side of multiple assignment operators in a paragraph, alignment may be used to emphasize this.

    Okay:

    this.specialize     = utils.bind(this, this.specialize);
    this.apps           = utils.bind(this, this.apps);
    this.configurations = utils.bind(this, this.configurations);
    this.indexes        = utils.bind(this, this.indexes);
    

    Okay:

    var Context = require('./context');
    var Http    = require('./http');
    var Async   = require('./async');
    

Use single spaces in the following situations:

  • After keywords that begin a statement.

    Yes: if (i > 0) { ... }
    No:  if(i > 0) { ... }
    
    Yes: return (i == 42);
    No:  return(i == 42);
    
  • Immediately inside braces.

    Yes: if (x === 4) { spam({ eggs: 2 }); }
    No:  if (x === 4) {spam({eggs: 2});}
    
  • Immediately before the open brace of a function literal:

    Yes: var isChoiceView = function(v) { 
             return v instanceof BaseChoiceView;
         };
    
    No:  var isChoiceView = function(v){ 
             return v instanceof BaseChoiceView;
         };
    
  • Immediately after commas and colons:

    Yes: chart.set({ seriesColors: [0xBF3030, 0xFFE800] });
    No:  chart.set({ seriesColors:[0xBF3030,0xFFE800] });
    
  • Around binary operators:

    Yes: for (int i = 0; i < cars.length; i++) { ... }
    No:  for (int i=0; i<cars.length; i++) { ... }
    

    However if operators with different priorities are used, consider removing whitespace around the operators with the highest precedence. Use your own judgment; however always have the same amount of whitespace on both sides of a binary operator.

    Okay:

    i = i + 1;
    submitted += 1;
    x = x*2 - 1;
    hypot2 = x*x + y*y;
    c = (a+b) * (a-b);
    

Other recommendations:

  • Prefer one statement per line.

    Yes:

    if (foo === 'blah') {
        do_blah_thing();
    }
    do_one();
    do_two();
    do_three();
    

    Avoid:

    if (foo === 'blah') do_blah_thing();
    do_one(); do_two(); do_three();
    
  • Avoid using parentheses with return statements if the meaning is clear without them:

    Yes:  return x;
    No:   return (x);
    Okay: return (x == 1);  // parens here avoid an initial misreading as "return x"
    

Trailing Whitespace

Trailing whitespace on non-blank lines should be avoided. For blank lines it is preferable to leave whitespace up to the logical indentation level. For example:

/**
.*.Marks the specified property as being push-enabled.
.*.
.*.Due to this definition, [...]
.*/
enablePush: function(propName) {
....if (this._isPushEnabled(propName)) {
........// Already push-enabled
........return;
....}
....
....this._pushed_properties.push(propName);
....
....// If binding already exists, push-enable it
....if (this._bindings[propName] !== undefined) {
........this._configureBindingForPush(propName);
....}
},

Leaving whitespace up to the indentation level makes it easier to insert new code around a blank line and easier to navigate.

Sublime's default settings automatically remove spaces on blank lines that are newly inserted. To avoid this use the setting:

// Do not remove whitespace on newly inserted blank lines when cursor is moved away.
"trim_automatic_white_space": false

Avoid autoformatting

Some editors can be configured to autoformat documents upon save. For example newlines can be automatically appended to the end of files that lack a trailing newline. Or trailing whitespace on lines can automatically be deleted.

Such autoformatting should be avoided. It generates unnecessary extra diffs, making code review more difficult. And the autoformat algorithm may clobber intentional elements of the local style, such as certain uses of trailing whitespace.

Prefer ' over " for string literals

String literals should prefer single quotes (') over double quotes ("). It is easier to type and encourages the use of double quotes in output messages which matches English typographical convention. It also makes it easy to use double quotes in SPL searches, which is required by the search language.

Yes: console.warn('SelectView declares "value" property with undefined value.');
No:  console.warn("SelectView declares 'value' property with undefined value.");

Yes: context.on('search:start', function(job) { ... });
No:  context.on("search:start", function(job) { ... });

However if you need to use single quotes in a string you can still use double-quoted strings in that case.

Prefer ===, !== over ==, !=

The former operators make a strong type comparison, which is the equality semantic you usually want.

Yes:

var a = 0;
if (a === '') {
    console.log('winning');
}

No:

var a = 0;
if (a == '') {
    console.log('losing');
}

Unfortunately there are no strong equivalents for the comparison operators <, <=, >, >=.

Prefer unquoted keys for object/dictionary literals

Only quote keys when your interpreter complains. In particular keys that are not alphanumeric or those that collide with JavaScript keywords (ex: default). This makes brevity the default.

Later versions of JavaScript allow keywords to be used directly as unquoted keys but many older browsers will choke.

Yes:

var foobar = {
    foo: 1,
    bar: 2
}

No:

var foobar = {
    'foo': 1,
    'bar': 2
}

Yes:

var options = {
    'charting.legend.placement': 'none',    // contains .
    disabled: false
    'default': undefined,                   // keyword
    seed: undefined,
    'type': 'text',                         // keyword
    value: undefined,
};

Comments

Comments that contradict the code are worse than no comments. Always make a priority of keeping the comments up-to-date when the code changes!

Comments should be complete sentences. If a comment is a phrase or sentence, its first word should be capitalized, unless it is an identifier that begins with a lower case letter (never alter the case of identifiers!).

If a comment is short, the period at the end can be omitted. Block comments generally consist of one or more paragraphs built out of complete sentences, and each sentence should end in a period.

Normal Comments

  • A space should occur between the comment marker and first letter. Capitalize the first letter.

    Yes: // Compensate for border
    No:  //compensate for border
    
  • Most comments apply to apply to some line, paragraph, method, or other code element that directly follows them. There should be no intervening blank line. Comments hug elements.

    Example:

    // Collect changes to be made
    var bulkTemplateSets = {};
    var bulkTemplateUnsets = [];
    var bulkSelfSets = {};
    var queueTemplateSet = function(propName, propTemplate) {
        bulkTemplateSets[propName] = propTemplate;
    };
    var queueLiteralSet = function(propName, propValue) {
        if (!that._isPushEnabled(propName)) {
            // Blank out any preexisting template unless this
            // is a pushed property
            bulkTemplateUnsets.push(propName);
        }
        bulkSelfSets[propName] = propValue;
    };
    _.each(attrs, function(propValue, propName) {
        if (propValue instanceof TokenSafeString) {
            // Interpret as template.
            queueTemplateSet(propName, propValue.value);
        } else if (propValue instanceof TokenEscapeString) {
            // Interpret as literal value.
            queueLiteralSet(propName, propValue.value);
        } else if (_.isString(propValue) && options.tokens) {
            // Interpret as template.
            queueTemplateSet(propName, propValue);
        } else {
            // Otherwise interpret as a literal value.
            queueLiteralSet(propName, propValue);
        }
    });
    

    The first comment in the above example applies to the entire paragraph. The other comments apply to individual lines.

  • The trailing period for short single-line comments is optional and often omitted.

    // This is a short comment
    <code element>
    
  • Any comment with multiple sentences should have trailing periods.

    // This is a longer comment that requires multiple lines.
    // And perhaps multiple sentences.
    // 
    // Or even multiple paragraphs.
    <code element>
    

    Or alternatively:

    /*
     * This is a longer comment that requires multiple lines.
     * And perhaps multiple sentences.
     * 
     * Or even multiple paragraphs.
     */
    <code element>
    
  • Comments should be indented to the same level as the element they describe.

    Yes:

            // Lorem ipsum sig dilor amit
            makeItSo();
    

    No:

    // Lorem ipsum sig dilor amit
            makeItSo();
    

Flag Comments

Some comments are designed to be easily identifiable by global searches and tools. These take the format:

// FLAGNAME: This is a short comment
<code element>

Or in longer form:

// FLAGNAME: This is a longer comment that requires multiple lines.
//           And perhaps multiple sentences.
<code element>

Flag comments don't generally use the /* variety of long comments.

TODO Comments

A TODO is a flag comment that indicates further action is needed on a set of code.

These comments should generally reference a JIRA that tracks the work identified by the TODO. That way the work isn't forgotten.

For example:

// TODO: Write test that checks whether get() is symmetric to set(). (DVPL-1610)

Code reviewers are encouraged to nag for the creation of JIRAs to track unmarked TODOs.

Other Flag Comments

There are a few other flag comments that are occasionally used:

  • FIXME - Flags code that shouldn't be committed. Recognized by some people's local checkin guards.
  • HACK - Warns maintainers of known brittleness.
  • NOTE - Warns maintainers of other issues.
  • XXX - Same as HACK. Not as clear. Less politically correct. Avoid.

Documentation Comments

Syntax

⚠ **Documentation comments have not been fully standardized** since no API documentation generator is used presently. If such a generator was used, its accepted syntax would dictate the syntax used by doc comments.

In general:

  • Use JavaDoc style comments, as this is the syntax that has inspired the syntax of most documentation generator tools, and thus will likely to be supported by whatever tool is chosen in the future.

  • All public classes, methods, and other members of the public API should have a documentation comment.

  • Documentation comments are marked with /** and */. Notice the extra leading * that distinguishes doc comments from regular multi-line comments.

  • File-level comments, such as a comment block at the top of a file, are not generally used. Class comments are typically used instead.

When documenting a class or method:

  • The leading sentence or paragraph should serve as the primary summary.

  • Additional paragraphs may be added to provide more detail. Notice that <p> isn't used between different paragraphs here.

When documenting a class:

  • A class declaration should always assign to a new variable whose name matches the class name.

    /**
     * A model with built-in data binding support.
     * 
     * The set() and get() methods support a `tokens` boolean option.
     * When true, the set is interpreted as a template
     * that may contain token escapes such as "$indexName$".
     * 
     * If a property is set to a template, it is defined as a computed property
     * based on the referenced tokens and kept up-to-date when those token
     * values change.
     */
    var TokenAwareModel = BaseModel.extend({
        
    });
    

When documenting a method:

  • The @param block tag is used to describe method parameters.

  • The @return block tag (not shown here) is used to describe the return value.

  • Dictionary-valued parameters used to pass keyword arguments may document the specific keywords as if it were a dictParamName.keywordArgName parameter.

    /**
     * Creates a new token-aware model.
     * 
     * @param attributes    Initial attributes of this model.
     * @param options.tokenNamespace
     *                      Name of the namespace to use when resolving
     *                      unqualified token references such as $token$.
     *                      Defaults to 'default'.
     * @param options.retainUnmatchedTokens
     *                      If true, returns the computed value of
     *                      the specified property, but with unresolved
     *                      tokens retained in the output string.
     *                      For example the template '$first$ $last$'
     *                      would resolve to 'Bob $last$' if only
     *                      the $first$ token was defined
     *                      (and it was 'Bob').
     * @param options.tokenEscaper
     *                      Escaping function that will be used to escape
     *                      all expanded token values.
     * @param options.*     Interpreted the same way as in TokenAwareModel.set().
     */
    constructor: function(attributes, options) {
        ...
    },
    

Private API may be documented in a separate multi-line comment directly below the public doc comment.

If there are also implementation comments that apply to the same element, the implementation comments appear last.

/**
 * Creates a new token-aware model.
 * 
 * @param attributes    Initial attributes of this model.
 * ...                  ...
 */
/*
 * Private API:
 * 
 * @param options._tokenRegistry
 *                      An alternate token registry to use other than
 *                      `mvc.Components`. For use by tests only.
 */
// NOTE: Must override constructor() and not initialize()
//       because this._templates and listeners need to be
//       in place before the first (non-empty) call to set(),
//       which the default constructor() does by default.
constructor: function(attributes, options) {
    ...
}

Style

(Much of this is adapted from the JavaDoc style guide.)

  • OK to use phrases instead of complete sentences, in the interests of brevity.

    This holds especially in the initial summary and in @param tag descriptions.

  • Use 3rd person (descriptive) not 2nd person (prescriptive).

    Yes: Gets the label.
    No:  Get the label.
    
  • Method descriptions begin with a verb phrase.

    Yes: Gets the label of this button.
    No:  This method gets the label of this button.
    
  • Class and field descriptions can omit the subject and simply state the object.

    Yes: A button label.
    No:  This field is a button label.
    
  • Use "this" instead of "the" when referring to an object created from the current class.

    Yes: Gets the toolkit for this component.
    No:  Gets the toolkit for the component.
    
  • Add description beyond the API name.

    If the doc comment merely repeats the API name in sentence form, it is not providing more information. The ideal comment goes beyond those words and should always reward you with some bit of information that was not immediately obvious from the API name.

    Any documentation that doesn't provide additional information should be omitted, particularly for @param and @return block tags.

    Yes:

    /**
     * Sets the tool tip text, which displays when the cursor
     * lingers over this component.
     */
    setToolTipText: function(text) { ... },
    

    No:

    /**
     * Sets the tool tip text.
     * 
     * @param text  The text of the tool tip.
     */
    setToolTipText: function(text) { ... },
    

Block tags should be used in the following order:

  • @param (methods and constructors only)
  • @return (methods only)
  • @deprecated

Non-Element Comments

There are a few cases where a comment doesn't apply directly to an element:

Empty Block Explanations

In cases where an empty block would be unusual, a comment can be inserted to explain why the block is blank.

For example, in Java:

FileReader fileIn = new FileReader("input.txt", "UTF-8");
try {
    ...
} finally {
    try {
        fileIn.close();
    } catch (IOException e) {
        // ignore
    }
}

Block Termination Explanations

switch (command) {
    case 1:
        ...
        // fallthrough
    case 2:
        ...
        break;
    default:
        break;
}

Section Dividers

Sometimes it is useful to divide the methods in a large class into sections.

var MainWindow = function() {
    this.initialize.apply(this, arguments);
};
_.extend(MainWindow, {
    // === Init ===
    
    initialize: function() {
        ...
    },
    
    _createContent: function() {
        ...
    },
    
    _createHeader: function() {
        ...
    },
    
    // === Events ===
    
    _onButtonClick: function() {
        ...
    },
    
    _onDispose: function() {
        ...
    }
});

Most JavaScript files we currently write aren't long enough to justify this kind of sectioning.

Avoid Commenting Out Code

Commented out code should usually not be committed. Rather it should be deleted. It can always be restored later through version control if needed.

This guideline avoids leaving in code that becomes out of date, and leaving in temporary code that shouldn't be committed at all.

If you need to commit commented out code, it is recommended that there be no space between the comment marker and the related code. This gives it a distinct appearance from normal comments which use a single intervening space.

It is also recommended that an additional comment be provided that explains why the commented out code shouldn't be removed.

new TextInputView({
    id: "rightField",
    el: $("#rightField"),
    // NOTE: If this default is specified, it will override the
    //       default defined in the view above.
    //default: "$1.00 $2.00", // literal value
    value: mvc.tokenSafe("$fullName$")
}).render();

In this example the commented out code is left in as an explanation for readers. Since this example comes from example code shipped with the Web Framework, there definitely will be future readers.

Naming Conventions

In general:

  • Files: myclass.js
    • All lower case.
  • Packages: splunkjs/mvc/myclass.js
    • Short. All lower case.
  • Classes: MyClass
    • Upper camel case.
  • Methods, Fields, Variables: myVar
    • Lower camel case.
  • Constants: MY_CONSTANT
    • Loud case.

Additionally:

  • A public class should be declared in a file whose name matches the class name, converted to all lowercase to match file naming conventions. So a class called MyClass should reside in file called myclass.js.

  • Acronyms are considered words for the purposes of the above conventions. Including 2-letter acronyms.

    Yes: HttpUrlConnection
    No:  HTTPURLConnection
    
    Yes: getId
    No:  getID
    
  • Members that are not part of the public API should be named with a leading underscore. Especially when naming methods and fields.

    When in doubt, a method should be considered private API and therefore named with a leading underscore.

    A class's private methods should not be invoked externally unless there is comment in the class describing the exception. For example unit tests occasionally require access to private API, although this is discouraged when possible.

Classes

When a file consists of a single require-compatible class it should be defined with imports at the top, followed by the exported class, optional private classes, and top-level code at the end.

define(function(require, exports, module) {
    var $ = require('jquery');                    // (1) imports
    var _ = require('underscore');
    var Backbone = require('backbone');
    var console = require('util/console');
    var mvc = require('./mvc');
    var Settings = require('./settings');

    var BaseSplunkView = Backbone.View.extend({   // (2) top-level class
        ...
    });
    
    return BaseSplunkView;                        // (3) top-level code
});
  • Imports should be sorted alphabetically by the variable name, case-insensitive.

    Thus symbolically-named imports like $ and _ usually precede alphabetically-named imports. And $ occurs before _ when being pedantic.

    This makes it easy to see whether a prospective import has already been inserted. And alphabetical ordering is also easy to reformat with tools if needed.

  • Classes may be defined in many ways depending on what object-orientation library or manual approach is being used.

    Here are a few examples if your environment is not opinionated:

    Normal Class:

    Underscore:

    var _ = require('underscore');
    
    var BaseSplunkView = function() {
        BaseSplunkView.prototype.initialize.apply(this, arguments);
    };
    _.extend(BaseSplunkView.prototype, {
        initialize: function(...) { ... },
        
        ... methods ...
    });
    

    Vanilla JS:

    var BaseSplunkView = function() {
        BaseSplunkView.prototype.initialize.apply(this, arguments);
    };
    
    BaseSplunkView.prototype.initialize = function(...) { ... };
    
    ... methods ...
    

    Inheriting from a Normal Class:

    Underscore:

    var _ = require('underscore');
    
    // NOTE: Cannot be detected as instanceof BaseSplunkView
    //       using this implementation strategy.
    var DerivedSplunkView = function() {
        DerivedSplunkView.prototype.initialize.apply(this, arguments);
    };
    _.extend(DerivedSplunkView.prototype, BaseSplunkView.prototype, {
        initialize: function(...) {
            BaseSplunkView.prototype.initialize.apply(this, arguments);
            
            ... constructor body ...
        },
        
        ... methods ...
    });
    

    Vanilla JS:

    Please don't implement inheritance manually. Use a library.

    Inheriting from a Backbone Class:

    var Backbone = require('backbone');
    
    var DerivedSplunkView = Backbone.View.extend({
        initialize: function(...) { ... },
        
        ... methods ...
    });
    

    Many other class libraries have a similar syntax, perhaps varying the name of the constructor method.

    Static Class:

    var TokenUtils = {
        ... methods ...
    };
    

Other Recommendations

  • Only one var should be declared per var statement.

    This makes new vars easier to recognize and insert.

    Yes:

    var ichi = 1;
    var ni = 2;
    var san = 3;
    

    No:

    var ichi = 1,
        ni = 2,
        san = 3;
    

    No:

    var ichi = 1, ni = 2, san = 3;
    

    Definitely not:

    var ichi = 1; var ni = 2; var san = 3;
    
  • Variables should have minimal (logical) scope. Therefore they should be declared as close to their first use as possible.

    Thus variables used in a single paragraph should be declared in that paragraph. Variables used in multiple paragraphs should be declared in a (potentially separate) paragraph immediately preceding the first reading paragraph.

    Yes:

    var ship = dock.get('USS Enterprise');
    
    ship.setRedAlert(true);
    
    var desiredEtaInSeconds = 15 * 60; // 15 min
    var distanceToTargetInLightSeconds = 1000;
    ship.setWarpFactor(distanceToTargetInLightSeconds / desiredEtaInSeconds);
    
  • Trailing commas break IE. Don't use them.

    Yes: var items = [
             item1,
             item2
         ];
    
    No:  var items = [
             item1,
             item2,
         ];
    

    (If they didn't break IE, I'd recommend their use because it makes inserting new elements easier.)

  • Avoid using block statements (such as if or while) without braces:

    Yes:

    if (!this.data) {
        return;
    }
    

    No:

    if (!this.data)
        return;
    
  • Avoid having multiple statements on the same line.

    Yes:

    if (!this.data) {
        return;
    }
    

    No:

    if (!this.data) { return; }
    
  • Avoid monkeypatching classes or individual objects by altering prototypes. Especially not native classes. Use the Foreign Method pattern instead.

    Monkeypatching introduces ordering dependencies, makes it difficult to determine where custom grafted functions came from, and can cause conflicts with other JavaScript libraries.

    Yes:

    var empty = function(self) {
        return (self.length === 0);
    }
    
    var a = [];
    if (empty(a)) {
        console.log('winning');
    }
    

    No:

    Array.prototype.empty = function() {
        return (this.length === 0);
    }
    
    var a = [];
    if (a.empty()) {
        console.log('losing');
    }
    
  • Return function values as early as possible to avoid deep nesting of if-statements.

    And do not contort code to follow the old "single function, single return" guideline from C.

    Yes:

    var isPercentage = function(val) {
        if (val < 0) {
            return false;
        }
        
        if (val > 100) {
            return false;
        }
        
        return true;
    }
    

    No:

    var isPercentage = function(val) {
        if (val < 0) {
            return false;
        } else {
            if (val > 100) {
                return false;
            } else {
                return true;
            }
        }
    }
    

    Definitely not:

    var isPercentage = function(val) {
        var result;
        if (val < 0) {
            result = false;
        } else {
            if (val > 100) {
                result = false;
            } else {
                result = true;
            }
        }
        return result;
    }
    
  • Prefer undefined over void(0) or _.isUndefined(...). It is more straightforward.

    Preferred:

    if (x === undefined) { ... }
    

    Avoid:

    if (_.isUndefined(x)) { ... }
    

    Definitely not:

    if (x === void(0)) { ... }
    
  • Do not use eval.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment